*--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_.
# 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)'
== SEE ALSO
*lsof*(8)
+*pidof*(1)
*proc*(5)
include::man-common/bugreports.adoc[]
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>
+#include <ctype.h>
#include <linux/sched.h>
#include <sys/syscall.h>
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;
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);
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);
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,
{ "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 },
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;
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;
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);
--- /dev/null
+#!/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