summaryrefslogtreecommitdiffstats
path: root/misc-utils/lsfd.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc-utils/lsfd.c')
-rw-r--r--misc-utils/lsfd.c2054
1 files changed, 2054 insertions, 0 deletions
diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c
new file mode 100644
index 0000000..f8537a7
--- /dev/null
+++ b/misc-utils/lsfd.c
@@ -0,0 +1,2054 @@
+/*
+ * lsfd(1) - list file descriptors
+ *
+ * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
+ * Written by Masatake YAMATO <yamato@redhat.com>
+ * Karel Zak <kzak@redhat.com>
+ *
+ * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu>
+ * It supports multiple OSes. lsfd specializes to Linux.
+ *
+ * This program 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 program is distributed in the hope that it would 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <search.h>
+
+#include <linux/sched.h>
+#include <sys/syscall.h>
+#include <linux/kcmp.h>
+static int kcmp(pid_t pid1, pid_t pid2, int type,
+ unsigned long idx1, unsigned long idx2)
+{
+ return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2);
+}
+
+/* See proc(5).
+ * Defined in linux/include/linux/sched.h private header file. */
+#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
+
+#include "c.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "list.h"
+#include "closestream.h"
+#include "strutils.h"
+#include "procfs.h"
+#include "fileutils.h"
+#include "idcache.h"
+#include "pathnames.h"
+
+#include "libsmartcols.h"
+
+#include "lsfd.h"
+#include "lsfd-filter.h"
+#include "lsfd-counter.h"
+
+/*
+ * /proc/$pid/mountinfo entries
+ */
+struct nodev {
+ struct list_head nodevs;
+ unsigned long minor;
+ char *filesystem;
+};
+
+struct nodev_table {
+#define NODEV_TABLE_SIZE 97
+ struct list_head tables[NODEV_TABLE_SIZE];
+};
+static struct nodev_table nodev_table;
+
+struct name_manager {
+ struct idcache *cache;
+ unsigned long next_id;
+};
+
+/*
+ * /proc/devices entries
+ */
+struct devdrv {
+ struct list_head devdrvs;
+ unsigned long major;
+ char *name;
+};
+
+static struct list_head chrdrvs;
+static struct list_head blkdrvs;
+
+/*
+ * IPC table
+ */
+
+#define IPC_TABLE_SIZE 997
+struct ipc_table {
+ struct list_head tables[IPC_TABLE_SIZE];
+};
+
+static struct ipc_table ipc_table;
+
+/*
+ * Column related stuffs
+ */
+
+/* column names */
+struct colinfo {
+ const char *name;
+ double whint;
+ int flags;
+ int json_type;
+ const char *help;
+};
+
+/* columns descriptions */
+static const struct colinfo infos[] = {
+ [COL_AINODECLASS] = { "AINODECLASS",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("class of anonymous inode") },
+ [COL_ASSOC] = { "ASSOC",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("association between file and process") },
+ [COL_BLKDRV] = { "BLKDRV",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("block device driver name resolved by /proc/devices") },
+ [COL_CHRDRV] = { "CHRDRV",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("character device driver name resolved by /proc/devices") },
+ [COL_COMMAND] = { "COMMAND",
+ 0.3, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
+ N_("command of the process opening the file") },
+ [COL_DELETED] = { "DELETED",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
+ N_("reachability from the file system") },
+ [COL_DEV] = { "DEV",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("ID of device containing file") },
+ [COL_DEVTYPE] = { "DEVTYPE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("device type (blk, char, or nodev)") },
+ [COL_ENDPOINTS] = { "ENDPOINTS",
+ 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING,
+ N_("IPC endpoints information communicated with the fd") },
+ [COL_FLAGS] = { "FLAGS",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("flags specified when opening the file") },
+ [COL_FD] = { "FD",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("file descriptor for the file") },
+ [COL_FUID] = { "FUID",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("user ID number of the file's owner") },
+ [COL_INODE] = { "INODE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("inode number") },
+ [COL_INET_LADDR] = { "INET.LADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("local IP address") },
+ [COL_INET_RADDR] = { "INET.RADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("remote IP address") },
+ [COL_INET6_LADDR] = { "INET6.LADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("local IPv6 address") },
+ [COL_INET6_RADDR] = { "INET6.RADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("remote IPv6 address") },
+ [COL_KNAME] = { "KNAME",
+ 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
+ N_("name of the file (raw)") },
+ [COL_KTHREAD] = { "KTHREAD",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
+ N_("opened by a kernel thread") },
+ [COL_MAJMIN] = { "MAJ:MIN",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("device ID for special, or ID of device containing file") },
+ [COL_MAPLEN] = { "MAPLEN",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("length of file mapping (in page)") },
+ [COL_MISCDEV] = { "MISCDEV",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("misc character device name resolved by /proc/misc") },
+ [COL_MNT_ID] = { "MNTID",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("mount id") },
+ [COL_MODE] = { "MODE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("access mode (rwx)") },
+ [COL_NAME] = { "NAME",
+ 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
+ N_("name of the file (cooked)") },
+ [COL_NETLINK_GROUPS] = { "NETLINK.GROUPS",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("netlink multicast groups") },
+ [COL_NETLINK_LPORT] = { "NETLINK.LPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("netlink local port id") },
+ [COL_NETLINK_PROTOCOL] = { "NETLINK.PROTOCOL",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("netlink protocol") },
+ [COL_NLINK] = { "NLINK",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("link count") },
+ [COL_NS_NAME] = { "NS.NAME",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("name of the namespace (NS.TYPE:[INODE])") },
+ [COL_NS_TYPE] = { "NS.TYPE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("type of the namespace") },
+ [COL_OWNER] = { "OWNER",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("owner of the file") },
+ [COL_PACKET_IFACE] = { "PACKET.IFACE",
+ 0, SCOLS_FL_RIGHT,SCOLS_JSON_STRING,
+ N_("net interface associated with the packet socket") },
+ [COL_PACKET_PROTOCOL] = { "PACKET.PROTOCOL",
+ 0, SCOLS_FL_RIGHT,SCOLS_JSON_STRING,
+ N_("L3 protocol associated with the packet socket") },
+ [COL_PARTITION] = { "PARTITION",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("block device name resolved by /proc/partition") },
+ [COL_PID] = { "PID",
+ 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("PID of the process opening the file") },
+ [COL_PIDFD_COMM] = { "PIDFD.COMM",
+ 0.2, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
+ N_("command of the process targeted by the pidfd") },
+ [COL_PIDFD_NSPID] = { "PIDFD.NSPID",
+ 0.2, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
+ N_("NSpid field in fdinfo of the pidfd") },
+ [COL_PIDFD_PID] = { "PIDFD.PID",
+ 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("PID of the process targeted by the pidfd") },
+ [COL_PING_ID] = { "PING.ID",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("ICMP echo request ID") },
+ [COL_POS] = { "POS",
+ 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("file position") },
+ [COL_RAW_PROTOCOL] = { "RAW.PROTOCOL",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("protocol number of the raw socket") },
+ [COL_RDEV] = { "RDEV",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("device ID (if special file)") },
+ [COL_SIZE] = { "SIZE",
+ 4, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("file size"), },
+ [COL_SOCK_LISTENING] = { "SOCK.LISTENING",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
+ N_("listening socket") },
+ [COL_SOCK_NETNS] = { "SOCK.NETNS",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("inode identifying network namespace where the socket belongs to") },
+ [COL_SOCK_PROTONAME] = { "SOCK.PROTONAME",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("protocol name") },
+ [COL_SOCK_STATE] = { "SOCK.STATE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("State of socket") },
+ [COL_SOCK_TYPE] = { "SOCK.TYPE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("Type of socket") },
+ [COL_SOURCE] = { "SOURCE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("file system, partition, or device containing file") },
+ [COL_STTYPE] = { "STTYPE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("file type (raw)") },
+ [COL_TCP_LADDR] = { "TCP.LADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("local TCP address (INET address:TCP port)") },
+ [COL_TCP_RADDR] = { "TCP.RADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("remote TCP address (INET address:TCP port)") },
+ [COL_TCP_LPORT] = { "TCP.LPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("local TCP port") },
+ [COL_TCP_RPORT] = { "TCP.RPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("remote TCP port") },
+ [COL_TID] = { "TID",
+ 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("thread ID of the process opening the file") },
+ [COL_TYPE] = { "TYPE",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("file type (cooked)") },
+ [COL_UDP_LADDR] = { "UDP.LADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("local UDP address (INET address:UDP port)") },
+ [COL_UDP_RADDR] = { "UDP.RADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("remote UDP address (INET address:UDP port)") },
+ [COL_UDP_LPORT] = { "UDP.LPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("local UDP port") },
+ [COL_UDP_RPORT] = { "UDP.RPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("remote UDP port") },
+ [COL_UDPLITE_LADDR] = { "UDPLITE.LADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("local UDPLite address (INET address:UDPLite port)") },
+ [COL_UDPLITE_RADDR] = { "UDPLITE.RADDR",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("remote UDPLite address (INET address:UDPLite port)") },
+ [COL_UDPLITE_LPORT] = { "UDPLITE.LPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("local UDPLite port") },
+ [COL_UDPLITE_RPORT] = { "UDPLITE.RPORT",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("remote UDPLite port") },
+ [COL_UID] = { "UID",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("user ID number of the process") },
+ [COL_UNIX_PATH] = { "UNIX.PATH",
+ 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
+ N_("filesystem pathname for UNIX domain socket") },
+ [COL_USER] = { "USER",
+ 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("user of the process") },
+};
+
+static const int default_columns[] = {
+ COL_COMMAND,
+ COL_PID,
+ COL_USER,
+ COL_ASSOC,
+ COL_MODE,
+ COL_TYPE,
+ COL_SOURCE,
+ COL_MNT_ID,
+ COL_INODE,
+ COL_NAME,
+};
+
+static const int default_threads_columns[] = {
+ COL_COMMAND,
+ COL_PID,
+ COL_TID,
+ COL_USER,
+ COL_ASSOC,
+ COL_MODE,
+ COL_TYPE,
+ COL_SOURCE,
+ COL_MNT_ID,
+ COL_INODE,
+ COL_NAME,
+};
+
+static int columns[ARRAY_SIZE(infos) * 2] = {-1};
+static size_t ncolumns;
+
+static ino_t *mnt_namespaces;
+static size_t nspaces;
+
+struct counter_spec {
+ struct list_head specs;
+ const char *name;
+ const char *expr;
+};
+
+static const struct counter_spec default_counter_specs[] = {
+ {
+ .name = N_("processes"),
+ .expr = "ASSOC == 'cwd'",
+ },
+ {
+ .name = N_("root owned processes"),
+ .expr = "(ASSOC == 'cwd') && (UID == 0)",
+ },
+ {
+ .name = N_("kernel threads"),
+ .expr = "(ASSOC == 'cwd') && KTHREAD",
+ },
+ {
+ .name = N_("open files"),
+ .expr = "FD >= 0",
+ },
+ {
+ .name = N_("RO open files"),
+ .expr = "(FD >= 0) and (MODE == 'r--')",
+ },
+ {
+ .name = N_("WO open files"),
+ .expr = "(FD >= 0) and (MODE == '-w-')",
+ },
+ {
+ .name = N_("shared mappings"),
+ .expr = "ASSOC == 'shm'",
+ },
+ {
+ .name = N_("RO shared mappings"),
+ .expr = "(ASSOC == 'shm') and (MODE == 'r--')",
+ },
+ {
+ .name = N_("WO shared mappings"),
+ .expr = "(ASSOC == 'shm') and (MODE == '-w-')",
+ },
+ {
+ .name = N_("regular files"),
+ .expr = "(FD >= 0) && (STTYPE == 'REG')",
+ },
+ {
+ .name = N_("directories"),
+ .expr = "(FD >= 0) && (STTYPE == 'DIR')",
+ },
+ {
+ .name = N_("sockets"),
+ .expr = "(FD >= 0) && (STTYPE == 'SOCK')",
+ },
+ {
+ .name = N_("fifos/pipes"),
+ .expr = "(FD >= 0) && (STTYPE == 'FIFO')",
+ },
+ {
+ .name = N_("character devices"),
+ .expr = "(FD >= 0) && (STTYPE == 'CHR')",
+ },
+ {
+ .name = N_("block devices"),
+ .expr = "(FD >= 0) && (STTYPE == 'BLK')",
+ },
+ {
+ .name = N_("unknown types"),
+ .expr = "(FD >= 0) && (STTYPE == 'UNKN')",
+ }
+};
+
+struct lsfd_control {
+ struct libscols_table *tb; /* output */
+ struct list_head procs; /* list of all processes */
+
+ unsigned int noheadings : 1,
+ raw : 1,
+ json : 1,
+ notrunc : 1,
+ threads : 1,
+ show_main : 1, /* print main table */
+ show_summary : 1, /* print summary/counters */
+ sockets_only : 1; /* display only SOCKETS */
+
+ struct lsfd_filter *filter;
+ struct lsfd_counter **counters; /* NULL terminated array. */
+};
+
+static void *proc_tree; /* for tsearch/tfind */
+
+static int proc_tree_compare(const void *a, const void *b)
+{
+ return ((struct proc *)a)->pid - ((struct proc *)b)->pid;
+}
+
+struct proc *get_proc(pid_t pid)
+{
+ struct proc key = { .pid = pid };
+ struct proc **node = tfind(&key, &proc_tree, proc_tree_compare);
+ if (node)
+ return *node;
+ return NULL;
+}
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+
+ return LSFD_FILTER_UNKNOWN_COL_ID;
+}
+
+static int column_name_to_id_cb(const char *name, void *data __attribute__((__unused__)))
+{
+ return column_name_to_id(name, strlen(name));
+}
+
+static int get_column_id(int num)
+{
+ assert(num >= 0);
+ assert((size_t) num < ncolumns);
+ assert(columns[num] < (int) ARRAY_SIZE(infos));
+
+ return columns[num];
+}
+
+static const struct colinfo *get_column_info(int num)
+{
+ return &infos[ get_column_id(num) ];
+}
+
+static struct libscols_column *add_column(struct libscols_table *tb, const struct colinfo *col)
+{
+ struct libscols_column *cl;
+ int flags = col->flags;
+
+ cl = scols_table_new_column(tb, col->name, col->whint, flags);
+ if (cl) {
+ scols_column_set_json_type(cl, col->json_type);
+ if (col->flags & SCOLS_FL_WRAP) {
+ scols_column_set_wrapfunc(cl,
+ scols_wrapnl_chunksize,
+ scols_wrapnl_nextchunk,
+ NULL);
+ scols_column_set_safechars(cl, "\n");
+ }
+ }
+
+ return cl;
+}
+
+static struct libscols_column *add_column_by_id_cb(struct libscols_table *tb, int colid, void *data)
+{
+ struct libscols_column *cl;
+
+ if (ncolumns >= ARRAY_SIZE(columns))
+ errx(EXIT_FAILURE, _("too many columns are added via filter expression"));
+
+ assert(colid < LSFD_N_COLS);
+
+ cl = add_column(tb, infos + colid);
+ if (!cl)
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+ columns[ncolumns++] = colid;
+
+ if (colid == COL_TID) {
+ struct lsfd_control *ctl = data;
+ ctl->threads = 1;
+ }
+
+ return cl;
+}
+
+static int has_mnt_ns(ino_t id)
+{
+ size_t i;
+
+ for (i = 0; i < nspaces; i++) {
+ if (mnt_namespaces[i] == id)
+ return 1;
+ }
+ return 0;
+}
+
+static void add_mnt_ns(ino_t id)
+{
+ size_t nmax = 0;
+
+ if (nspaces)
+ nmax = (nspaces + 16) / 16 * 16;
+ if (nmax <= nspaces + 1) {
+ nmax += 16;
+ mnt_namespaces = xrealloc(mnt_namespaces,
+ sizeof(ino_t) * nmax);
+ }
+ mnt_namespaces[nspaces++] = id;
+}
+
+static const struct file_class *stat2class(struct stat *sb)
+{
+ dev_t dev;
+
+ assert(sb);
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFCHR:
+ return &cdev_class;
+ case S_IFBLK:
+ return &bdev_class;
+ case S_IFSOCK:
+ return &sock_class;
+ case S_IFIFO:
+ return &fifo_class;
+ case S_IFLNK:
+ case S_IFDIR:
+ return &file_class;
+ case S_IFREG:
+ dev = sb->st_dev;
+ if (major(dev) != 0)
+ return &file_class;
+
+ if (is_nsfs_dev(dev))
+ return &nsfs_file_class;
+
+ return &file_class;
+ default:
+ break;
+ }
+
+ return &unkn_class;
+}
+
+static struct file *new_file(struct proc *proc, const struct file_class *class)
+{
+ struct file *file;
+
+ file = xcalloc(1, class->size);
+ file->proc = proc;
+
+ INIT_LIST_HEAD(&file->files);
+ list_add_tail(&file->files, &proc->files);
+
+ return file;
+}
+
+static struct file *copy_file(struct file *old)
+{
+ struct file *file = xcalloc(1, old->class->size);
+
+ INIT_LIST_HEAD(&file->files);
+ file->proc = old->proc;
+ list_add_tail(&file->files, &old->proc->files);
+
+ file->class = old->class;
+ file->association = old->association;
+ file->name = xstrdup(old->name);
+ file->stat = old->stat;
+
+ return file;
+}
+
+static void file_set_path(struct file *file, struct stat *sb, const char *name, int association)
+{
+ const struct file_class *class = stat2class(sb);
+
+ assert(class);
+
+ file->class = class;
+ file->association = association;
+ file->name = xstrdup(name);
+ file->stat = *sb;
+}
+
+static void file_init_content(struct file *file)
+{
+ if (file->class && file->class->initialize_content)
+ file->class->initialize_content(file);
+}
+
+static void free_file(struct file *file)
+{
+ const struct file_class *class = file->class;
+
+ while (class) {
+ if (class->free_content)
+ class->free_content(file);
+ class = class->super;
+ }
+ free(file);
+}
+
+
+static struct proc *new_process(pid_t pid, struct proc *leader)
+{
+ struct proc *proc = xcalloc(1, sizeof(*proc));
+
+ proc->pid = pid;
+ proc->leader = leader? leader: proc;
+ proc->command = NULL;
+
+ INIT_LIST_HEAD(&proc->files);
+ INIT_LIST_HEAD(&proc->procs);
+
+ proc->kthread = 0;
+ return proc;
+}
+
+static void free_proc(struct proc *proc)
+{
+ list_free(&proc->files, struct file, files, free_file);
+
+ free(proc->command);
+ free(proc);
+}
+
+static void read_fdinfo(struct file *file, FILE *fdinfo)
+{
+ char buf[1024];
+
+ while (fgets(buf, sizeof(buf), fdinfo)) {
+ const struct file_class *class;
+ char *val = strchr(buf, ':');
+
+ if (!val)
+ continue;
+ *val++ = '\0'; /* terminate key */
+
+ val = (char *) skip_space(val);
+ rtrim_whitespace((unsigned char *) val);
+
+ class = file->class;
+ while (class) {
+ if (class->handle_fdinfo
+ && class->handle_fdinfo(file, buf, val))
+ break;
+ class = class->super;
+ }
+ }
+}
+
+static struct file *collect_file_symlink(struct path_cxt *pc,
+ struct proc *proc,
+ const char *name,
+ int assoc,
+ bool sockets_only)
+{
+ char sym[PATH_MAX] = { '\0' };
+ struct stat sb;
+ struct file *f, *prev;
+
+ if (ul_path_readlink(pc, sym, sizeof(sym), name) < 0)
+ return NULL;
+
+ /* 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) {
+ f = copy_file(prev);
+ f->association = assoc;
+ } else {
+ const struct file_class *class;
+
+ if (ul_path_stat(pc, &sb, 0, name) < 0)
+ return NULL;
+
+ class = stat2class(&sb);
+ if (sockets_only
+ /* A nsfs is not a socket but the nsfs can be used to
+ * collect information from other network namespaces.
+ * Besed on the information, various columns of sockets.
+ */
+ && (class != &sock_class)&& (class != &nsfs_file_class))
+ return NULL;
+ f = new_file(proc, class);
+ file_set_path(f, &sb, sym, assoc);
+ }
+
+ file_init_content(f);
+
+ if (is_association(f, NS_MNT))
+ proc->ns_mnt = f->stat.st_ino;
+ else if (is_association(f, NS_NET))
+ load_sock_xinfo(pc, name, f->stat.st_ino);
+
+ else if (assoc >= 0) {
+ /* file-descriptor based association */
+ FILE *fdinfo;
+
+ if (ul_path_stat(pc, &sb, AT_SYMLINK_NOFOLLOW, name) == 0)
+ f->mode = sb.st_mode;
+
+ if (is_nsfs_dev(f->stat.st_dev))
+ load_sock_xinfo(pc, name, f->stat.st_ino);
+
+ fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%d", assoc);
+ if (fdinfo) {
+ read_fdinfo(f, fdinfo);
+ fclose(fdinfo);
+ }
+ }
+
+ return f;
+}
+
+/* read symlinks from /proc/#/fd
+ */
+static void collect_fd_files(struct path_cxt *pc, struct proc *proc,
+ bool sockets_only)
+{
+ DIR *sub = NULL;
+ struct dirent *d = NULL;
+ char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))];
+
+ while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) {
+ uint64_t num;
+
+ if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */
+ continue;
+
+ snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num);
+ collect_file_symlink(pc, proc, path, num, sockets_only);
+ }
+}
+
+static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc)
+{
+ uint64_t start, end, offset, ino;
+ unsigned long major, minor;
+ enum association assoc = ASSOC_MEM;
+ struct stat sb;
+ struct file *f, *prev;
+ char *path, modestr[5];
+ dev_t devno;
+
+ /* read rest of the map */
+ if (sscanf(buf, "%"SCNx64 /* start */
+ "-%"SCNx64 /* end */
+ " %4[^ ]" /* mode */
+ " %"SCNx64 /* offset */
+ " %lx:%lx" /* maj:min */
+ " %"SCNu64, /* inode */
+
+ &start, &end, modestr, &offset,
+ &major, &minor, &ino) != 7)
+ return;
+
+ /* Skip private anonymous mappings. */
+ if (major == 0 && minor == 0 && ino == 0)
+ return;
+
+ devno = makedev(major, minor);
+
+ if (modestr[3] == 's')
+ assoc = ASSOC_SHM;
+
+ /* The map usually contains the same file more than once, try to reuse
+ * the previous file (if devno and ino are the same) to save stat() call.
+ */
+ prev = list_last_entry(&proc->files, struct file, files);
+
+ if (prev && prev->stat.st_dev == devno && prev->stat.st_ino == ino) {
+ f = copy_file(prev);
+ f->association = -assoc;
+ } else if ((path = strchr(buf, '/'))) {
+ rtrim_whitespace((unsigned char *) path);
+ if (stat(path, &sb) < 0)
+ /* If a file is mapped but deleted from the file system,
+ * "stat by the file name" may not work. In that case,
+ */
+ goto try_map_files;
+ f = new_file(proc, stat2class(&sb));
+ if (!f)
+ return;
+
+ file_set_path(f, &sb, path, -assoc);
+ } else {
+ /* As used in tcpdump, AF_PACKET socket can be mmap'ed. */
+ char map_file[sizeof("map_files/0000000000000000-ffffffffffffffff")];
+ char sym[PATH_MAX] = { '\0' };
+
+ try_map_files:
+ snprintf(map_file, sizeof(map_file), "map_files/%"PRIx64"-%"PRIx64, start, end);
+ if (ul_path_stat(pc, &sb, 0, map_file) < 0)
+ return;
+ if (ul_path_readlink(pc, sym, sizeof(sym), map_file) < 0)
+ return;
+ f = new_file(proc, stat2class(&sb));
+ if (!f)
+ return;
+
+ file_set_path(f, &sb, sym, -assoc);
+ }
+
+ if (modestr[0] == 'r')
+ f->mode |= S_IRUSR;
+ if (modestr[1] == 'w')
+ f->mode |= S_IWUSR;
+ if (modestr[2] == 'x')
+ f->mode |= S_IXUSR;
+
+ f->map_start = start;
+ f->map_end = end;
+ f->pos = offset;
+
+ file_init_content(f);
+}
+
+static void collect_mem_files(struct path_cxt *pc, struct proc *proc)
+{
+ FILE *fp;
+ char buf[BUFSIZ];
+
+ fp = ul_path_fopen(pc, "r", "maps");
+ if (!fp)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp))
+ parse_maps_line(pc, buf, proc);
+
+ fclose(fp);
+}
+
+static void collect_outofbox_files(struct path_cxt *pc,
+ struct proc *proc,
+ enum association assocs[],
+ const char *names[],
+ size_t count,
+ bool sockets_only)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ collect_file_symlink(pc, proc, names[assocs[i]], assocs[i] * -1,
+ sockets_only);
+}
+
+static void collect_execve_file(struct path_cxt *pc, struct proc *proc,
+ bool sockets_only)
+{
+ enum association assocs[] = { ASSOC_EXE };
+ const char *names[] = {
+ [ASSOC_EXE] = "exe",
+ };
+ collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
+ sockets_only);
+}
+
+static void collect_fs_files(struct path_cxt *pc, struct proc *proc,
+ bool sockets_only)
+{
+ enum association assocs[] = { ASSOC_EXE, ASSOC_CWD, ASSOC_ROOT };
+ const char *names[] = {
+ [ASSOC_CWD] = "cwd",
+ [ASSOC_ROOT] = "root",
+ };
+ collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
+ sockets_only);
+}
+
+static void collect_namespace_files(struct path_cxt *pc, struct proc *proc)
+{
+ enum association assocs[] = {
+ ASSOC_NS_CGROUP,
+ ASSOC_NS_IPC,
+ ASSOC_NS_MNT,
+ ASSOC_NS_NET,
+ ASSOC_NS_PID,
+ ASSOC_NS_PID4C,
+ ASSOC_NS_TIME,
+ ASSOC_NS_TIME4C,
+ ASSOC_NS_USER,
+ ASSOC_NS_UTS,
+ };
+ const char *names[] = {
+ [ASSOC_NS_CGROUP] = "ns/cgroup",
+ [ASSOC_NS_IPC] = "ns/ipc",
+ [ASSOC_NS_MNT] = "ns/mnt",
+ [ASSOC_NS_NET] = "ns/net",
+ [ASSOC_NS_PID] = "ns/pid",
+ [ASSOC_NS_PID4C] = "ns/pid_for_children",
+ [ASSOC_NS_TIME] = "ns/time",
+ [ASSOC_NS_TIME4C] = "ns/time_for_children",
+ [ASSOC_NS_USER] = "ns/user",
+ [ASSOC_NS_UTS] = "ns/uts",
+ };
+ collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
+ /* Namespace information is alwasys needed. */
+ false);
+}
+
+static struct nodev *new_nodev(unsigned long minor, const char *filesystem)
+{
+ struct nodev *nodev = xcalloc(1, sizeof(*nodev));
+
+ INIT_LIST_HEAD(&nodev->nodevs);
+ nodev->minor = minor;
+ nodev->filesystem = xstrdup(filesystem);
+
+ return nodev;
+}
+
+static void free_nodev(struct nodev *nodev)
+{
+ free(nodev->filesystem);
+ free(nodev);
+}
+
+static void initialize_nodevs(void)
+{
+ int i;
+
+ for (i = 0; i < NODEV_TABLE_SIZE; i++)
+ INIT_LIST_HEAD(&nodev_table.tables[i]);
+}
+
+static void finalize_nodevs(void)
+{
+ int i;
+
+ for (i = 0; i < NODEV_TABLE_SIZE; i++)
+ list_free(&nodev_table.tables[i], struct nodev, nodevs, free_nodev);
+
+ free(mnt_namespaces);
+}
+
+const char *get_nodev_filesystem(unsigned long minor)
+{
+ struct list_head *n;
+ int slot = minor % NODEV_TABLE_SIZE;
+
+ list_for_each (n, &nodev_table.tables[slot]) {
+ struct nodev *nodev = list_entry(n, struct nodev, nodevs);
+ if (nodev->minor == minor)
+ return nodev->filesystem;
+ }
+ return NULL;
+}
+
+static void add_nodevs(FILE *mnt)
+{
+ /* This can be very long. A line in mountinfo can have more than 3
+ * paths. */
+ char line[PATH_MAX * 3 + 256];
+
+ while (fgets(line, sizeof(line), mnt)) {
+ unsigned long major, minor;
+ char filesystem[256];
+ struct nodev *nodev;
+ int slot;
+
+
+ /* 23 61 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:2 - sysfs sysfs rw,seclabel */
+ if(sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s %*[^-] - %s %*[^\n]",
+ &major, &minor, filesystem) != 3)
+ /* 1600 1458 0:55 / / rw,nodev,relatime - overlay overlay rw,context="s... */
+ if (sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s - %s %*[^\n]",
+ &major, &minor, filesystem) != 3)
+ continue;
+
+ if (major != 0)
+ continue;
+ if (get_nodev_filesystem(minor))
+ continue;
+
+ nodev = new_nodev(minor, filesystem);
+ slot = minor % NODEV_TABLE_SIZE;
+
+ list_add_tail(&nodev->nodevs, &nodev_table.tables[slot]);
+ }
+}
+
+static void initialize_ipc_table(void)
+{
+ for (int i = 0; i < IPC_TABLE_SIZE; i++)
+ INIT_LIST_HEAD(ipc_table.tables + i);
+}
+
+static void free_ipc(struct ipc *ipc)
+{
+ if (ipc->class->free)
+ ipc->class->free(ipc);
+ free(ipc);
+}
+
+static void finalize_ipc_table(void)
+{
+ for (int i = 0; i < IPC_TABLE_SIZE; i++)
+ list_free(&ipc_table.tables[i], struct ipc, ipcs, free_ipc);
+}
+
+struct ipc *get_ipc(struct file *file)
+{
+ int slot;
+ struct list_head *e;
+ const struct ipc_class *ipc_class;
+
+ if (!file->class->get_ipc_class)
+ return NULL;
+
+ ipc_class = file->class->get_ipc_class(file);
+ if (!ipc_class)
+ return NULL;
+
+ slot = ipc_class->get_hash(file) % IPC_TABLE_SIZE;
+ list_for_each (e, &ipc_table.tables[slot]) {
+ struct ipc *ipc = list_entry(e, struct ipc, ipcs);
+ if (ipc->class != ipc_class)
+ continue;
+ if (ipc_class->is_suitable_ipc(ipc, file))
+ return ipc;
+ }
+ return NULL;
+}
+
+void add_ipc(struct ipc *ipc, unsigned int hash)
+{
+ int slot = hash % IPC_TABLE_SIZE;
+ list_add(&ipc->ipcs, &ipc_table.tables[slot]);
+}
+
+static void fill_column(struct proc *proc,
+ struct file *file,
+ struct libscols_line *ln,
+ int column_id,
+ size_t column_index)
+{
+ const struct file_class *class = file->class;
+
+ while (class) {
+ if (class->fill_column
+ && class->fill_column(proc, file, ln,
+ column_id, column_index))
+ break;
+ class = class->super;
+ }
+}
+
+static void convert_file(struct proc *proc,
+ struct file *file,
+ struct libscols_line *ln)
+
+{
+ size_t i;
+
+ for (i = 0; i < ncolumns; i++)
+ fill_column(proc, file, ln, get_column_id(i), i);
+}
+
+static void convert(struct list_head *procs, struct lsfd_control *ctl)
+{
+ struct list_head *p;
+
+ list_for_each (p, procs) {
+ struct proc *proc = list_entry(p, struct proc, procs);
+ struct list_head *f;
+
+ list_for_each (f, &proc->files) {
+ struct file *file = list_entry(f, struct file, files);
+ struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL);
+ struct lsfd_counter **counter = NULL;
+
+ if (!ln)
+ err(EXIT_FAILURE, _("failed to allocate output line"));
+
+ convert_file(proc, file, ln);
+
+ if (!lsfd_filter_apply(ctl->filter, ln)) {
+ scols_table_remove_line(ctl->tb, ln);
+ continue;
+ }
+
+ if (!ctl->counters)
+ continue;
+
+ for (counter = ctl->counters; *counter; counter++)
+ lsfd_counter_accumulate(*counter, ln);
+ }
+ }
+}
+
+static void delete(struct list_head *procs, struct lsfd_control *ctl)
+{
+ struct list_head *p;
+
+ list_for_each (p, procs) {
+ struct proc *proc = list_entry(p, struct proc, procs);
+ tdelete(proc, &proc_tree, proc_tree_compare);
+ }
+ list_free(procs, struct proc, procs, free_proc);
+
+ scols_unref_table(ctl->tb);
+ lsfd_filter_free(ctl->filter);
+ if (ctl->counters) {
+ struct lsfd_counter **counter;
+ for (counter = ctl->counters; *counter; counter++)
+ lsfd_counter_free(*counter);
+ free(ctl->counters);
+ }
+}
+
+static void emit(struct lsfd_control *ctl)
+{
+ scols_print_table(ctl->tb);
+}
+
+
+static void initialize_class(const struct file_class *class)
+{
+ if (class->initialize_class)
+ class->initialize_class();
+}
+
+static void initialize_classes(void)
+{
+ initialize_class(&file_class);
+ initialize_class(&cdev_class);
+ initialize_class(&bdev_class);
+ initialize_class(&sock_class);
+ initialize_class(&unkn_class);
+}
+
+static void finalize_class(const struct file_class *class)
+{
+ if (class->finalize_class)
+ class->finalize_class();
+}
+
+static void finalize_classes(void)
+{
+ finalize_class(&file_class);
+ finalize_class(&cdev_class);
+ finalize_class(&bdev_class);
+ finalize_class(&sock_class);
+ finalize_class(&unkn_class);
+}
+
+static struct devdrv *new_devdrv(unsigned long major, const char *name)
+{
+ struct devdrv *devdrv = xcalloc(1, sizeof(*devdrv));
+
+ INIT_LIST_HEAD(&devdrv->devdrvs);
+
+ devdrv->major = major;
+ devdrv->name = xstrdup(name);
+
+ return devdrv;
+}
+
+static void free_devdrv(struct devdrv *devdrv)
+{
+ free(devdrv->name);
+ free(devdrv);
+}
+
+#define READ_DEVICES_LINE_LEN 256
+static struct devdrv *read_devdrv(const char *line)
+{
+ unsigned long major;
+ char name[READ_DEVICES_LINE_LEN];
+
+ if (sscanf(line, "%lu %s", &major, name) != 2)
+ return NULL;
+
+ return new_devdrv(major, name);
+}
+
+static void read_devices(struct list_head *chrdrvs_list,
+ struct list_head *blkdrvs_list, FILE *devices_fp)
+{
+ char line[READ_DEVICES_LINE_LEN];
+
+ /* Skip to the line "Character devices:". */
+ while (fgets(line, sizeof(line), devices_fp)) {
+ if (line[0] == 'C')
+ break;
+ continue;
+ }
+
+ while (fgets(line, sizeof(line), devices_fp)) {
+ /* Find the blank line before "Block devices:" line. */
+ if (line[0] == '\n')
+ break;
+
+ /* Read the character device drivers */
+ struct devdrv *devdrv = read_devdrv(line);
+ if (devdrv)
+ list_add_tail(&devdrv->devdrvs, chrdrvs_list);
+ }
+
+ /* Skip to the line "Block devices:". */
+ while (fgets(line, sizeof(line), devices_fp)) {
+ if (line[0] == 'B')
+ break;
+ continue;
+ }
+
+ /* Read the block device drivers */
+ while (fgets(line, sizeof(line), devices_fp)) {
+ struct devdrv *devdrv = read_devdrv(line);
+ if (devdrv)
+ list_add_tail(&devdrv->devdrvs, blkdrvs_list);
+ }
+}
+
+static void initialize_devdrvs(void)
+{
+ FILE *devices_fp;
+
+ INIT_LIST_HEAD(&chrdrvs);
+ INIT_LIST_HEAD(&blkdrvs);
+
+ devices_fp = fopen("/proc/devices", "r");
+ if (devices_fp) {
+ read_devices(&chrdrvs, &blkdrvs, devices_fp);
+ fclose(devices_fp);
+ }
+}
+
+static void finalize_devdrvs(void)
+{
+ list_free(&blkdrvs, struct devdrv, devdrvs, free_devdrv);
+ list_free(&chrdrvs, struct devdrv, devdrvs, free_devdrv);
+}
+
+static const char *get_devdrv(struct list_head *devdrvs_list, unsigned long major)
+{
+ struct list_head *c;
+ list_for_each(c, devdrvs_list) {
+ struct devdrv *devdrv = list_entry(c, struct devdrv, devdrvs);
+ if (devdrv->major == major)
+ return devdrv->name;
+ }
+ return NULL;
+}
+
+const char *get_chrdrv(unsigned long major)
+{
+ return get_devdrv(&chrdrvs, major);
+}
+
+const char *get_blkdrv(unsigned long major)
+{
+ return get_devdrv(&blkdrvs, major);
+}
+
+struct name_manager *new_name_manager(void)
+{
+ struct name_manager *nm = xcalloc(1, sizeof(struct name_manager));
+
+ nm->cache = new_idcache();
+ if (!nm->cache)
+ err(EXIT_FAILURE, _("failed to allocate an idcache"));
+
+ nm->next_id = 1; /* 0 is never issued as id. */
+ return nm;
+}
+
+void free_name_manager(struct name_manager *nm)
+{
+ free_idcache(nm->cache);
+ free(nm);
+}
+
+const char *get_name(struct name_manager *nm, unsigned long id)
+{
+ struct identry *e;
+
+ e = get_id(nm->cache, id);
+
+ return e? e->name: NULL;
+}
+
+unsigned long add_name(struct name_manager *nm, const char *name)
+{
+ struct identry *e = NULL, *tmp;
+
+ for (tmp = nm->cache->ent; tmp; tmp = tmp->next) {
+ if (strcmp(tmp->name, name) == 0) {
+ e = tmp;
+ break;
+ }
+ }
+
+ if (e)
+ return e->id;
+
+ e = xmalloc(sizeof(struct identry));
+ e->name = xstrdup(name);
+ e->id = nm->next_id++;
+ e->next = nm->cache->ent;
+ nm->cache->ent = e;
+
+ return e->id;
+}
+
+static void read_process(struct lsfd_control *ctl, struct path_cxt *pc,
+ pid_t pid, struct proc *leader)
+{
+ char buf[BUFSIZ];
+ struct proc *proc;
+
+ if (procfs_process_init_path(pc, pid) != 0)
+ return;
+
+ proc = new_process(pid, leader);
+ proc->command = procfs_process_get_cmdname(pc, buf, sizeof(buf)) > 0 ?
+ xstrdup(buf) : xstrdup(_("(unknown)"));
+ procfs_process_get_uid(pc, &proc->uid);
+
+ if (procfs_process_get_stat(pc, buf, sizeof(buf)) > 0) {
+ char *p;
+ unsigned int flags;
+ char *pat = NULL;
+
+ /* See proc(5) about the column in the line. */
+ xstrappend(&pat, "%*d (");
+ for (p = proc->command; *p != '\0'; p++) {
+ if (*p == '%')
+ xstrappend(&pat, "%%");
+ else
+ xstrputc(&pat, *p);
+ }
+ xstrappend(&pat, ") %*c %*d %*d %*d %*d %*d %u %*[^\n]");
+ if (sscanf(buf, pat, &flags) == 1)
+ proc->kthread = !!(flags & PF_KTHREAD);
+ free(pat);
+ }
+
+ collect_execve_file(pc, proc, ctl->sockets_only);
+
+ if (proc->pid == proc->leader->pid
+ || kcmp(proc->leader->pid, proc->pid, KCMP_FS, 0, 0) != 0)
+ collect_fs_files(pc, proc, ctl->sockets_only);
+
+ if (proc->ns_mnt == 0 || !has_mnt_ns(proc->ns_mnt)) {
+ FILE *mnt = ul_path_fopen(pc, "r", "mountinfo");
+ if (mnt) {
+ add_nodevs(mnt);
+ if (proc->ns_mnt)
+ add_mnt_ns(proc->ns_mnt);
+ fclose(mnt);
+ }
+ }
+
+ collect_namespace_files(pc, proc);
+
+ /* If kcmp is not available,
+ * there is no way to no whether threads share resources.
+ * In such cases, we must pay the costs: call collect_mem_files()
+ * and collect_fd_files().
+ */
+ if ((!ctl->sockets_only)
+ && (proc->pid == proc->leader->pid
+ || kcmp(proc->leader->pid, proc->pid, KCMP_VM, 0, 0) != 0))
+ collect_mem_files(pc, proc);
+
+ if (proc->pid == proc->leader->pid
+ || kcmp(proc->leader->pid, proc->pid, KCMP_FILES, 0, 0) != 0)
+ collect_fd_files(pc, proc, ctl->sockets_only);
+
+ list_add_tail(&proc->procs, &ctl->procs);
+ if (tsearch(proc, &proc_tree, proc_tree_compare) == NULL)
+ errx(EXIT_FAILURE, _("failed to allocate memory"));
+
+ /* The tasks collecting overwrites @pc by /proc/<task-pid>/. Keep it as
+ * the last path based operation in read_process()
+ */
+ if (ctl->threads && leader == NULL) {
+ DIR *sub = NULL;
+ pid_t tid = 0;
+
+ while (procfs_process_next_tid(pc, &sub, &tid) == 0) {
+ if (tid == pid)
+ continue;
+ read_process(ctl, pc, tid, proc);
+ }
+ }
+
+ /* Let's be careful with number of open files */
+ ul_path_close_dirfd(pc);
+}
+
+static void parse_pids(const char *str, pid_t **pids, int *count)
+{
+ long v;
+ char *next = NULL;
+
+ 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);
+
+ (*count)++;
+ *pids = xrealloc(*pids, (*count) * sizeof(**pids));
+ (*pids)[*count - 1] = (pid_t)v;
+
+ while (next && *next != '\0'
+ && (isspace((unsigned char)*next) || *next == ','))
+ next++;
+ if (*next != '\0')
+ parse_pids(next, pids, count);
+}
+
+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;
+ struct path_cxt *pc = NULL;
+
+ pc = ul_new_path(NULL);
+ if (!pc)
+ err(EXIT_FAILURE, _("failed to alloc procfs handler"));
+
+ dir = opendir(_PATH_PROC);
+ if (!dir)
+ err(EXIT_FAILURE, _("failed to open /proc"));
+
+ while ((d = readdir(dir))) {
+ pid_t pid;
+
+ if (procfs_dirent_get_pid(d, &pid) != 0)
+ continue;
+ if (n_pids == 0 || member_pids(pid, pids, n_pids))
+ read_process(ctl, pc, pid, 0);
+ }
+
+ closedir(dir);
+ ul_unref_path(pc);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -l, --threads list in threads level\n"), out);
+ fputs(_(" -J, --json use JSON output format\n"), out);
+ fputs(_(" -n, --noheadings don't print headings\n"), out);
+ fputs(_(" -o, --output <list> output columns\n"), out);
+ fputs(_(" -r, --raw use raw output format\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(_(" -i[4|6], --inet[=4|6] list only IPv4 and/or IPv6 sockets\n"), out);
+ fputs(_(" -Q, --filter <expr> apply display filter\n"), out);
+ fputs(_(" --debug-filter dump the internal data structure of filter and exit\n"), out);
+ fputs(_(" -C, --counter <name>:<expr>\n"
+ " define custom counter for --summary output\n"), out);
+ fputs(_(" --dump-counters dump counter definitions\n"), out);
+ fputs(_(" --summary[=<when>] print summary information (only, append, or never)\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(30));
+
+ fprintf(out, USAGE_COLUMNS);
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %16s %-10s%s\n", infos[i].name,
+ infos[i].json_type == SCOLS_JSON_STRING? "<string>":
+ infos[i].json_type == SCOLS_JSON_ARRAY_STRING? "<string>":
+ infos[i].json_type == SCOLS_JSON_NUMBER? "<number>":
+ "<boolean>",
+ _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("lsfd(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static void append_filter_expr(char **a, const char *b, bool and)
+{
+ if (*a == NULL) {
+ *a = xstrdup(b);
+ return;
+ }
+
+ char *tmp = *a;
+ *a = NULL;
+
+ xstrappend(a, "(");
+ xstrappend(a, tmp);
+ xstrappend(a, ")");
+ if (and)
+ xstrappend(a, "and(");
+ else
+ xstrappend(a, "or(");
+ xstrappend(a, b);
+ xstrappend(a, ")");
+
+ free(tmp);
+}
+
+static struct lsfd_filter *new_filter(const char *expr, bool debug, const char *err_prefix, struct lsfd_control *ctl)
+{
+ struct lsfd_filter *filter;
+ const char *errmsg;
+
+ filter = lsfd_filter_new(expr, ctl->tb,
+ LSFD_N_COLS,
+ column_name_to_id_cb,
+ add_column_by_id_cb, ctl);
+ errmsg = lsfd_filter_get_errmsg(filter);
+ if (errmsg)
+ errx(EXIT_FAILURE, "%s%s", err_prefix, errmsg);
+ if (debug) {
+ lsfd_filter_dump(filter, stdout);
+ exit(EXIT_SUCCESS);
+ }
+
+ return filter;
+}
+
+static struct counter_spec *new_counter_spec(const char *spec_str)
+{
+ char *sep;
+ struct counter_spec *spec;
+
+ if (spec_str[0] == '\0')
+ errx(EXIT_FAILURE,
+ _("too short counter specification: -C/--counter %s"),
+ spec_str);
+ if (spec_str[0] == ':')
+ errx(EXIT_FAILURE,
+ _("no name for counter: -C/--counter %s"),
+ spec_str);
+
+ sep = strchr(spec_str, ':');
+ if (sep == NULL)
+ errx(EXIT_FAILURE,
+ _("no name for counter: -C/--counter %s"),
+ spec_str);
+ if (sep[1] == '\0')
+ errx(EXIT_FAILURE,
+ _("empty counter expression given: -C/--counter %s"),
+ spec_str);
+
+ /* Split the spec_str in to name and expr. */
+ *sep = '\0';
+
+ if (strchr(spec_str, '{'))
+ errx(EXIT_FAILURE,
+ _("don't use `{' in the name of a counter: %s"),
+ spec_str);
+
+ spec = xmalloc(sizeof(struct counter_spec));
+ INIT_LIST_HEAD(&spec->specs);
+ spec->name = spec_str;
+ spec->expr = sep + 1;
+
+ return spec;
+}
+
+static void free_counter_spec(struct counter_spec *counter_spec)
+{
+ free(counter_spec);
+}
+
+static struct lsfd_counter *new_counter(const struct counter_spec *spec, struct lsfd_control *ctl)
+{
+ struct lsfd_filter *filter;
+
+ filter = new_filter(spec->expr, false,
+ _("failed in making filter for a counter: "),
+ ctl);
+ return lsfd_counter_new(spec->name, filter);
+}
+
+static struct lsfd_counter **new_counters(struct list_head *specs, struct lsfd_control *ctl)
+{
+ struct lsfd_counter **counters;
+ size_t len = list_count_entries(specs);
+ size_t i = 0;
+ struct list_head *s;
+
+ counters = xcalloc(len + 1, sizeof(struct lsfd_counter *));
+ list_for_each(s, specs) {
+ struct counter_spec *spec = list_entry(s, struct counter_spec, specs);
+ counters[i++] = new_counter(spec, ctl);
+ }
+ assert(counters[len] == NULL);
+
+ return counters;
+}
+
+static struct lsfd_counter **new_default_counters(struct lsfd_control *ctl)
+{
+ struct lsfd_counter **counters;
+ size_t len = ARRAY_SIZE(default_counter_specs);
+ size_t i;
+
+ counters = xcalloc(len + 1, sizeof(struct lsfd_counter *));
+ for (i = 0; i < len; i++) {
+ const struct counter_spec *spec = default_counter_specs + i;
+ counters[i] = new_counter(spec, ctl);
+ }
+ assert(counters[len] == NULL);
+
+ return counters;
+}
+
+static void dump_default_counter_specs(void)
+{
+ size_t len = ARRAY_SIZE(default_counter_specs);
+ size_t i;
+
+ puts("default counter specs:");
+ for (i = 0; i < len; i++) {
+ const struct counter_spec *spec = default_counter_specs + i;
+ printf("\t%s:%s\n", spec->name, spec->expr);
+ }
+}
+
+static void dump_counter_specs(struct list_head *specs)
+{
+ struct list_head *s;
+
+ puts("custom counter specs:");
+ list_for_each(s, specs) {
+ struct counter_spec *spec = list_entry(s, struct counter_spec, specs);
+ printf("\t%s:%s\n", spec->name, spec->expr);
+ }
+}
+
+static struct libscols_table *new_summary_table(struct lsfd_control *ctl)
+{
+ struct libscols_table *tb = scols_new_table();
+
+ struct libscols_column *name_cl, *value_cl;
+
+ if (!tb)
+ err(EXIT_FAILURE, _("failed to allocate summary table"));
+
+ scols_table_enable_noheadings(tb, ctl->noheadings);
+ scols_table_enable_raw(tb, ctl->raw);
+ scols_table_enable_json(tb, ctl->json);
+
+ if(ctl->json)
+ scols_table_set_name(tb, "lsfd-summary");
+
+
+ value_cl = scols_table_new_column(tb, _("VALUE"), 0, SCOLS_FL_RIGHT);
+ if (!value_cl)
+ err(EXIT_FAILURE, _("failed to allocate summary column"));
+ if (ctl->json)
+ scols_column_set_json_type(value_cl, SCOLS_JSON_NUMBER);
+
+ name_cl = scols_table_new_column(tb, _("COUNTER"), 0, 0);
+ if (!name_cl)
+ err(EXIT_FAILURE, _("failed to allocate summary column"));
+ if (ctl->json)
+ scols_column_set_json_type(name_cl, SCOLS_JSON_STRING);
+
+ return tb;
+}
+
+static void fill_summary_line(struct libscols_line *ln, struct lsfd_counter *counter)
+{
+ char *str = NULL;
+
+ xasprintf(&str, "%llu", (unsigned long long)lsfd_counter_value(counter));
+ if (!str)
+ err(EXIT_FAILURE, _("failed to add summary data"));
+ if (scols_line_refer_data(ln, 0, str))
+ err(EXIT_FAILURE, _("failed to add summary data"));
+
+ if (scols_line_set_data(ln, 1, lsfd_counter_name(counter)))
+ err(EXIT_FAILURE, _("failed to add summary data"));
+}
+
+static void emit_summary(struct lsfd_control *ctl, struct lsfd_counter **counter)
+{
+ struct libscols_table *tb = new_summary_table(ctl);
+
+ for (; *counter; counter++) {
+ struct libscols_line *ln = scols_table_new_line(tb, NULL);
+ fill_summary_line(ln, *counter);
+ }
+ scols_print_table(tb);
+
+ scols_unref_table(tb);
+}
+
+static void attach_xinfos(struct list_head *procs)
+{
+ struct list_head *p;
+
+ list_for_each (p, procs) {
+ struct proc *proc = list_entry(p, struct proc, procs);
+ struct list_head *f;
+
+ list_for_each (f, &proc->files) {
+ struct file *file = list_entry(f, struct file, files);
+ if (file->class->attach_xinfo)
+ file->class->attach_xinfo(file);
+ }
+ }
+}
+
+/* Filter expressions for implementing -i option.
+ *
+ * To list up the protocol names, use the following command line
+ *
+ * cd linux/net;
+ * find . -type f -exec grep -A 1 --color=auto -nH --null -e 'struct proto .*{' \{\} +
+ *
+ */
+#define INET_SUBEXP_BEGIN "(SOCK.PROTONAME =~ \"^("
+#define INET4_REG "TCP|UDP|RAW|PING|UDP-Lite|SCTP|DCCP|L2TP/IP|SMC"
+#define INET6_REG "TCPv6|UDPv6|RAWv6|PINGv6|UDPLITEv6|SCTPv6|DCCPv6|L2TP/IPv6|SMC6"
+#define INET_SUBEXP_END ")$\")"
+
+static const char *inet4_subexpr = INET_SUBEXP_BEGIN
+ INET4_REG
+ INET_SUBEXP_END;
+static const char *inet6_subexpr = INET_SUBEXP_BEGIN
+ INET6_REG
+ INET_SUBEXP_END;
+static const char *inet46_subexpr = INET_SUBEXP_BEGIN
+ INET4_REG "|" INET6_REG
+ INET_SUBEXP_END;
+
+int main(int argc, char *argv[])
+{
+ int c;
+ size_t i;
+ char *outarg = NULL;
+ char *filter_expr = NULL;
+ bool debug_filter = false;
+ bool dump_counters = false;
+ pid_t *pids = NULL;
+ int n_pids = 0;
+ struct list_head counter_specs;
+
+ struct lsfd_control ctl = {
+ .show_main = 1
+ };
+
+ INIT_LIST_HEAD(&counter_specs);
+
+ enum {
+ OPT_DEBUG_FILTER = CHAR_MAX + 1,
+ OPT_SUMMARY,
+ OPT_DUMP_COUNTERS,
+ };
+ static const struct option longopts[] = {
+ { "noheadings", no_argument, NULL, 'n' },
+ { "output", required_argument, NULL, 'o' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { "json", no_argument, NULL, 'J' },
+ { "raw", no_argument, NULL, 'r' },
+ { "threads", no_argument, NULL, 'l' },
+ { "notruncate", no_argument, NULL, 'u' },
+ { "pid", required_argument, NULL, 'p' },
+ { "inet", optional_argument, NULL, 'i' },
+ { "filter", required_argument, NULL, 'Q' },
+ { "debug-filter",no_argument, NULL, OPT_DEBUG_FILTER },
+ { "summary", optional_argument, NULL, OPT_SUMMARY },
+ { "counter", required_argument, NULL, 'C' },
+ { "dump-counters",no_argument, NULL, OPT_DUMP_COUNTERS },
+ { NULL, 0, NULL, 0 },
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:i::C:s", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'n':
+ ctl.noheadings = 1;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case 'J':
+ ctl.json = 1;
+ break;
+ case 'r':
+ ctl.raw = 1;
+ break;
+ case 'l':
+ ctl.threads = 1;
+ break;
+ case 'u':
+ ctl.notrunc = 1;
+ break;
+ case 'p':
+ parse_pids(optarg, &pids, &n_pids);
+ break;
+ case 'i': {
+ const char *subexpr = NULL;
+
+ ctl.sockets_only = 1;
+ if (optarg == NULL)
+ subexpr = inet46_subexpr;
+ else if (strcmp(optarg, "4") == 0)
+ subexpr = inet4_subexpr;
+ else if (strcmp(optarg, "6") == 0)
+ subexpr = inet6_subexpr;
+ else
+ errx(EXIT_FAILURE,
+ _("unknown -i/--inet argument: %s"),
+ optarg);
+
+ append_filter_expr(&filter_expr, subexpr, true);
+ break;
+ }
+ case 'Q':
+ append_filter_expr(&filter_expr, optarg, true);
+ break;
+ case 'C': {
+ struct counter_spec *c = new_counter_spec(optarg);
+ list_add_tail(&c->specs, &counter_specs);
+ break;
+ }
+ case OPT_DEBUG_FILTER:
+ debug_filter = true;
+ break;
+ case OPT_SUMMARY:
+ if (optarg) {
+ if (strcmp(optarg, "never") == 0)
+ ctl.show_summary = 0, ctl.show_main = 1;
+ else if (strcmp(optarg, "only") == 0)
+ ctl.show_summary = 1, ctl.show_main = 0;
+ else if (strcmp(optarg, "append") == 0)
+ ctl.show_summary = 1, ctl.show_main = 1;
+ else
+ errx(EXIT_FAILURE, _("unsupported --summary argument"));
+ } else
+ ctl.show_summary = 1, ctl.show_main = 0;
+ break;
+ case OPT_DUMP_COUNTERS:
+ dump_counters = true;
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ if (argv[optind])
+ errtryhelp(EXIT_FAILURE);
+
+#define INITIALIZE_COLUMNS(COLUMN_SPEC) \
+ for (i = 0; i < ARRAY_SIZE(COLUMN_SPEC); i++) \
+ columns[ncolumns++] = COLUMN_SPEC[i]
+ if (!ncolumns) {
+ if (ctl.threads)
+ INITIALIZE_COLUMNS(default_threads_columns);
+ else
+ INITIALIZE_COLUMNS(default_columns);
+ }
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ scols_init_debug(0);
+
+ INIT_LIST_HEAD(&ctl.procs);
+
+ /* inilialize scols table */
+ ctl.tb = scols_new_table();
+ if (!ctl.tb)
+ err(EXIT_FAILURE, _("failed to allocate output table"));
+
+ scols_table_enable_noheadings(ctl.tb, ctl.noheadings);
+ scols_table_enable_raw(ctl.tb, ctl.raw);
+ scols_table_enable_json(ctl.tb, ctl.json);
+ if (ctl.json)
+ scols_table_set_name(ctl.tb, "lsfd");
+
+ /* create output columns */
+ for (i = 0; i < ncolumns; i++) {
+ const struct colinfo *col = get_column_info(i);
+ struct libscols_column *cl = add_column(ctl.tb, col);
+
+ if (!cl)
+ err(EXIT_FAILURE, _("failed to allocate output column"));
+
+ if (ctl.notrunc) {
+ int flags = scols_column_get_flags(cl);
+ flags &= ~SCOLS_FL_TRUNC;
+ scols_column_set_flags(cl, flags);
+ }
+ }
+
+ /* make fitler */
+ if (filter_expr) {
+ ctl.filter = new_filter(filter_expr, debug_filter, "", &ctl);
+ free(filter_expr);
+ }
+
+ if (dump_counters) {
+ if (list_empty(&counter_specs))
+ dump_default_counter_specs();
+ else
+ dump_counter_specs(&counter_specs);
+ return 0;
+ }
+
+ /* make counters */
+ if (ctl.show_summary) {
+ if (list_empty(&counter_specs))
+ ctl.counters = new_default_counters(&ctl);
+ else {
+ ctl.counters = new_counters(&counter_specs, &ctl);
+ list_free(&counter_specs, struct counter_spec, specs,
+ free_counter_spec);
+ }
+ }
+
+ if (n_pids > 0)
+ sort_pids(pids, n_pids);
+
+ /* collect data */
+ initialize_nodevs();
+ initialize_classes();
+ initialize_devdrvs();
+ initialize_ipc_table();
+
+ collect_processes(&ctl, pids, n_pids);
+ free(pids);
+
+ attach_xinfos(&ctl.procs);
+
+ convert(&ctl.procs, &ctl);
+
+ /* print */
+ if (ctl.show_main)
+ emit(&ctl);
+
+ if (ctl.show_summary && ctl.counters)
+ emit_summary(&ctl, ctl.counters);
+
+ /* cleanup */
+ delete(&ctl.procs, &ctl);
+
+ finalize_ipc_table();
+ finalize_devdrvs();
+ finalize_classes();
+ finalize_nodevs();
+
+ return 0;
+}