2 * lsfd(1) - list file descriptors
4 * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
6 * Karel Zak <kzak@redhat.com>
8 * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu>
9 * It supports multiple OSes. lsfd specializes to Linux.
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it would be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include <sys/types.h>
34 #include <linux/sched.h>
35 #include <sys/syscall.h>
36 #include <linux/kcmp.h>
37 static int kcmp(pid_t pid1
, pid_t pid2
, int type
,
38 unsigned long idx1
, unsigned long idx2
)
40 return syscall(SYS_kcmp
, pid1
, pid2
, type
, idx1
, idx2
);
44 * Defined in linux/include/linux/sched.h private header file. */
45 #define PF_KTHREAD 0x00200000 /* I am a kernel thread */
51 #include "closestream.h"
54 #include "fileutils.h"
56 #include "pathnames.h"
58 #include "libsmartcols.h"
61 #include "lsfd-filter.h"
62 #include "lsfd-counter.h"
65 * /proc/$pid/mountinfo entries
68 struct list_head nodevs
;
74 #define NODEV_TABLE_SIZE 97
75 struct list_head tables
[NODEV_TABLE_SIZE
];
79 struct idcache
*cache
;
80 unsigned long next_id
;
84 * Column related stuffs
96 /* columns descriptions */
97 static struct colinfo infos
[] = {
98 [COL_ASSOC
] = { "ASSOC", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
99 N_("association between file and process") },
100 [COL_CHRDRV
] = { "CHRDRV", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
101 N_("character device driver name resolved by /proc/devices") },
102 [COL_COMMAND
] = { "COMMAND",0.3, SCOLS_FL_TRUNC
, SCOLS_JSON_STRING
,
103 N_("command of the process opening the file") },
104 [COL_DELETED
] = { "DELETED", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_BOOLEAN
,
105 N_("reachability from the file system") },
106 [COL_DEV
] = { "DEV", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
107 N_("ID of device containing file") },
108 [COL_DEVTYPE
] = { "DEVTYPE", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
109 N_("device type (blk, char, or nodev)") },
110 [COL_FLAGS
] = { "FLAGS", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
111 N_("flags specified when opening the file") },
112 [COL_FD
] = { "FD", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
113 N_("file descriptor for the file") },
114 [COL_FUID
] = { "FUID", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
115 N_("user ID number of the file's owner") },
116 [COL_INODE
] = { "INODE", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
117 N_("inode number") },
118 [COL_KTHREAD
] = { "KTHREAD", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_BOOLEAN
,
119 N_("opened by a kernel thread") },
120 [COL_MAJMIN
] = { "MAJ:MIN", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
121 N_("device ID for special, or ID of device containing file") },
122 [COL_MAPLEN
] = { "MAPLEN", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
123 N_("length of file mapping (in page)") },
124 [COL_MISCDEV
] = { "MISCDEV", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
125 N_("misc character device name resolved by /proc/misc") },
126 [COL_MNT_ID
] = { "MNTID", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
128 [COL_MODE
] = { "MODE", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
129 N_("access mode (rwx)") },
130 [COL_NAME
] = { "NAME", 0.4, SCOLS_FL_TRUNC
, SCOLS_JSON_STRING
,
131 N_("name of the file") },
132 [COL_NLINK
] = { "NLINK", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
134 [COL_OWNER
] = { "OWNER", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
135 N_("owner of the file") },
136 [COL_PID
] = { "PID", 5, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
137 N_("PID of the process opening the file") },
138 [COL_PARTITION
]={ "PARTITION",0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
139 N_("block device name resolved by /proc/partition") },
140 [COL_POS
] = { "POS", 5, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
141 N_("file position") },
142 [COL_PROTONAME
]={ "PROTONAME",0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
143 N_("protocol name") },
144 [COL_RDEV
] = { "RDEV", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
145 N_("device ID (if special file)") },
146 [COL_SIZE
] = { "SIZE", 4, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
148 [COL_SOURCE
] = { "SOURCE", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
149 N_("file system, partition, or device containing file") },
150 [COL_TID
] = { "TID", 5, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
151 N_("thread ID of the process opening the file") },
152 [COL_TYPE
] = { "TYPE", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
154 [COL_UID
] = { "UID", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_NUMBER
,
155 N_("user ID number of the process") },
156 [COL_USER
] = { "USER", 0, SCOLS_FL_RIGHT
, SCOLS_JSON_STRING
,
157 N_("user of the process") },
160 static const int default_columns
[] = {
173 static const int default_threads_columns
[] = {
187 static int columns
[ARRAY_SIZE(infos
) * 2] = {-1};
188 static size_t ncolumns
;
190 static ino_t
*mnt_namespaces
;
191 static size_t nspaces
;
193 struct counter_spec
{
194 struct list_head specs
;
199 static struct counter_spec default_counter_specs
[] = {
201 .name
= N_("processes"),
202 .expr
= "ASSOC == 'cwd'",
205 .name
= N_("root owned processes"),
206 .expr
= "(ASSOC == 'cwd') && (UID == 0)",
209 .name
= N_("kernel threads"),
210 .expr
= "(ASSOC == 'cwd') && KTHREAD",
213 .name
= N_("open files"),
217 .name
= N_("RO open files"),
218 .expr
= "(FD >= 0) and (MODE == 'r--')",
221 .name
= N_("WO open files"),
222 .expr
= "(FD >= 0) and (MODE == '-w-')",
225 .name
= N_("shared mappings"),
226 .expr
= "ASSOC == 'shm'",
229 .name
= N_("RO shared mappings"),
230 .expr
= "(ASSOC == 'shm') and (MODE == 'r--')",
233 .name
= N_("WO shared mappings"),
234 .expr
= "(ASSOC == 'shm') and (MODE == '-w-')",
237 .name
= N_("regular files"),
238 .expr
= "(FD >= 0) && (TYPE == 'REG')",
241 .name
= N_("directories"),
242 .expr
= "(FD >= 0) && (TYPE == 'DIR')",
245 .name
= N_("sockets"),
246 .expr
= "(FD >= 0) && (TYPE == 'SOCK')",
249 .name
= N_("fifos/pipes"),
250 .expr
= "(FD >= 0) && (TYPE == 'FIFO')",
253 .name
= N_("character devices"),
254 .expr
= "(FD >= 0) && (TYPE == 'CHR')",
257 .name
= N_("block devices"),
258 .expr
= "(FD >= 0) && (TYPE == 'BLK')",
261 .name
= N_("unknown types"),
262 .expr
= "(FD >= 0) && (TYPE == 'UNKN')",
267 SUMMARY_EMIT
= 1 << 0,
268 SUMMARY_ONLY
= 1 << 1,
271 struct lsfd_control
{
272 struct libscols_table
*tb
; /* output */
273 struct list_head procs
; /* list of all processes */
275 unsigned int noheadings
: 1,
282 struct lsfd_filter
*filter
;
283 struct lsfd_counter
**counters
; /* NULL terminated array. */
286 static void xstrappend(char **a
, const char *b
);
287 static void xstrputc(char **a
, char c
);
289 static int column_name_to_id(const char *name
, size_t namesz
)
293 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++) {
294 const char *cn
= infos
[i
].name
;
296 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
299 warnx(_("unknown column: %s"), name
);
301 return LSFD_FILTER_UNKNOWN_COL_ID
;
304 static int column_name_to_id_cb(const char *name
, void *data
__attribute__((__unused__
)))
306 return column_name_to_id(name
, strlen(name
));
309 static int get_column_id(int num
)
312 assert((size_t) num
< ncolumns
);
313 assert(columns
[num
] < (int) ARRAY_SIZE(infos
));
318 static const struct colinfo
*get_column_info(int num
)
320 return &infos
[ get_column_id(num
) ];
323 static struct libscols_column
*add_column(struct libscols_table
*tb
, const struct colinfo
*col
)
325 struct libscols_column
*cl
;
326 int flags
= col
->flags
;
328 cl
= scols_table_new_column(tb
, col
->name
, col
->whint
, flags
);
330 scols_column_set_json_type(cl
, col
->json_type
);
335 static struct libscols_column
*add_column_by_id_cb(struct libscols_table
*tb
, int colid
, void *data
)
337 struct libscols_column
*cl
;
339 if (ncolumns
>= ARRAY_SIZE(columns
))
340 errx(EXIT_FAILURE
, _("too many columns are added via filter expression"));
342 assert(colid
< LSFD_N_COLS
);
344 cl
= add_column(tb
, infos
+ colid
);
346 err(EXIT_FAILURE
, _("failed to allocate output column"));
347 columns
[ncolumns
++] = colid
;
349 if (colid
== COL_TID
) {
350 struct lsfd_control
*ctl
= data
;
357 static int has_mnt_ns(ino_t id
)
361 for (i
= 0; i
< nspaces
; i
++) {
362 if (mnt_namespaces
[i
] == id
)
368 static void add_mnt_ns(ino_t id
)
373 nmax
= (nspaces
+ 16) / 16 * 16;
374 if (nmax
<= nspaces
+ 1) {
376 mnt_namespaces
= xrealloc(mnt_namespaces
,
377 sizeof(ino_t
) * nmax
);
379 mnt_namespaces
[nspaces
++] = id
;
382 static const struct file_class
*stat2class(struct stat
*sb
)
386 switch (sb
->st_mode
& S_IFMT
) {
406 static struct file
*new_file(struct proc
*proc
, const struct file_class
*class)
410 file
= xcalloc(1, class->size
);
413 INIT_LIST_HEAD(&file
->files
);
414 list_add_tail(&file
->files
, &proc
->files
);
419 static struct file
*copy_file(struct file
*old
)
421 struct file
*file
= xcalloc(1, old
->class->size
);
423 INIT_LIST_HEAD(&file
->files
);
424 file
->proc
= old
->proc
;
425 list_add_tail(&file
->files
, &old
->proc
->files
);
427 file
->class = old
->class;
428 file
->association
= old
->association
;
429 file
->name
= xstrdup(old
->name
);
430 file
->stat
= old
->stat
;
435 static void file_set_path(struct file
*file
, struct stat
*sb
, const char *name
, int association
)
437 const struct file_class
*class = stat2class(sb
);
442 file
->association
= association
;
443 file
->name
= xstrdup(name
);
447 static void file_init_content(struct file
*file
)
449 if (file
->class && file
->class->initialize_content
)
450 file
->class->initialize_content(file
);
453 static void free_file(struct file
*file
)
455 const struct file_class
*class = file
->class;
458 if (class->free_content
)
459 class->free_content(file
);
460 class = class->super
;
466 static struct proc
*new_process(pid_t pid
, struct proc
*leader
)
468 struct proc
*proc
= xcalloc(1, sizeof(*proc
));
471 proc
->leader
= leader
? leader
: proc
;
472 proc
->command
= NULL
;
474 INIT_LIST_HEAD(&proc
->files
);
475 INIT_LIST_HEAD(&proc
->procs
);
481 static void free_proc(struct proc
*proc
)
483 list_free(&proc
->files
, struct file
, files
, free_file
);
489 static void read_fdinfo(struct file
*file
, FILE *fdinfo
)
493 while (fgets(buf
, sizeof(buf
), fdinfo
)) {
494 const struct file_class
*class;
495 char *val
= strchr(buf
, ':');
499 *val
++ = '\0'; /* terminate key */
501 val
= (char *) skip_space(val
);
502 rtrim_whitespace((unsigned char *) val
);
506 if (class->handle_fdinfo
507 && class->handle_fdinfo(file
, buf
, val
))
509 class = class->super
;
514 static struct file
*collect_file_symlink(struct path_cxt
*pc
,
519 char sym
[PATH_MAX
] = { '\0' };
521 struct file
*f
, *prev
;
523 if (ul_path_readlink(pc
, sym
, sizeof(sym
), name
) < 0)
526 /* The /proc/#/{fd,ns} often contains the same file (e.g. /dev/tty)
527 * more than once. Let's try to reuse the previous file if the real
528 * path is the same to save stat() call.
530 prev
= list_last_entry(&proc
->files
, struct file
, files
);
531 if (prev
&& prev
->name
&& strcmp(prev
->name
, sym
) == 0) {
533 f
->association
= assoc
;
535 if (ul_path_stat(pc
, &sb
, 0, name
) < 0)
538 f
= new_file(proc
, stat2class(&sb
));
539 file_set_path(f
, &sb
, sym
, assoc
);
542 file_init_content(f
);
544 if (is_association(f
, NS_MNT
))
545 proc
->ns_mnt
= sb
.st_ino
;
547 else if (assoc
>= 0) {
548 /* file-descriptor based association */
551 if (ul_path_stat(pc
, &sb
, AT_SYMLINK_NOFOLLOW
, name
) == 0)
552 f
->mode
= sb
.st_mode
;
554 fdinfo
= ul_path_fopenf(pc
, "r", "fdinfo/%d", assoc
);
556 read_fdinfo(f
, fdinfo
);
564 /* read symlinks from /proc/#/fd
566 static void collect_fd_files(struct path_cxt
*pc
, struct proc
*proc
)
569 struct dirent
*d
= NULL
;
570 char path
[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX
))];
572 while (ul_path_next_dirent(pc
, &sub
, "fd", &d
) == 0) {
575 if (ul_strtou64(d
->d_name
, &num
, 10) != 0) /* only numbers */
578 snprintf(path
, sizeof(path
), "fd/%ju", (uintmax_t) num
);
579 collect_file_symlink(pc
, proc
, path
, num
);
583 static void parse_maps_line(char *buf
, struct proc
*proc
)
585 uint64_t start
, end
, offset
, ino
;
586 unsigned long major
, minor
;
587 enum association assoc
= ASSOC_MEM
;
589 struct file
*f
, *prev
;
590 char *path
, modestr
[5];
593 /* ignore non-path entries */
594 path
= strchr(buf
, '/');
597 rtrim_whitespace((unsigned char *) path
);
599 /* read rest of the map */
600 if (sscanf(buf
, "%"SCNx64
/* start */
603 " %"SCNx64
/* offset */
604 " %lx:%lx" /* maj:min */
605 " %"SCNu64
, /* inode */
607 &start
, &end
, modestr
, &offset
,
608 &major
, &minor
, &ino
) != 7)
611 devno
= makedev(major
, minor
);
613 if (modestr
[3] == 's')
616 /* The map usually contains the same file more than once, try to reuse
617 * the previous file (if devno and ino are the same) to save stat() call.
619 prev
= list_last_entry(&proc
->files
, struct file
, files
);
621 if (prev
&& prev
->stat
.st_dev
== devno
&& prev
->stat
.st_ino
== ino
) {
623 f
->association
= -assoc
;
625 if (stat(path
, &sb
) < 0)
627 f
= new_file(proc
, stat2class(&sb
));
631 file_set_path(f
, &sb
, path
, -assoc
);
634 if (modestr
[0] == 'r')
636 if (modestr
[1] == 'w')
638 if (modestr
[2] == 'x')
641 f
->map_start
= start
;
645 file_init_content(f
);
648 static void collect_mem_files(struct path_cxt
*pc
, struct proc
*proc
)
653 fp
= ul_path_fopen(pc
, "r", "maps");
657 while (fgets(buf
, sizeof(buf
), fp
))
658 parse_maps_line(buf
, proc
);
663 static void collect_outofbox_files(struct path_cxt
*pc
,
665 enum association assocs
[],
671 for (i
= 0; i
< count
; i
++)
672 collect_file_symlink(pc
, proc
, names
[assocs
[i
]], assocs
[i
] * -1);
675 static void collect_execve_file(struct path_cxt
*pc
, struct proc
*proc
)
677 enum association assocs
[] = { ASSOC_EXE
};
678 const char *names
[] = {
681 collect_outofbox_files(pc
, proc
, assocs
, names
, ARRAY_SIZE(assocs
));
684 static void collect_fs_files(struct path_cxt
*pc
, struct proc
*proc
)
686 enum association assocs
[] = { ASSOC_EXE
, ASSOC_CWD
, ASSOC_ROOT
};
687 const char *names
[] = {
689 [ASSOC_ROOT
] = "root",
691 collect_outofbox_files(pc
, proc
, assocs
, names
, ARRAY_SIZE(assocs
));
694 static void collect_namespace_files(struct path_cxt
*pc
, struct proc
*proc
)
696 enum association assocs
[] = {
708 const char *names
[] = {
709 [ASSOC_NS_CGROUP
] = "ns/cgroup",
710 [ASSOC_NS_IPC
] = "ns/ipc",
711 [ASSOC_NS_MNT
] = "ns/mnt",
712 [ASSOC_NS_NET
] = "ns/net",
713 [ASSOC_NS_PID
] = "ns/pid",
714 [ASSOC_NS_PID4C
] = "ns/pid_for_children",
715 [ASSOC_NS_TIME
] = "ns/time",
716 [ASSOC_NS_TIME4C
] = "ns/time_for_children",
717 [ASSOC_NS_USER
] = "ns/user",
718 [ASSOC_NS_UTS
] = "ns/uts",
720 collect_outofbox_files(pc
, proc
, assocs
, names
, ARRAY_SIZE(assocs
));
723 static struct nodev
*new_nodev(unsigned long minor
, const char *filesystem
)
725 struct nodev
*nodev
= xcalloc(1, sizeof(*nodev
));
727 INIT_LIST_HEAD(&nodev
->nodevs
);
728 nodev
->minor
= minor
;
729 nodev
->filesystem
= xstrdup(filesystem
);
734 static void free_nodev(struct nodev
*nodev
)
736 free(nodev
->filesystem
);
740 static void initialize_nodevs(void)
744 for (i
= 0; i
< NODEV_TABLE_SIZE
; i
++)
745 INIT_LIST_HEAD(&nodev_table
.tables
[i
]);
748 static void finalize_nodevs(void)
752 for (i
= 0; i
< NODEV_TABLE_SIZE
; i
++)
753 list_free(&nodev_table
.tables
[i
], struct nodev
, nodevs
, free_nodev
);
755 free(mnt_namespaces
);
758 const char *get_nodev_filesystem(unsigned long minor
)
761 int slot
= minor
% NODEV_TABLE_SIZE
;
763 list_for_each (n
, &nodev_table
.tables
[slot
]) {
764 struct nodev
*nodev
= list_entry(n
, struct nodev
, nodevs
);
765 if (nodev
->minor
== minor
)
766 return nodev
->filesystem
;
771 static void add_nodevs(FILE *mnt
)
773 /* This can be very long. A line in mountinfo can have more than 3
775 char line
[PATH_MAX
* 3 + 256];
777 while (fgets(line
, sizeof(line
), mnt
)) {
778 unsigned long major
, minor
;
779 char filesystem
[256];
784 /* 23 61 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:2 - sysfs sysfs rw,seclabel */
785 if(sscanf(line
, "%*d %*d %lu:%lu %*s %*s %*s %*[^-] - %s %*[^\n]",
786 &major
, &minor
, filesystem
) != 3)
787 /* 1600 1458 0:55 / / rw,nodev,relatime - overlay overlay rw,context="s... */
788 if (sscanf(line
, "%*d %*d %lu:%lu %*s %*s %*s - %s %*[^\n]",
789 &major
, &minor
, filesystem
) != 3)
794 if (get_nodev_filesystem(minor
))
797 nodev
= new_nodev(minor
, filesystem
);
798 slot
= minor
% NODEV_TABLE_SIZE
;
800 list_add_tail(&nodev
->nodevs
, &nodev_table
.tables
[slot
]);
804 static void fill_column(struct proc
*proc
,
806 struct libscols_line
*ln
,
810 const struct file_class
*class = file
->class;
813 if (class->fill_column
814 && class->fill_column(proc
, file
, ln
,
815 column_id
, column_index
))
817 class = class->super
;
821 static void convert_file(struct proc
*proc
,
823 struct libscols_line
*ln
)
828 for (i
= 0; i
< ncolumns
; i
++)
829 fill_column(proc
, file
, ln
, get_column_id(i
), i
);
832 static void convert(struct list_head
*procs
, struct lsfd_control
*ctl
)
836 list_for_each (p
, procs
) {
837 struct proc
*proc
= list_entry(p
, struct proc
, procs
);
840 list_for_each (f
, &proc
->files
) {
841 struct file
*file
= list_entry(f
, struct file
, files
);
842 struct libscols_line
*ln
= scols_table_new_line(ctl
->tb
, NULL
);
843 struct lsfd_counter
**counter
= NULL
;
846 err(EXIT_FAILURE
, _("failed to allocate output line"));
848 convert_file(proc
, file
, ln
);
850 if (!lsfd_filter_apply(ctl
->filter
, ln
)) {
851 scols_table_remove_line(ctl
->tb
, ln
);
858 for (counter
= ctl
->counters
; *counter
; counter
++)
859 lsfd_counter_accumulate(*counter
, ln
);
864 static void delete(struct list_head
*procs
, struct lsfd_control
*ctl
)
866 list_free(procs
, struct proc
, procs
, free_proc
);
868 scols_unref_table(ctl
->tb
);
869 lsfd_filter_free(ctl
->filter
);
871 struct lsfd_counter
**counter
;
872 for (counter
= ctl
->counters
; *counter
; counter
++)
873 lsfd_counter_free(*counter
);
878 static void emit(struct lsfd_control
*ctl
)
880 scols_print_table(ctl
->tb
);
884 static void initialize_class(const struct file_class
*class)
886 if (class->initialize_class
)
887 class->initialize_class();
890 static void initialize_classes(void)
892 initialize_class(&file_class
);
893 initialize_class(&cdev_class
);
894 initialize_class(&bdev_class
);
895 initialize_class(&sock_class
);
896 initialize_class(&unkn_class
);
899 static void finalize_class(const struct file_class
*class)
901 if (class->finalize_class
)
902 class->finalize_class();
905 static void finalize_classes(void)
907 finalize_class(&file_class
);
908 finalize_class(&cdev_class
);
909 finalize_class(&bdev_class
);
910 finalize_class(&sock_class
);
911 finalize_class(&unkn_class
);
916 struct name_manager
*new_name_manager(void)
918 struct name_manager
*nm
= xcalloc(1, sizeof(struct name_manager
));
920 nm
->cache
= new_idcache();
922 err(EXIT_FAILURE
, _("failed to allocate an idcache"));
924 nm
->next_id
= 1; /* 0 is never issued as id. */
928 void free_name_manager(struct name_manager
*nm
)
930 free_idcache(nm
->cache
);
934 const char *get_name(struct name_manager
*nm
, unsigned long id
)
938 e
= get_id(nm
->cache
, id
);
940 return e
? e
->name
: NULL
;
943 unsigned long add_name(struct name_manager
*nm
, const char *name
)
945 struct identry
*e
= NULL
, *tmp
;
947 for (tmp
= nm
->cache
->ent
; tmp
; tmp
= tmp
->next
) {
948 if (strcmp(tmp
->name
, name
) == 0) {
957 e
= xmalloc(sizeof(struct identry
));
958 e
->name
= xstrdup(name
);
959 e
->id
= nm
->next_id
++;
960 e
->next
= nm
->cache
->ent
;
966 static void read_process(struct lsfd_control
*ctl
, struct path_cxt
*pc
,
967 pid_t pid
, struct proc
*leader
)
972 if (procfs_process_init_path(pc
, pid
) != 0)
975 proc
= new_process(pid
, leader
);
976 proc
->command
= procfs_process_get_cmdname(pc
, buf
, sizeof(buf
)) > 0 ?
977 xstrdup(buf
) : xstrdup(_("(unknown)"));
978 procfs_process_get_uid(pc
, &proc
->uid
);
980 if (procfs_process_get_stat(pc
, buf
, sizeof(buf
)) > 0) {
985 /* See proc(5) about the column in the line. */
986 xstrappend(&pat
, "%*d (");
987 for (p
= proc
->command
; *p
!= '\0'; p
++) {
989 xstrappend(&pat
, "%%");
993 xstrappend(&pat
, ") %*c %*d %*d %*d %*d %*d %u %*[^\n]");
994 if (sscanf(buf
, pat
, &flags
) == 1)
995 proc
->kthread
= !!(flags
& PF_KTHREAD
);
999 collect_execve_file(pc
, proc
);
1001 if (proc
->pid
== proc
->leader
->pid
1002 || kcmp(proc
->leader
->pid
, proc
->pid
, KCMP_FS
, 0, 0) != 0)
1003 collect_fs_files(pc
, proc
);
1005 collect_namespace_files(pc
, proc
);
1007 if (proc
->ns_mnt
== 0 || !has_mnt_ns(proc
->ns_mnt
)) {
1008 FILE *mnt
= ul_path_fopen(pc
, "r", "mountinfo");
1012 add_mnt_ns(proc
->ns_mnt
);
1017 /* If kcmp is not available,
1018 * there is no way to no whether threads share resources.
1019 * In such cases, we must pay the costs: call collect_mem_files()
1020 * and collect_fd_files().
1022 if (proc
->pid
== proc
->leader
->pid
1023 || kcmp(proc
->leader
->pid
, proc
->pid
, KCMP_VM
, 0, 0) != 0)
1024 collect_mem_files(pc
, proc
);
1026 if (proc
->pid
== proc
->leader
->pid
1027 || kcmp(proc
->leader
->pid
, proc
->pid
, KCMP_FILES
, 0, 0) != 0)
1028 collect_fd_files(pc
, proc
);
1030 list_add_tail(&proc
->procs
, &ctl
->procs
);
1032 /* The tasks collecting overwrites @pc by /proc/<task-pid>/. Keep it as
1033 * the last path based operation in read_process()
1035 if (ctl
->threads
&& leader
== NULL
) {
1039 while (procfs_process_next_tid(pc
, &sub
, &tid
) == 0) {
1042 read_process(ctl
, pc
, tid
, proc
);
1046 /* Let's be careful with number of open files */
1047 ul_path_close_dirfd(pc
);
1050 static void parse_pids(const char *str
, pid_t
**pids
, int *count
)
1059 v
= strtol(str
, &next
, 10);
1061 err(EXIT_FAILURE
, _("unexpected value for pid specification: %s"), str
);
1063 errx(EXIT_FAILURE
, _("garbage at the end of pid specification: %s"), str
);
1065 errx(EXIT_FAILURE
, _("out of range value for pid specification: %ld"), v
);
1068 *pids
= xrealloc(*pids
, (*count
) * sizeof(**pids
));
1069 (*pids
)[*count
- 1] = (pid_t
)v
;
1071 while (next
&& *next
!= '\0'
1072 && (isspace((unsigned char)*next
) || *next
== ','))
1075 parse_pids(next
, pids
, count
);
1078 static int pidcmp(const void *a
, const void *b
)
1080 pid_t pa
= *(pid_t
*)a
;
1081 pid_t pb
= *(pid_t
*)b
;
1091 static void sort_pids(pid_t pids
[], const int count
)
1093 qsort(pids
, count
, sizeof(pid_t
), pidcmp
);
1096 static bool member_pids(const pid_t pid
, const pid_t pids
[], const int count
)
1098 return bsearch(&pid
, pids
, count
, sizeof(pid_t
), pidcmp
)? true: false;
1101 static void collect_processes(struct lsfd_control
*ctl
, const pid_t pids
[], int n_pids
)
1105 struct path_cxt
*pc
= NULL
;
1107 pc
= ul_new_path(NULL
);
1109 err(EXIT_FAILURE
, _("failed to alloc procfs handler"));
1111 dir
= opendir(_PATH_PROC
);
1113 err(EXIT_FAILURE
, _("failed to open /proc"));
1115 while ((d
= readdir(dir
))) {
1118 if (procfs_dirent_get_pid(d
, &pid
) != 0)
1120 if (n_pids
== 0 || member_pids(pid
, pids
, n_pids
))
1121 read_process(ctl
, pc
, pid
, 0);
1128 static void __attribute__((__noreturn__
)) usage(void)
1133 fputs(USAGE_HEADER
, out
);
1134 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
1136 fputs(USAGE_OPTIONS
, out
);
1137 fputs(_(" -l, --threads list in threads level\n"), out
);
1138 fputs(_(" -J, --json use JSON output format\n"), out
);
1139 fputs(_(" -n, --noheadings don't print headings\n"), out
);
1140 fputs(_(" -o, --output <list> output columns\n"), out
);
1141 fputs(_(" -r, --raw use raw output format\n"), out
);
1142 fputs(_(" -u, --notruncate don't truncate text in columns\n"), out
);
1143 fputs(_(" -p, --pid <pid(s)> collect information only specified processes\n"), out
);
1144 fputs(_(" -Q, --filter <expr> apply display filter\n"), out
);
1145 fputs(_(" --debug-filter dump the innternal data structure of filter and exit\n"), out
);
1146 fputs(_(" -C, --counter <name>:<expr>\n"
1147 " make a counter used in --summary output\n"), out
);
1148 fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out
);
1150 fputs(USAGE_SEPARATOR
, out
);
1151 printf(USAGE_HELP_OPTIONS(23));
1153 fprintf(out
, USAGE_COLUMNS
);
1155 for (i
= 0; i
< ARRAY_SIZE(infos
); i
++)
1156 fprintf(out
, " %11s %-10s%s\n", infos
[i
].name
,
1157 infos
[i
].json_type
== SCOLS_JSON_STRING
? "<string>":
1158 infos
[i
].json_type
== SCOLS_JSON_NUMBER
? "<number>":
1162 printf(USAGE_MAN_TAIL("lsfd(1)"));
1167 static void xstrappend(char **a
, const char *b
)
1169 if (strappend(a
, b
) < 0)
1170 err(EXIT_FAILURE
, _("failed to allocate memory for string"));
1173 static void xstrputc(char **a
, char c
)
1175 char b
[] = {c
, '\0'};
1179 static void append_filter_expr(char **a
, const char *b
, bool and)
1193 xstrappend(a
, "and(");
1195 xstrappend(a
, "or(");
1200 static struct lsfd_filter
*new_filter(const char *expr
, bool debug
, const char *err_prefix
, struct lsfd_control
*ctl
)
1202 struct lsfd_filter
*filter
;
1205 filter
= lsfd_filter_new(expr
, ctl
->tb
,
1207 column_name_to_id_cb
,
1208 add_column_by_id_cb
, ctl
);
1209 errmsg
= lsfd_filter_get_errmsg(filter
);
1211 errx(EXIT_FAILURE
, "%s%s", err_prefix
, errmsg
);
1213 lsfd_filter_dump(filter
, stdout
);
1220 static struct counter_spec
*new_counter_spec(const char *spec_str
)
1223 struct counter_spec
*spec
;
1225 if (spec_str
[0] == '\0')
1227 _("too short counter specification: -C/--counter %s"),
1229 if (spec_str
[0] == ':')
1231 _("no name for counter: -C/--counter %s"),
1234 sep
= strchr(spec_str
, ':');
1237 _("no name for counter: -C/--counter %s"),
1241 _("empty ecounter expression given: -C/--counter %s"),
1244 /* Split the spec_str in to name and expr. */
1247 if (strchr(spec_str
, '{'))
1249 _("don't use `{' in the name of a counter: %s"),
1252 spec
= xmalloc(sizeof(struct counter_spec
));
1253 INIT_LIST_HEAD(&spec
->specs
);
1254 spec
->name
= spec_str
;
1255 spec
->expr
= sep
+ 1;
1260 static void free_counter_spec(struct counter_spec
*counter_spec
)
1265 static struct lsfd_counter
*new_counter(struct counter_spec
*spec
, struct lsfd_control
*ctl
)
1267 struct lsfd_filter
*filter
;
1269 filter
= new_filter(spec
->expr
, false,
1270 _("failed in making filter for a counter: "),
1272 return lsfd_counter_new(spec
->name
, filter
);
1275 static struct lsfd_counter
**new_counters(struct list_head
*specs
, struct lsfd_control
*ctl
)
1277 struct lsfd_counter
**counters
;
1278 size_t len
= list_count_entries(specs
);
1280 struct list_head
*s
;
1282 counters
= xcalloc(len
+ 1, sizeof(struct lsfd_counter
*));
1283 list_for_each(s
, specs
) {
1284 struct counter_spec
*spec
= list_entry(s
, struct counter_spec
, specs
);
1285 counters
[i
++] = new_counter(spec
, ctl
);
1287 assert(counters
[len
] == NULL
);
1292 static struct lsfd_counter
**new_default_counters(struct lsfd_control
*ctl
)
1294 struct lsfd_counter
**counters
;
1295 size_t len
= ARRAY_SIZE(default_counter_specs
);
1298 counters
= xcalloc(len
+ 1, sizeof(struct lsfd_counter
*));
1299 for (i
= 0; i
< len
; i
++) {
1300 struct counter_spec
*spec
= default_counter_specs
+ i
;
1301 counters
[i
] = new_counter(spec
, ctl
);
1303 assert(counters
[len
] == NULL
);
1308 static struct libscols_table
*new_summary_table(struct lsfd_control
*ctl
)
1310 struct libscols_table
*tb
= scols_new_table();
1312 struct libscols_column
*name_cl
, *value_cl
;
1315 err(EXIT_FAILURE
, _("failed to allocate summary table"));
1317 scols_table_enable_noheadings(tb
, ctl
->noheadings
);
1318 scols_table_enable_raw(tb
, ctl
->raw
);
1319 scols_table_enable_json(tb
, ctl
->json
);
1322 scols_table_set_name(tb
, "lsfd-summary");
1325 value_cl
= scols_table_new_column(tb
, _("VALUE"), 0, SCOLS_FL_RIGHT
);
1327 err(EXIT_FAILURE
, _("failed to allocate summary column"));
1329 scols_column_set_json_type(value_cl
, SCOLS_JSON_NUMBER
);
1331 name_cl
= scols_table_new_column(tb
, _("COUNTER"), 0, 0);
1333 err(EXIT_FAILURE
, _("failed to allocate summary column"));
1335 scols_column_set_json_type(name_cl
, SCOLS_JSON_STRING
);
1340 static void fill_summary_line(struct libscols_line
*ln
, struct lsfd_counter
*counter
)
1344 xasprintf(&str
, "%llu", (unsigned long long)lsfd_counter_value(counter
));
1346 err(EXIT_FAILURE
, _("failed to add summary data"));
1347 if (scols_line_refer_data(ln
, 0, str
))
1348 err(EXIT_FAILURE
, _("failed to add summary data"));
1350 if (scols_line_set_data(ln
, 1, lsfd_counter_name(counter
)))
1351 err(EXIT_FAILURE
, _("failed to add summary data"));
1354 static void emit_summary(struct lsfd_control
*ctl
, struct lsfd_counter
**counter
)
1356 struct libscols_table
*tb
= new_summary_table(ctl
);
1358 for (; *counter
; counter
++) {
1359 struct libscols_line
*ln
= scols_table_new_line(tb
, NULL
);
1360 fill_summary_line(ln
, *counter
);
1362 scols_print_table(tb
);
1364 scols_unref_table(tb
);
1367 int main(int argc
, char *argv
[])
1371 char *outarg
= NULL
;
1372 struct lsfd_control ctl
= {};
1373 char *filter_expr
= NULL
;
1374 bool debug_filter
= false;
1377 struct list_head counter_specs
;
1379 INIT_LIST_HEAD(&counter_specs
);
1382 OPT_DEBUG_FILTER
= CHAR_MAX
+ 1,
1385 static const struct option longopts
[] = {
1386 { "noheadings", no_argument
, NULL
, 'n' },
1387 { "output", required_argument
, NULL
, 'o' },
1388 { "version", no_argument
, NULL
, 'V' },
1389 { "help", no_argument
, NULL
, 'h' },
1390 { "json", no_argument
, NULL
, 'J' },
1391 { "raw", no_argument
, NULL
, 'r' },
1392 { "threads", no_argument
, NULL
, 'l' },
1393 { "notruncate", no_argument
, NULL
, 'u' },
1394 { "pid", required_argument
, NULL
, 'p' },
1395 { "filter", required_argument
, NULL
, 'Q' },
1396 { "debug-filter",no_argument
, NULL
, OPT_DEBUG_FILTER
},
1397 { "summary", optional_argument
, NULL
, OPT_SUMMARY
},
1398 { "counter", required_argument
, NULL
, 'C' },
1399 { NULL
, 0, NULL
, 0 },
1402 setlocale(LC_ALL
, "");
1403 bindtextdomain(PACKAGE
, LOCALEDIR
);
1404 textdomain(PACKAGE
);
1405 close_stdout_atexit();
1407 while ((c
= getopt_long(argc
, argv
, "no:JrVhluQ:p:C:s", longopts
, NULL
)) != -1) {
1428 parse_pids(optarg
, &pids
, &n_pids
);
1431 append_filter_expr(&filter_expr
, optarg
, true);
1434 struct counter_spec
*c
= new_counter_spec(optarg
);
1435 list_add_tail(&c
->specs
, &counter_specs
);
1438 case OPT_DEBUG_FILTER
:
1439 debug_filter
= true;
1443 if (strcmp(optarg
, "never") == 0)
1445 else if (strcmp(optarg
, "only") == 0)
1446 ctl
.summary
|= (SUMMARY_ONLY
|SUMMARY_EMIT
);
1447 else if (strcmp(optarg
, "always") == 0)
1448 ctl
.summary
|= SUMMARY_EMIT
;
1450 errx(EXIT_FAILURE
, _("unsupported --summary argument"));
1452 ctl
.summary
|= SUMMARY_EMIT
;
1455 print_version(EXIT_SUCCESS
);
1459 errtryhelp(EXIT_FAILURE
);
1463 #define INITIALIZE_COLUMNS(COLUMN_SPEC) \
1464 for (i = 0; i < ARRAY_SIZE(COLUMN_SPEC); i++) \
1465 columns[ncolumns++] = COLUMN_SPEC[i]
1468 INITIALIZE_COLUMNS(default_threads_columns
);
1470 INITIALIZE_COLUMNS(default_columns
);
1473 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
1474 &ncolumns
, column_name_to_id
) < 0)
1475 return EXIT_FAILURE
;
1477 scols_init_debug(0);
1479 INIT_LIST_HEAD(&ctl
.procs
);
1481 /* inilialize scols table */
1482 ctl
.tb
= scols_new_table();
1484 err(EXIT_FAILURE
, _("failed to allocate output table"));
1486 scols_table_enable_noheadings(ctl
.tb
, ctl
.noheadings
);
1487 scols_table_enable_raw(ctl
.tb
, ctl
.raw
);
1488 scols_table_enable_json(ctl
.tb
, ctl
.json
);
1490 scols_table_set_name(ctl
.tb
, "lsfd");
1492 /* create output columns */
1493 for (i
= 0; i
< ncolumns
; i
++) {
1494 const struct colinfo
*col
= get_column_info(i
);
1495 struct libscols_column
*cl
= add_column(ctl
.tb
, col
);
1498 err(EXIT_FAILURE
, _("failed to allocate output column"));
1501 int flags
= scols_column_get_flags(cl
);
1502 flags
&= ~SCOLS_FL_TRUNC
;
1503 scols_column_set_flags(cl
, flags
);
1509 ctl
.filter
= new_filter(filter_expr
, debug_filter
, "", &ctl
);
1514 if (ctl
.summary
& SUMMARY_EMIT
) {
1515 if (list_empty(&counter_specs
))
1516 ctl
.counters
= new_default_counters(&ctl
);
1518 ctl
.counters
= new_counters(&counter_specs
, &ctl
);
1519 list_free(&counter_specs
, struct counter_spec
, specs
,
1525 sort_pids(pids
, n_pids
);
1528 initialize_nodevs();
1529 initialize_classes();
1531 collect_processes(&ctl
, pids
, n_pids
);
1534 convert(&ctl
.procs
, &ctl
);
1535 if (!(ctl
.summary
& SUMMARY_ONLY
))
1537 if (ctl
.counters
&& (ctl
.summary
& SUMMARY_EMIT
))
1538 emit_summary(&ctl
, ctl
.counters
);
1541 delete(&ctl
.procs
, &ctl
);