]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: add ERROR as a new type
authorMasatake YAMATO <yamato@redhat.com>
Mon, 15 Jan 2024 19:18:38 +0000 (04:18 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Fri, 2 Feb 2024 20:13:38 +0000 (05:13 +0900)
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 <yamato@redhat.com>
.gitignore
meson.build
misc-utils/Makemodule.am
misc-utils/lsfd-file.c
misc-utils/lsfd.1.adoc
misc-utils/lsfd.c
misc-utils/lsfd.h
tools/Makemodule.am
tools/all_errnos [new file with mode: 0755]

index 3af79d132539aded41b863b4aead491b6e492525..6ecbfa7fedfd25855c543ca6282d5b608fdad64c 100644 (file)
@@ -46,6 +46,7 @@ config/test-driver
 configure
 cscope.out
 depcomp
+errnos.h
 GPATH
 GRTAGS
 GTAGS
index 7bd87e6fa2db3d73b0fb94f17a6c302e6a8c153f..09b8a7829fdc7ad9fc5ef6684aed57268854cfe4 100644 (file)
@@ -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],
index 5f035f84dee734a4f5fca76d8429946e82e3d47a..445f1e44ca2637cac898356a7f34b47b80954d22 100644 (file)
@@ -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
index bfb1f8bcdf8933b5d3fcb15d2d18e8fd39baca4b..7287a1de83b30898e89b21771f9bf9db25637464 100644 (file)
@@ -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
  */
index 512fb231e2850b3c4f1c7268aa8b5a461180d0b9..f7d398da06a4107a2e8e79fda1b571261acf53b7 100644 (file)
@@ -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.
index b4162159c1f068a3cca247549e2582b1dc48e6d3..6286577a50c422164a875bee554a67892d7b7adf 100644 (file)
@@ -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')
index 8e4e603c19c9c65a124403d8a68c9a0350ae7240..6fc83f3480eab13fc3cabd066fb6db527cd2b36e 100644 (file)
@@ -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;
 
 /*
index ebee6c334fefc430be433ed1ac7235fc06308195..10b11c323d8a0de9816c3640565bf864c5d60025 100644 (file)
@@ -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 (executable)
index 0000000..2cfb14b
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Derrived from all_syscalls.
+
+set -e
+
+OUTPUT=errnos.h
+ERRNO_INCLUDES="
+#include <sys/errno.h>
+"
+
+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"