]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: introduce -p/--pid option, pids filter working in the early stage
authorMasatake YAMATO <yamato@redhat.com>
Fri, 22 Oct 2021 03:48:47 +0000 (12:48 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Thu, 28 Oct 2021 19:47:57 +0000 (04:47 +0900)
    $ time sudo ./lsfd -Q '(PID == 1) or (PID == 2)' > /dev/null

    real 0m0.508s
    user 0m0.230s
    sys 0m0.267s

    $ time sudo ./lsfd -p 1,2 > /dev/null

    real 0m0.088s
    user 0m0.036s
    sys 0m0.033s

    $ [ $(./lsfd -p 1,2) = $(./lsfd -Q '(PID == 1) or (PID == 2)') ]
    $ echo $?
    0

Signed-off-by: Masatake YAMATO <yamato@redhat.com>
misc-utils/lsfd.1.adoc
misc-utils/lsfd.c
tests/expected/lsfd/option-pid [new file with mode: 0644]
tests/ts/lsfd/option-pid [new file with mode: 0755]

index eaa6e2ef525a73ceb3f3a49eb227f4c3d52f9faa..e6a72d302f1db1496ff55f5587996f1a7b6b02e8 100644 (file)
@@ -57,6 +57,16 @@ Use specified directory as system root.
 *--notruncate*::
 Don't truncate text in columns.
 
+*-p*, *--pid* _pids_::
+Collect information only specified processes.
+_pids_ is a list of pids. A comma or whitespaces can be used as separators.
+You can use this option with *pidof*(1). See "EXAMPLES".
++
+Both *-Q* option with an expression including PID, e.g. -Q (PID == 1),
+and *-p* option, e.g. -p 1, may print the same output but using *-p*
+option is a much more efficient because *-p* option works much earlier
+stage of processing than *-Q* option.
+
 *-Q*, *--filter* _expr_::
 Print the files only satisfying with the condition represented by the _expr_.
 
@@ -277,6 +287,18 @@ Do the same in an alternative way::
 
   # lsfd -Q '(PID == 1) || (PID == 2)'
 
+Do the same in a more efficient way::
+
+  # lsfd --pid 1,2
+
+Whitescapes can be used instead of a comma::
+
+  # lsfd --pid '1 2'
+
+Utilize *pidof*(1) for list the files associated with "firefox"::
+
+  # lsfd --pid "$(pidof firefox)"
+
 List the 1st file descriptor opened by PID 1 process::
 
   # lsfd -Q '(PID == 1) and (FD == 1)'
@@ -337,6 +359,7 @@ mailto:kzak@redhat.com[Karel Zak]
 == SEE ALSO
 
 *lsof*(8)
+*pidof*(1)
 *proc*(5)
 
 include::man-common/bugreports.adoc[]
index 3d27466e78434e57265994531cd2a08405f8656e..8ab57a42803dd7037a31f81df820b8bbe76fc0bf 100644 (file)
@@ -29,6 +29,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <getopt.h>
+#include <ctype.h>
 
 #include <linux/sched.h>
 #include <sys/syscall.h>
@@ -948,7 +949,57 @@ static void read_process(struct lsfd_control *ctl, struct path_cxt *pc,
         ul_path_close_dirfd(pc);
 }
 
-static void collect_processes(struct lsfd_control *ctl)
+static void parse_pids(const char *str, pid_t pids[], int *count, const int pids_size)
+{
+       long v;
+       char *next = NULL;
+
+       if (!(*count < pids_size))
+               err(EXIT_FAILURE, _("too many pids (more than %d) are specified"), pids_size);
+       if (*str == '\0')
+               return;
+
+       errno = 0;
+       v = strtol(str, &next, 10);
+       if (errno)
+               err(EXIT_FAILURE, _("unexpected value for pid specification: %s"), str);
+       if (next == str)
+               errx(EXIT_FAILURE, _("garbage at the end of pid specification: %s"), str);
+       if (v < 0)
+               errx(EXIT_FAILURE, _("out of range value for pid specification: %ld"), v);
+       pids[(*count)++] = (pid_t)v;
+
+       while (next && *next != '\0'
+              && (isspace((unsigned char)*next) || *next == ','))
+               next++;
+       if (*next != '\0')
+               parse_pids(next, pids, count, pids_size);
+}
+
+static int pidcmp(const void *a, const void *b)
+{
+       pid_t pa = *(pid_t *)a;
+       pid_t pb = *(pid_t *)b;
+
+       if (pa < pb)
+               return -1;
+       else if (pa == pb)
+               return 0;
+       else
+               return 1;
+}
+
+static void sort_pids(pid_t pids[], const int count)
+{
+       qsort(pids, count, sizeof(pid_t), pidcmp);
+}
+
+static bool member_pids(const pid_t pid, const pid_t pids[], const int count)
+{
+       return bsearch(&pid, pids, count, sizeof(pid_t), pidcmp)? true: false;
+}
+
+static void collect_processes(struct lsfd_control *ctl, const pid_t pids[], int n_pids)
 {
        DIR *dir;
        struct dirent *d;
@@ -967,7 +1018,8 @@ static void collect_processes(struct lsfd_control *ctl)
 
                if (procfs_dirent_get_pid(d, &pid) != 0)
                        continue;
-               read_process(ctl, pc, pid, 0);
+               if (n_pids == 0 || member_pids(pid, pids, n_pids))
+                       read_process(ctl, pc, pid, 0);
        }
 
        closedir(dir);
@@ -990,6 +1042,7 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" -r, --raw             use raw output format\n"), out);
        fputs(_("     --sysroot <dir>   use specified directory as system root\n"), out);
        fputs(_(" -u, --notruncate      don't truncate text in columns\n"), out);
+       fputs(_(" -p, --pid  <pid(s)>   collect information only specified processes\n"), out);
        fputs(_(" -Q, --filter <expr>   apply display filter\n"), out);
        fputs(_("     --debug-filter    dump the innternal data structure of filter and exit\n"), out);
 
@@ -1051,6 +1104,9 @@ int main(int argc, char *argv[])
        struct lsfd_control ctl = {};
        char  *filter_expr = NULL;
        bool debug_filter = false;
+#define MAX_PIDS 128
+       pid_t pids[MAX_PIDS] = {};
+       int n_pids = 0;
 
        enum {
                OPT_SYSROOT = CHAR_MAX + 1,
@@ -1066,6 +1122,7 @@ int main(int argc, char *argv[])
                { "threads",    no_argument, NULL, 'l' },
                { "notruncate", no_argument, NULL, 'u' },
                { "sysroot",    required_argument, NULL, OPT_SYSROOT },
+               { "pid",        required_argument, NULL, 'p' },
                { "filter",     required_argument, NULL, 'Q' },
                { "debug-filter",no_argument, NULL, OPT_DEBUG_FILTER },
                { NULL, 0, NULL, 0 },
@@ -1076,7 +1133,7 @@ int main(int argc, char *argv[])
        textdomain(PACKAGE);
        close_stdout_atexit();
 
-       while ((c = getopt_long(argc, argv, "no:JrVhluQ:", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:", longopts, NULL)) != -1) {
                switch (c) {
                case 'n':
                        ctl.noheadings = 1;
@@ -1099,6 +1156,9 @@ int main(int argc, char *argv[])
                case OPT_SYSROOT:
                        ctl.sysroot = optarg;
                        break;
+               case 'p':
+                       parse_pids(optarg, pids, &n_pids, MAX_PIDS);
+                       break;
                case 'Q':
                        append_filter_expr(&filter_expr, optarg, true);
                        break;
@@ -1174,11 +1234,14 @@ int main(int argc, char *argv[])
                free(filter_expr);
        }
 
+       if (n_pids > 0)
+               sort_pids(pids, n_pids);
+
        /* collect data */
        initialize_nodevs();
        initialize_classes();
 
-       collect_processes(&ctl);
+       collect_processes(&ctl, pids, n_pids);
 
        convert(&ctl.procs, &ctl);
        emit(&ctl);
diff --git a/tests/expected/lsfd/option-pid b/tests/expected/lsfd/option-pid
new file mode 100644 (file)
index 0000000..f05aa57
--- /dev/null
@@ -0,0 +1,13 @@
+QOUT: 0
+POUT[--pid=1 PID]: 0
+EQ[--pid=1 PID]: 0
+POUT[--pid=PID 1]: 0
+EQ[--pid=PID 1]: 0
+POUT[--pid=1,PID]: 0
+EQ[--pid=1,PID]: 0
+POUT[--pid=PID,1]: 0
+EQ[--pid=PID,1]: 0
+POUT[-p 1 --pid=PID]: 0
+EQ[-p 1 --pid=PID]: 0
+POUT[-p PID --pid=1]: 0
+EQ[-p PID --pid=1]: 0
diff --git a/tests/ts/lsfd/option-pid b/tests/ts/lsfd/option-pid
new file mode 100755 (executable)
index 0000000..7882dc1
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="directory"
+
+. $TS_TOPDIR/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_LSFD"
+ts_check_test_command "$TS_HELPER_MKFDS"
+ts_check_prog "ps"
+
+ts_cd "$TS_OUTDIR"
+
+ts_skip_nonroot
+
+[ "$(ps --no-headers -o comm 1)" = 'systemd'  ] || ts_skip "pid 1 is not systemd"
+
+PID=
+FD=3
+EXPR=
+QOUT=
+POUT=
+
+{
+    coproc MKFDS { "$TS_HELPER_MKFDS" ro-regular-file $FD file=/etc/group; }
+    if read -u ${MKFDS[0]} PID; then
+       EXPR='((PID == '"${PID}"') or (PID == 1)) and (FD == '"$FD"')'
+       QOUT=$(${TS_CMD_LSFD} -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "${EXPR}")
+       echo "QOUT:" $?
+
+       POUT=$(${TS_CMD_LSFD} --pid="1 $PID" -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "(FD == $FD)")
+       echo "POUT[--pid=1 PID]:" $?
+       [ "${QOUT}" = "${POUT}" ]
+       echo "EQ[--pid=1 PID]:" $?
+
+       POUT=$(${TS_CMD_LSFD} --pid="$PID 1" -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "(FD == $FD)")
+       echo "POUT[--pid=PID 1]:" $?
+       [ "${QOUT}" = "${POUT}" ]
+       echo "EQ[--pid=PID 1]:" $?
+
+       POUT=$(${TS_CMD_LSFD} --pid="1,$PID" -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "(FD == $FD)")
+       echo "POUT[--pid=1,PID]:" $?
+       [ "${QOUT}" = "${POUT}" ]
+       echo "EQ[--pid=1,PID]:" $?
+
+       POUT=$(${TS_CMD_LSFD} --pid="$PID,1" -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "(FD == $FD)")
+       echo "POUT[--pid=PID,1]:" $?
+       [ "${QOUT}" = "${POUT}" ]
+       echo "EQ[--pid=PID,1]:" $?
+
+       POUT=$(${TS_CMD_LSFD} -p 1 --pid="$PID"  -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "(FD == $FD)")
+       echo "POUT[-p 1 --pid=PID]:" $?
+       [ "${QOUT}" = "${POUT}" ]
+       echo "EQ[-p 1 --pid=PID]:" $?
+
+       POUT=$(${TS_CMD_LSFD} -p "$PID" --pid=1  -n -o ASSOC,MODE,TYPE,FLAGS,NAME -Q "(FD == $FD)")
+       echo "POUT[-p PID --pid=1]:" $?
+       [ "${QOUT}" = "${POUT}" ]
+       echo "EQ[-p PID --pid=1]:" $?
+
+       kill -CONT ${PID}
+       wait ${MKFDS_PID}
+    fi
+} > $TS_OUTPUT 2>&1
+
+ts_finalize