From: Masatake YAMATO Date: Mon, 15 Jan 2024 19:18:38 +0000 (+0900) Subject: lsfd: add ERROR as a new type X-Git-Tag: v2.42-start~545^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a125e2eea7cea4d0bac1404c2e1b1d65d11cc10c;p=thirdparty%2Futil-linux.git lsfd: add ERROR as a new type lsfd ignored file descriptors with that readlink(2) or stat(2) returned errors. With ERORR type, lsfd can print these file descriptors with incomplete information. An example output: COMMAND PID USER ASSOC XMODE TYPE SOURCE MNTID INODE NAME systemd 10677 yamato 31 ????-- ERROR readlink:EACCES slirp4netns 901147 yamato 7 ????-- ERROR stat:EACCES /home/yamato/.local/share/.../merged The format of SOURCE column is SYSCALL:ERRNO. The code for printing errno name is highly based on the code for printing syscall name in enosys.c. Signed-off-by: Masatake YAMATO --- diff --git a/.gitignore b/.gitignore index 3af79d1325..6ecbfa7fed 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ config/test-driver configure cscope.out depcomp +errnos.h GPATH GRTAGS GTAGS diff --git a/meson.build b/meson.build index 7bd87e6fa2..09b8a7829f 100644 --- a/meson.build +++ b/meson.build @@ -2680,12 +2680,18 @@ if not is_disabler(exe) bashcompletions += ['lsblk'] endif +errnos_h = custom_target('errnos.h', + input : 'tools/all_errnos', + output : 'errnos.h', + command : ['tools/all_errnos', cc.cmd_array()] +) + mq_libs = [] mq_libs += cc.find_library('rt', required : true) exe = executable( 'lsfd', - lsfd_sources, + lsfd_sources, errnos_h, include_directories : includes, link_with : [lib_common, lib_smartcols], diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 5f035f84de..445f1e44ca 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -272,6 +272,16 @@ hardlink_CFLAGS = $(AM_CFLAGS) endif if BUILD_LSFD + +misc-utils/lsfd-file.c: errnos.h + +errnos.h: $(top_srcdir)/tools/all_errnos + @echo ' GEN $@' + @$(top_srcdir)/tools/all_errnos $(CC) $(CFLAGS) + +-include errnos.h.deps +CLEANFILES += errnos.h errnos.h.deps + bin_PROGRAMS += lsfd MANPAGES += misc-utils/lsfd.1 dist_noinst_DATA += misc-utils/lsfd.1.adoc diff --git a/misc-utils/lsfd-file.c b/misc-utils/lsfd-file.c index bfb1f8bcdf..7287a1de83 100644 --- a/misc-utils/lsfd-file.c +++ b/misc-utils/lsfd-file.c @@ -225,6 +225,104 @@ const struct file_class abst_class = { .fill_column = abst_fill_column, }; +/* + * Error classes + */ + +/* get_errno_name() --- the private replacement of strerrorname_np(3). + * Some platforms don't have strerrorname_np. + * + * Mainly copied from misc-utils/enosys.c. + */ +struct errno_s { + const char *const name; + long number; +}; + +static const struct errno_s errnos[] = { +#define UL_ERRNO(name, nr) { name, nr }, +#include "errnos.h" +#undef UL_ERRNO +}; + +static const char *get_errno_name(int ern) +{ + for (size_t i = 0; i < ARRAY_SIZE(errnos); i ++) { + if (errnos[i].number == ern) + return errnos[i].name; + } + return NULL; +} + +static bool error_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file __attribute__((__unused__)), + struct libscols_line *ln, + int column_id, + size_t column_index) +{ + char *str = NULL; + const char *ename; + + switch(column_id) { + case COL_TYPE: + if (scols_line_set_data(ln, column_index, "ERROR")) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + case COL_SOURCE: + ename = get_errno_name(file->error.number); + if (ename) + xasprintf(&str, "%s:%s", + file->error.syscall, ename); + else + xasprintf(&str, "%s:unknown(%d)", + file->error.syscall, file->error.number); + if (scols_line_refer_data(ln, column_index, str)) + err(EXIT_FAILURE, _("failed to add output data")); + return true; + default: + return false; + } +} + +static const struct file_class error_class = { + .super = &abst_class, + .size = sizeof(struct file), + .fill_column = error_fill_column, +}; + +static void init_error_content(struct file *file) +{ + file->is_error = 1; +} + +static bool readlink_error_fill_column(struct proc *proc __attribute__((__unused__)), + struct file *file __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__))) +{ + switch(column_id) { + case COL_NAME: + case COL_KNAME: + return true; + default: + return false; + } +} + +const struct file_class readlink_error_class = { + .super = &error_class, + .size = sizeof(struct file), + .initialize_content = init_error_content, + .fill_column = readlink_error_fill_column, +}; + +const struct file_class stat_error_class = { + .super = &error_class, + .size = sizeof(struct file), + .initialize_content = init_error_content, +}; + /* * Concrete file class */ diff --git a/misc-utils/lsfd.1.adoc b/misc-utils/lsfd.1.adoc index 512fb231e2..f7d398da06 100644 --- a/misc-utils/lsfd.1.adoc +++ b/misc-utils/lsfd.1.adoc @@ -463,6 +463,8 @@ socket system call: SOURCE <``string``>:: File system, partition, or device containing the file. +For the association having ERROR as the value for _TYPE_ coulumn, *lsfd* +fills this column with _syscall_:_errno_. STTYPE <``string``>:: Raw file types returned from *stat*(2): BLK, CHR, DIR, FIFO, LINK, REG, SOCK, or UNKN. @@ -501,6 +503,9 @@ TYPE <``string``>:: Cooked version of _STTYPE_. It is same as _STTYPE_ with exceptions. For _SOCK_, print the value for _SOCK.PROTONAME_. For _UNKN_, print the value for _AINODECLASS_ if _SOURCE_ is `anon_inodefs`. ++ +If *lsfd* gets an error when calling a syscall to know about a target +file descriptor, *lsfd* fills this column for it with ERROR. UDP.LADDR <``string``>:: Local IP address and local UDP port. diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c index b4162159c1..6286577a50 100644 --- a/misc-utils/lsfd.c +++ b/misc-utils/lsfd.c @@ -704,6 +704,46 @@ static struct file *new_file(struct proc *proc, const struct file_class *class, return file; } +static struct file *new_readlink_error_file(struct proc *proc, int error_no, int association) +{ + struct file *file; + + file = xcalloc(1, readlink_error_class.size); + file->class = &readlink_error_class; + + file->proc = proc; + + INIT_LIST_HEAD(&file->files); + list_add_tail(&file->files, &proc->files); + + file->error.syscall = "readlink"; + file->error.number = error_no; + file->association = association; + file->name = NULL; + + return file; +} + +static struct file *new_stat_error_file(struct proc *proc, const char *name, int error_no, int association) +{ + struct file *file; + + file = xcalloc(1, stat_error_class.size); + file->class = &stat_error_class; + + file->proc = proc; + + INIT_LIST_HEAD(&file->files); + list_add_tail(&file->files, &proc->files); + + file->error.syscall = "stat"; + file->error.number = error_no; + file->association = association; + file->name = xstrdup(name); + + return file; +} + static struct file *copy_file(struct file *old, int new_association) { struct file *file = xcalloc(1, old->class->size); @@ -799,22 +839,20 @@ static struct file *collect_file_symlink(struct path_cxt *pc, struct file *f, *prev; if (ul_path_readlink(pc, sym, sizeof(sym), name) < 0) - return NULL; - + f = new_readlink_error_file(proc, errno, assoc); /* The /proc/#/{fd,ns} often contains the same file (e.g. /dev/tty) * more than once. Let's try to reuse the previous file if the real * path is the same to save stat() call. */ - prev = list_last_entry(&proc->files, struct file, files); - if (prev && prev->name && strcmp(prev->name, sym) == 0) + else if ((prev = list_last_entry(&proc->files, struct file, files)) + && (!prev->is_error) + && prev->name && strcmp(prev->name, sym) == 0) f = copy_file(prev, assoc); + else if (ul_path_stat(pc, &sb, 0, name) < 0) + f = new_stat_error_file(proc, sym, errno, assoc); else { - const struct file_class *class; - - if (ul_path_stat(pc, &sb, 0, name) < 0) - return NULL; + const struct file_class *class = stat2class(&sb); - class = stat2class(&sb); if (sockets_only /* A nsfs file is not a socket but the nsfs file can * be used as a entry point to collect information from @@ -828,6 +866,9 @@ static struct file *collect_file_symlink(struct path_cxt *pc, file_init_content(f); + if (f->is_error) + return f; + if (is_association(f, NS_MNT)) proc->ns_mnt = f->stat.st_ino; else if (is_association(f, NS_NET)) @@ -909,7 +950,8 @@ static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc) */ prev = list_last_entry(&proc->files, struct file, files); - if (prev && prev->stat.st_dev == devno && prev->stat.st_ino == ino) + if (prev && (!prev->is_error) + && prev->stat.st_dev == devno && prev->stat.st_ino == ino) f = copy_file(prev, -assoc); else if ((path = strchr(buf, '/'))) { rtrim_whitespace((unsigned char *) path); @@ -927,10 +969,11 @@ static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc) try_map_files: snprintf(map_file, sizeof(map_file), "map_files/%"PRIx64"-%"PRIx64, start, end); if (ul_path_readlink(pc, sym, sizeof(sym), map_file) < 0) - return; - if (ul_path_stat(pc, &sb, 0, map_file) < 0) - return; - f = new_file(proc, stat2class(&sb), &sb, sym, -assoc); + f = new_readlink_error_file(proc, errno, -assoc); + else if (ul_path_stat(pc, &sb, 0, map_file) < 0) + f = new_stat_error_file(proc, sym, errno, -assoc); + else + f = new_file(proc, stat2class(&sb), &sb, sym, -assoc); } if (modestr[0] == 'r') diff --git a/misc-utils/lsfd.h b/misc-utils/lsfd.h index 8e4e603c19..6fc83f3480 100644 --- a/misc-utils/lsfd.h +++ b/misc-utils/lsfd.h @@ -174,7 +174,13 @@ struct file { const struct file_class *class; int association; char *name; - struct stat stat; + union { + struct stat stat; + struct { + int number; + const char *syscall; + } error; + }; mode_t mode; struct proc *proc; @@ -187,7 +193,8 @@ struct file { uint8_t locked_read:1, locked_write:1, - multiplexed:1; + multiplexed:1, + is_error:1; }; #define is_opened_file(_f) ((_f)->association >= 0) @@ -211,7 +218,8 @@ struct file_class { const struct ipc_class *(*get_ipc_class)(struct file *file); }; -extern const struct file_class abst_class, file_class, cdev_class, bdev_class, sock_class, unkn_class, fifo_class, +extern const struct file_class abst_class, readlink_error_class, stat_error_class, + file_class, cdev_class, bdev_class, sock_class, unkn_class, fifo_class, nsfs_file_class, mqueue_file_class; /* diff --git a/tools/Makemodule.am b/tools/Makemodule.am index ebee6c334f..10b11c323d 100644 --- a/tools/Makemodule.am +++ b/tools/Makemodule.am @@ -58,4 +58,5 @@ EXTRA_DIST += \ tools/asciidoctor-includetracker.rb \ tools/asciidoctor-unicodeconverter.rb \ \ + tools/all_errnos \ tools/all_syscalls diff --git a/tools/all_errnos b/tools/all_errnos new file mode 100755 index 0000000000..2cfb14bc3a --- /dev/null +++ b/tools/all_errnos @@ -0,0 +1,17 @@ +#!/bin/bash + +# Derrived from all_syscalls. + +set -e + +OUTPUT=errnos.h +ERRNO_INCLUDES=" +#include +" + +trap 'rm $OUTPUT $OUTPUT.deps' ERR + +"$@" -MD -MF "$OUTPUT.deps" <<< "$ERRNO_INCLUDES" -dM -E - \ + | gawk 'match($0, /^#[ \t]*define[ \t]*E([^ ]+)/, res) { print "UL_ERRNO(\"E" res[1] "\", E" res[1] ")" }' \ + | sort \ + > "$OUTPUT"