]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/lsfd.c
lsfd: (refactor) rename a local variable and a parameter
[thirdparty/util-linux.git] / misc-utils / lsfd.c
CommitLineData
128beb71
MY
1/*
2 * lsfd(1) - list file descriptors
3 *
4 * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
ee194909 6 * Karel Zak <kzak@redhat.com>
128beb71
MY
7 *
8 * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu>
9 * It supports multiple OSes. lsfd specializes to Linux.
10 *
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.
15 *
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.
20 *
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
24 */
25
26#include <stdio.h>
27#include <sys/types.h>
128beb71 28#include <inttypes.h>
128beb71
MY
29#include <sys/stat.h>
30#include <unistd.h>
31#include <getopt.h>
4cfabab2 32#include <ctype.h>
7dc7b6f4 33#include <search.h>
adf10f7d 34#include <poll.h>
036e5886 35#include <sys/select.h>
128beb71 36
adf10f7d 37#include <sys/uio.h>
f26ddd08 38#include <linux/sched.h>
3d4b926a 39#include <sys/syscall.h>
87a2cff6
MY
40
41#ifdef HAVE_LINUX_KCMP_H
42# include <linux/kcmp.h>
3d4b926a
MY
43static int kcmp(pid_t pid1, pid_t pid2, int type,
44 unsigned long idx1, unsigned long idx2)
45{
46 return syscall(SYS_kcmp, pid1, pid2, type, idx1, idx2);
47}
87a2cff6
MY
48#else
49# ifndef KCMP_FS
50# define KCMP_FS 0
51# endif
52# ifndef KCMP_VM
53# define KCMP_VM 0
54# endif
55# ifndef KCMP_FILES
56# define KCMP_FILES 0
57# endif
58static int kcmp(pid_t pid1 __attribute__((__unused__)),
59 pid_t pid2 __attribute__((__unused__)),
60 int type __attribute__((__unused__)),
61 unsigned long idx1 __attribute__((__unused__)),
62 unsigned long idx2 __attribute__((__unused__)))
63{
64 /* lsfd uses kcmp only for optimization. If the platform doesn't provide
65 * kcmp, just returning an error is acceptable. */
66 errno = ENOSYS;
67 return -1;
68}
69#endif
3d4b926a 70
f26ddd08
MY
71/* See proc(5).
72 * Defined in linux/include/linux/sched.h private header file. */
73#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
74
128beb71 75#include "c.h"
128beb71
MY
76#include "list.h"
77#include "closestream.h"
0cd21cf6 78#include "column-list-table.h"
128beb71 79#include "strutils.h"
b8ae00a7 80#include "procfs.h"
128beb71 81#include "fileutils.h"
1deac7db 82#include "idcache.h"
cb6f0c4a 83#include "pathnames.h"
128beb71 84
600e6e52 85#include "lsfd.h"
128beb71 86
269c425f
MY
87/*
88 * /proc/$pid/mountinfo entries
89 */
90struct nodev {
91 struct list_head nodevs;
92 unsigned long minor;
93 char *filesystem;
94};
95
bc9fd537
MY
96struct nodev_table {
97#define NODEV_TABLE_SIZE 97
98 struct list_head tables[NODEV_TABLE_SIZE];
f3653e4e
MY
99};
100static struct nodev_table nodev_table;
bc9fd537 101
e1a79781 102struct mnt_namespace {
5b4b9e9a 103 bool read_mountinfo;
e1a79781
MY
104 ino_t id;
105};
106
5b4b9e9a
MY
107static struct mnt_namespace *find_mnt_ns(ino_t id);
108static struct mnt_namespace *add_mnt_ns(ino_t id);
e1a79781
MY
109static void *mnt_namespaces; /* for tsearch/tfind */
110
a9109434
KZ
111struct name_manager {
112 struct idcache *cache;
113 unsigned long next_id;
114};
115
3c007400
MY
116/*
117 * /proc/devices entries
118 */
119struct devdrv {
120 struct list_head devdrvs;
121 unsigned long major;
122 char *name;
123};
124
125static struct list_head chrdrvs;
00726e78 126static struct list_head blkdrvs;
3c007400 127
feb48e88
MY
128/*
129 * IPC table
130 */
131
132#define IPC_TABLE_SIZE 997
133struct ipc_table {
134 struct list_head tables[IPC_TABLE_SIZE];
135};
136
137static struct ipc_table ipc_table;
138
128beb71
MY
139/*
140 * Column related stuffs
141 */
142
128beb71
MY
143/* column names */
144struct colinfo {
145 const char *name;
146 double whint;
147 int flags;
fd4fe429 148 int json_type;
128beb71
MY
149 const char *help;
150};
151
152/* columns descriptions */
54a06438 153static const struct colinfo infos[] = {
73777694
MY
154 [COL_AINODECLASS] = { "AINODECLASS",
155 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
156 N_("class of anonymous inode") },
157 [COL_ASSOC] = { "ASSOC",
158 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
159 N_("association between file and process") },
160 [COL_BLKDRV] = { "BLKDRV",
161 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
162 N_("block device driver name resolved by /proc/devices") },
ea9466d4
MY
163 [COL_BPF_MAP_ID] = { "BPF-MAP.ID",
164 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
165 N_("bpf map id associated with the fd") },
166 [COL_BPF_MAP_TYPE] = { "BPF-MAP.TYPE",
167 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
168 N_("bpf map type (decoded)") },
169 [COL_BPF_MAP_TYPE_RAW]= { "BPF-MAP.TYPE.RAW",
170 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
171 N_("bpf map type (raw)") },
bc808368
MY
172 [COL_BPF_NAME] = { "BPF.NAME",
173 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
174 N_("bpf object name") },
ec2c77da
MY
175 [COL_BPF_PROG_ID] = { "BPF-PROG.ID",
176 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
177 N_("bpf program id associated with the fd") },
178 [COL_BPF_PROG_TYPE] = { "BPF-PROG.TYPE",
179 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
180 N_("bpf program type (decoded)") },
181 [COL_BPF_PROG_TYPE_RAW]= { "BPF-PROG.TYPE.RAW",
182 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
183 N_("bpf program type (raw)") },
73777694
MY
184 [COL_CHRDRV] = { "CHRDRV",
185 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
186 N_("character device driver name resolved by /proc/devices") },
187 [COL_COMMAND] = { "COMMAND",
188 0.3, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
189 N_("command of the process opening the file") },
190 [COL_DELETED] = { "DELETED",
191 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
192 N_("reachability from the file system") },
193 [COL_DEV] = { "DEV",
194 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
195 N_("ID of device containing file") },
196 [COL_DEVTYPE] = { "DEVTYPE",
197 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
198 N_("device type (blk, char, or nodev)") },
199 [COL_ENDPOINTS] = { "ENDPOINTS",
42497e7d 200 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING,
73777694 201 N_("IPC endpoints information communicated with the fd") },
8c2ca9c9
MY
202 [COL_EVENTFD_ID] = {"EVENTFD.ID",
203 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
204 N_("eventfd ID") },
c2e2dd60 205 [COL_EVENTPOLL_TFDS] = {"EVENTPOLL.TFDS",
42497e7d 206 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_NUMBER,
c2e2dd60 207 N_("file descriptors targeted by the eventpoll file") },
73777694
MY
208 [COL_FD] = { "FD",
209 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
210 N_("file descriptor for the file") },
c2e2dd60
MY
211 [COL_FLAGS] = { "FLAGS",
212 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
213 N_("flags specified when opening the file") },
73777694
MY
214 [COL_FUID] = { "FUID",
215 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
216 N_("user ID number of the file's owner") },
73777694
MY
217 [COL_INET_LADDR] = { "INET.LADDR",
218 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
219 N_("local IP address") },
220 [COL_INET_RADDR] = { "INET.RADDR",
221 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
222 N_("remote IP address") },
223 [COL_INET6_LADDR] = { "INET6.LADDR",
224 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
225 N_("local IPv6 address") },
226 [COL_INET6_RADDR] = { "INET6.RADDR",
227 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
228 N_("remote IPv6 address") },
c2e2dd60
MY
229 [COL_INODE] = { "INODE",
230 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
231 N_("inode number") },
c1a26ac8 232 [COL_INOTIFY_INODES] = { "INOTIFY.INODES",
42497e7d 233 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING,
c1a26ac8
MY
234 N_("list of monitoring inodes (cooked)") },
235 [COL_INOTIFY_INODES_RAW]={ "INOTIFY.INODES.RAW",
42497e7d 236 0, SCOLS_FL_WRAP, SCOLS_JSON_ARRAY_STRING,
c1a26ac8 237 N_("list of monitoring inodes (raw, don't decode devices)") },
73777694
MY
238 [COL_KNAME] = { "KNAME",
239 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
240 N_("name of the file (raw)") },
241 [COL_KTHREAD] = { "KTHREAD",
242 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
243 N_("opened by a kernel thread") },
244 [COL_MAJMIN] = { "MAJ:MIN",
245 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
246 N_("device ID for special, or ID of device containing file") },
247 [COL_MAPLEN] = { "MAPLEN",
248 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
249 N_("length of file mapping (in page)") },
250 [COL_MISCDEV] = { "MISCDEV",
251 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
252 N_("misc character device name resolved by /proc/misc") },
253 [COL_MNT_ID] = { "MNTID",
254 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
255 N_("mount id") },
256 [COL_MODE] = { "MODE",
257 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
258 N_("access mode (rwx)") },
259 [COL_NAME] = { "NAME",
260 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
261 N_("name of the file (cooked)") },
262 [COL_NETLINK_GROUPS] = { "NETLINK.GROUPS",
263 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
264 N_("netlink multicast groups") },
265 [COL_NETLINK_LPORT] = { "NETLINK.LPORT",
266 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
267 N_("netlink local port id") },
268 [COL_NETLINK_PROTOCOL] = { "NETLINK.PROTOCOL",
269 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
270 N_("netlink protocol") },
271 [COL_NLINK] = { "NLINK",
272 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
273 N_("link count") },
274 [COL_NS_NAME] = { "NS.NAME",
275 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
276 N_("name of the namespace (NS.TYPE:[INODE])") },
277 [COL_NS_TYPE] = { "NS.TYPE",
278 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
279 N_("type of the namespace") },
280 [COL_OWNER] = { "OWNER",
281 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
282 N_("owner of the file") },
adfc156a
MY
283 [COL_PACKET_IFACE] = { "PACKET.IFACE",
284 0, SCOLS_FL_RIGHT,SCOLS_JSON_STRING,
285 N_("net interface associated with the packet socket") },
286 [COL_PACKET_PROTOCOL] = { "PACKET.PROTOCOL",
287 0, SCOLS_FL_RIGHT,SCOLS_JSON_STRING,
288 N_("L3 protocol associated with the packet socket") },
73777694
MY
289 [COL_PARTITION] = { "PARTITION",
290 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
291 N_("block device name resolved by /proc/partition") },
292 [COL_PID] = { "PID",
293 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
294 N_("PID of the process opening the file") },
295 [COL_PIDFD_COMM] = { "PIDFD.COMM",
296 0.2, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
297 N_("command of the process targeted by the pidfd") },
298 [COL_PIDFD_NSPID] = { "PIDFD.NSPID",
299 0.2, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
300 N_("NSpid field in fdinfo of the pidfd") },
301 [COL_PIDFD_PID] = { "PIDFD.PID",
302 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
303 N_("PID of the process targeted by the pidfd") },
304 [COL_PING_ID] = { "PING.ID",
305 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
306 N_("ICMP echo request ID") },
307 [COL_POS] = { "POS",
308 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
309 N_("file position") },
6919acff
MY
310 [COL_PTMX_TTY_INDEX] = { "PTMX.TTY-INDEX",
311 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
312 N_("tty index of the counterpart") },
73777694
MY
313 [COL_RAW_PROTOCOL] = { "RAW.PROTOCOL",
314 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
315 N_("protocol number of the raw socket") },
316 [COL_RDEV] = { "RDEV",
317 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
318 N_("device ID (if special file)") },
b22d1e54
MY
319 [COL_SIGNALFD_MASK] = { "SIGNALFD.MASK",
320 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
321 N_("masked signals") },
73777694
MY
322 [COL_SIZE] = { "SIZE",
323 4, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
324 N_("file size"), },
325 [COL_SOCK_LISTENING] = { "SOCK.LISTENING",
326 0, SCOLS_FL_RIGHT, SCOLS_JSON_BOOLEAN,
327 N_("listening socket") },
328 [COL_SOCK_NETNS] = { "SOCK.NETNS",
329 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
330 N_("inode identifying network namespace where the socket belongs to") },
331 [COL_SOCK_PROTONAME] = { "SOCK.PROTONAME",
332 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
333 N_("protocol name") },
426ff07f
MY
334 [COL_SOCK_SHUTDOWN] = { "SOCK.SHUTDOWN",
335 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
336 N_("shutdown state of socket ([-r?][-w?])") },
73777694
MY
337 [COL_SOCK_STATE] = { "SOCK.STATE",
338 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
cd30dd6f 339 N_("state of socket") },
73777694
MY
340 [COL_SOCK_TYPE] = { "SOCK.TYPE",
341 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
cd30dd6f 342 N_("type of socket") },
73777694
MY
343 [COL_SOURCE] = { "SOURCE",
344 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
345 N_("file system, partition, or device containing file") },
346 [COL_STTYPE] = { "STTYPE",
347 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
348 N_("file type (raw)") },
349 [COL_TCP_LADDR] = { "TCP.LADDR",
350 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
351 N_("local TCP address (INET address:TCP port)") },
352 [COL_TCP_RADDR] = { "TCP.RADDR",
353 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
354 N_("remote TCP address (INET address:TCP port)") },
355 [COL_TCP_LPORT] = { "TCP.LPORT",
356 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
357 N_("local TCP port") },
358 [COL_TCP_RPORT] = { "TCP.RPORT",
359 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
360 N_("remote TCP port") },
361 [COL_TID] = { "TID",
362 5, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
363 N_("thread ID of the process opening the file") },
188b7d64
MY
364 [COL_TIMERFD_CLOCKID] = { "TIMERFD.CLOCKID",
365 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
366 N_("clockid") },
367 [COL_TIMERFD_INTERVAL] = { "TIMERFD.INTERVAL",
bdc89612 368 0, SCOLS_FL_RIGHT, SCOLS_JSON_FLOAT,
188b7d64
MY
369 N_("interval") },
370 [COL_TIMERFD_REMAINING]= { "TIMERFD.REMAINING",
bdc89612 371 0, SCOLS_FL_RIGHT, SCOLS_JSON_FLOAT,
188b7d64 372 N_("remaining time") },
7cbec02a
MY
373 [COL_TUN_IFACE] = { "TUN.IFACE",
374 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
375 N_("network interface behind the tun device") },
73777694
MY
376 [COL_TYPE] = { "TYPE",
377 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
378 N_("file type (cooked)") },
379 [COL_UDP_LADDR] = { "UDP.LADDR",
380 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
381 N_("local UDP address (INET address:UDP port)") },
382 [COL_UDP_RADDR] = { "UDP.RADDR",
383 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
384 N_("remote UDP address (INET address:UDP port)") },
385 [COL_UDP_LPORT] = { "UDP.LPORT",
386 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
387 N_("local UDP port") },
388 [COL_UDP_RPORT] = { "UDP.RPORT",
389 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
390 N_("remote UDP port") },
391 [COL_UDPLITE_LADDR] = { "UDPLITE.LADDR",
392 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
393 N_("local UDPLite address (INET address:UDPLite port)") },
394 [COL_UDPLITE_RADDR] = { "UDPLITE.RADDR",
395 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
396 N_("remote UDPLite address (INET address:UDPLite port)") },
397 [COL_UDPLITE_LPORT] = { "UDPLITE.LPORT",
398 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
399 N_("local UDPLite port") },
400 [COL_UDPLITE_RPORT] = { "UDPLITE.RPORT",
401 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
402 N_("remote UDPLite port") },
403 [COL_UID] = { "UID",
404 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
405 N_("user ID number of the process") },
406 [COL_UNIX_PATH] = { "UNIX.PATH",
407 0.4, SCOLS_FL_TRUNC, SCOLS_JSON_STRING,
df19f7c6 408 N_("filesystem pathname for UNIX domain socket") },
73777694
MY
409 [COL_USER] = { "USER",
410 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
411 N_("user of the process") },
92c00d72
MY
412 [COL_XMODE] = { "XMODE",
413 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
6b3efddf 414 N_("extended version of MDOE (rwxD[Ll]m)") },
128beb71
MY
415};
416
78f20159 417static const int default_columns[] = {
b7b6e261
MY
418 COL_COMMAND,
419 COL_PID,
420 COL_USER,
421 COL_ASSOC,
92c00d72 422 COL_XMODE,
b7b6e261 423 COL_TYPE,
618db964 424 COL_SOURCE,
2f947be1 425 COL_MNT_ID,
b7b6e261
MY
426 COL_INODE,
427 COL_NAME,
428};
429
78f20159 430static const int default_threads_columns[] = {
6008b460
MY
431 COL_COMMAND,
432 COL_PID,
433 COL_TID,
434 COL_USER,
435 COL_ASSOC,
92c00d72 436 COL_XMODE,
6008b460 437 COL_TYPE,
618db964 438 COL_SOURCE,
2f947be1 439 COL_MNT_ID,
6008b460
MY
440 COL_INODE,
441 COL_NAME,
442};
443
128beb71
MY
444static int columns[ARRAY_SIZE(infos) * 2] = {-1};
445static size_t ncolumns;
446
7d8c81d7
MY
447struct counter_spec {
448 struct list_head specs;
449 const char *name;
450 const char *expr;
451};
452
54a06438 453static const struct counter_spec default_counter_specs[] = {
7d8c81d7
MY
454 {
455 .name = N_("processes"),
456 .expr = "ASSOC == 'cwd'",
457 },
458 {
459 .name = N_("root owned processes"),
460 .expr = "(ASSOC == 'cwd') && (UID == 0)",
461 },
462 {
463 .name = N_("kernel threads"),
464 .expr = "(ASSOC == 'cwd') && KTHREAD",
465 },
466 {
467 .name = N_("open files"),
468 .expr = "FD >= 0",
469 },
470 {
471 .name = N_("RO open files"),
472 .expr = "(FD >= 0) and (MODE == 'r--')",
473 },
474 {
475 .name = N_("WO open files"),
476 .expr = "(FD >= 0) and (MODE == '-w-')",
477 },
478 {
479 .name = N_("shared mappings"),
480 .expr = "ASSOC == 'shm'",
481 },
482 {
483 .name = N_("RO shared mappings"),
484 .expr = "(ASSOC == 'shm') and (MODE == 'r--')",
485 },
486 {
487 .name = N_("WO shared mappings"),
488 .expr = "(ASSOC == 'shm') and (MODE == '-w-')",
489 },
490 {
491 .name = N_("regular files"),
198c6408 492 .expr = "(FD >= 0) && (STTYPE == 'REG')",
7d8c81d7
MY
493 },
494 {
495 .name = N_("directories"),
198c6408 496 .expr = "(FD >= 0) && (STTYPE == 'DIR')",
7d8c81d7
MY
497 },
498 {
499 .name = N_("sockets"),
198c6408 500 .expr = "(FD >= 0) && (STTYPE == 'SOCK')",
7d8c81d7
MY
501 },
502 {
503 .name = N_("fifos/pipes"),
198c6408 504 .expr = "(FD >= 0) && (STTYPE == 'FIFO')",
7d8c81d7
MY
505 },
506 {
507 .name = N_("character devices"),
198c6408 508 .expr = "(FD >= 0) && (STTYPE == 'CHR')",
7d8c81d7
MY
509 },
510 {
511 .name = N_("block devices"),
198c6408 512 .expr = "(FD >= 0) && (STTYPE == 'BLK')",
7d8c81d7
MY
513 },
514 {
515 .name = N_("unknown types"),
198c6408 516 .expr = "(FD >= 0) && (STTYPE == 'UNKN')",
7d8c81d7
MY
517 }
518};
519
46eca86e
KZ
520/* "userdata" used by callback for libsmartcols filter */
521struct filler_data {
522 struct proc *proc;
523 struct file *file;
524};
525
128beb71
MY
526struct lsfd_control {
527 struct libscols_table *tb; /* output */
cb6f0c4a 528 struct list_head procs; /* list of all processes */
128beb71 529
73ee7d4c
KZ
530 unsigned int noheadings : 1,
531 raw : 1,
532 json : 1,
533 notrunc : 1,
7d8c81d7 534 threads : 1,
0d681980 535 show_main : 1, /* print main table */
0ece530d 536 show_summary : 1, /* print summary/counters */
c3f40eeb
MY
537 sockets_only : 1, /* display only SOCKETS */
538 show_xmode : 1; /* XMODE column is enabled. */
52ab46da 539
46eca86e
KZ
540 struct libscols_filter *filter; /* filter */
541 struct libscols_filter **ct_filters; /* counters (NULL terminated array) */
128beb71
MY
542};
543
7dc7b6f4
MY
544static void *proc_tree; /* for tsearch/tfind */
545
546static int proc_tree_compare(const void *a, const void *b)
547{
0caccbf1 548 return ((struct proc *)a)->pid - ((struct proc *)b)->pid;
7dc7b6f4
MY
549}
550
551struct proc *get_proc(pid_t pid)
552{
816109f6
TW
553 struct proc key = { .pid = pid };
554 struct proc **node = tfind(&key, &proc_tree, proc_tree_compare);
7dc7b6f4
MY
555 if (node)
556 return *node;
557 return NULL;
558}
559
128beb71
MY
560static int column_name_to_id(const char *name, size_t namesz)
561{
562 size_t i;
563
564 for (i = 0; i < ARRAY_SIZE(infos); i++) {
565 const char *cn = infos[i].name;
566
567 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
568 return i;
569 }
570 warnx(_("unknown column: %s"), name);
46eca86e 571 return -1;
52ab46da
MY
572}
573
128beb71
MY
574static int get_column_id(int num)
575{
576 assert(num >= 0);
577 assert((size_t) num < ncolumns);
578 assert(columns[num] < (int) ARRAY_SIZE(infos));
579
580 return columns[num];
581}
582
583static const struct colinfo *get_column_info(int num)
584{
585 return &infos[ get_column_id(num) ];
586}
587
e8f945e4
KZ
588static struct libscols_column *add_column(struct libscols_table *tb,
589 const struct colinfo *col, int extra)
52ab46da
MY
590{
591 struct libscols_column *cl;
592 int flags = col->flags;
593
e8f945e4 594 cl = scols_table_new_column(tb, col->name, col->whint, flags | extra);
5211d5a1 595 if (cl) {
52ab46da 596 scols_column_set_json_type(cl, col->json_type);
5211d5a1
MY
597 if (col->flags & SCOLS_FL_WRAP) {
598 scols_column_set_wrapfunc(cl,
599 scols_wrapnl_chunksize,
600 scols_wrapnl_nextchunk,
601 NULL);
602 scols_column_set_safechars(cl, "\n");
603 }
604 }
52ab46da
MY
605
606 return cl;
607}
608
e8f945e4
KZ
609static struct libscols_column *add_column_by_id(struct lsfd_control *ctl,
610 int colid, int extra)
52ab46da 611{
6f7d3562
MY
612 struct libscols_column *cl;
613
52ab46da
MY
614 if (ncolumns >= ARRAY_SIZE(columns))
615 errx(EXIT_FAILURE, _("too many columns are added via filter expression"));
616
617 assert(colid < LSFD_N_COLS);
618
e8f945e4 619 cl = add_column(ctl->tb, infos + colid, extra);
52ab46da
MY
620 if (!cl)
621 err(EXIT_FAILURE, _("failed to allocate output column"));
622 columns[ncolumns++] = colid;
7b366469 623
46eca86e 624 if (colid == COL_TID)
7b366469 625 ctl->threads = 1;
7b366469 626
52ab46da
MY
627 return cl;
628}
629
faf8c94f
KZ
630static const struct file_class *stat2class(struct stat *sb)
631{
c5eb81b3
MY
632 dev_t dev;
633
faf8c94f 634 assert(sb);
a9109434 635
faf8c94f
KZ
636 switch (sb->st_mode & S_IFMT) {
637 case S_IFCHR:
638 return &cdev_class;
639 case S_IFBLK:
640 return &bdev_class;
641 case S_IFSOCK:
642 return &sock_class;
643 case S_IFIFO:
644 return &fifo_class;
645 case S_IFLNK:
faf8c94f 646 case S_IFDIR:
c5eb81b3
MY
647 return &file_class;
648 case S_IFREG:
649 dev = sb->st_dev;
650 if (major(dev) != 0)
651 return &file_class;
652
7f7b36be 653 if (is_nsfs_dev(dev))
c5eb81b3
MY
654 return &nsfs_file_class;
655
ad056988
MY
656 if (is_mqueue_dev(dev))
657 return &mqueue_file_class;
658
faf8c94f
KZ
659 return &file_class;
660 default:
661 break;
662 }
663
664 return &unkn_class;
665}
666
51b8adce
MY
667static struct file *new_file(struct proc *proc, const struct file_class *class,
668 struct stat *sb, const char *name, int association)
f9fa97d3
KZ
669{
670 struct file *file;
f9fa97d3 671
2beb17a6 672 assert(class);
f9fa97d3 673 file = xcalloc(1, class->size);
2beb17a6
MY
674 file->class = class;
675
34f3fd1a
KZ
676 file->proc = proc;
677
34f3fd1a
KZ
678 INIT_LIST_HEAD(&file->files);
679 list_add_tail(&file->files, &proc->files);
f9fa97d3 680
51b8adce
MY
681 file->association = association;
682 file->name = xstrdup(name);
683 file->stat = *sb;
684
99ce4250
KZ
685 return file;
686}
f9fa97d3 687
a125e2ee
MY
688static struct file *new_readlink_error_file(struct proc *proc, int error_no, int association)
689{
690 struct file *file;
691
692 file = xcalloc(1, readlink_error_class.size);
693 file->class = &readlink_error_class;
694
695 file->proc = proc;
696
697 INIT_LIST_HEAD(&file->files);
698 list_add_tail(&file->files, &proc->files);
699
700 file->error.syscall = "readlink";
701 file->error.number = error_no;
702 file->association = association;
703 file->name = NULL;
704
705 return file;
706}
707
708static struct file *new_stat_error_file(struct proc *proc, const char *name, int error_no, int association)
709{
710 struct file *file;
711
712 file = xcalloc(1, stat_error_class.size);
713 file->class = &stat_error_class;
714
715 file->proc = proc;
716
717 INIT_LIST_HEAD(&file->files);
718 list_add_tail(&file->files, &proc->files);
719
720 file->error.syscall = "stat";
721 file->error.number = error_no;
722 file->association = association;
723 file->name = xstrdup(name);
724
725 return file;
726}
727
82a688d0 728static struct file *copy_file(struct file *old, int new_association)
5593f1d1
KZ
729{
730 struct file *file = xcalloc(1, old->class->size);
731
732 INIT_LIST_HEAD(&file->files);
733 file->proc = old->proc;
734 list_add_tail(&file->files, &old->proc->files);
735
736 file->class = old->class;
82a688d0 737 file->association = new_association;
5593f1d1
KZ
738 file->name = xstrdup(old->name);
739 file->stat = old->stat;
740
741 return file;
742}
743
99ce4250
KZ
744static void file_init_content(struct file *file)
745{
746 if (file->class && file->class->initialize_content)
747 file->class->initialize_content(file);
f9fa97d3
KZ
748}
749
6f5e8c7c
KZ
750static void free_file(struct file *file)
751{
752 const struct file_class *class = file->class;
753
754 while (class) {
755 if (class->free_content)
756 class->free_content(file);
757 class = class->super;
758 }
759 free(file);
760}
761
f9fa97d3 762
717dfc23 763static struct proc *new_proc(pid_t pid, struct proc *leader)
128beb71
MY
764{
765 struct proc *proc = xcalloc(1, sizeof(*proc));
766
6008b460
MY
767 proc->pid = pid;
768 proc->leader = leader? leader: proc;
128beb71
MY
769 proc->command = NULL;
770
cb6f0c4a
KZ
771 INIT_LIST_HEAD(&proc->files);
772 INIT_LIST_HEAD(&proc->procs);
da901314 773 INIT_LIST_HEAD(&proc->eventpolls);
cb6f0c4a 774
f26ddd08 775 proc->kthread = 0;
128beb71
MY
776 return proc;
777}
778
6f5e8c7c 779static void free_proc(struct proc *proc)
128beb71 780{
6f5e8c7c 781 list_free(&proc->files, struct file, files, free_file);
128beb71 782
6f5e8c7c
KZ
783 free(proc->command);
784 free(proc);
128beb71
MY
785}
786
19299584
MY
787static void read_fdinfo(struct file *file, FILE *fdinfo)
788{
19299584 789 char buf[1024];
19299584
MY
790
791 while (fgets(buf, sizeof(buf), fdinfo)) {
5adbe6cf
KZ
792 const struct file_class *class;
793 char *val = strchr(buf, ':');
794
19299584
MY
795 if (!val)
796 continue;
5adbe6cf
KZ
797 *val++ = '\0'; /* terminate key */
798
799 val = (char *) skip_space(val);
800 rtrim_whitespace((unsigned char *) val);
19299584
MY
801
802 class = file->class;
803 while (class) {
804 if (class->handle_fdinfo
805 && class->handle_fdinfo(file, buf, val))
806 break;
807 class = class->super;
808 }
809 }
810}
811
174baf3b
KZ
812static struct file *collect_file_symlink(struct path_cxt *pc,
813 struct proc *proc,
814 const char *name,
0ece530d
MY
815 int assoc,
816 bool sockets_only)
128beb71 817{
174baf3b
KZ
818 char sym[PATH_MAX] = { '\0' };
819 struct stat sb;
b813e8ce 820 struct file *f, *prev;
128beb71 821
174baf3b 822 if (ul_path_readlink(pc, sym, sizeof(sym), name) < 0)
a125e2ee 823 f = new_readlink_error_file(proc, errno, assoc);
b813e8ce
KZ
824 /* The /proc/#/{fd,ns} often contains the same file (e.g. /dev/tty)
825 * more than once. Let's try to reuse the previous file if the real
826 * path is the same to save stat() call.
827 */
a125e2ee
MY
828 else if ((prev = list_last_entry(&proc->files, struct file, files))
829 && (!prev->is_error)
830 && prev->name && strcmp(prev->name, sym) == 0)
82a688d0 831 f = copy_file(prev, assoc);
a125e2ee
MY
832 else if (ul_path_stat(pc, &sb, 0, name) < 0)
833 f = new_stat_error_file(proc, sym, errno, assoc);
82a688d0 834 else {
a125e2ee 835 const struct file_class *class = stat2class(&sb);
b813e8ce 836
0ece530d 837 if (sockets_only
d2891493
MY
838 /* A nsfs file is not a socket but the nsfs file can
839 * be used as a entry point to collect information from
840 * other network namespaces. Besed on the information,
841 * various columns of sockets can be filled.
0ece530d 842 */
ccd4e142 843 && (class != &sock_class) && (class != &nsfs_file_class))
0ece530d 844 return NULL;
51b8adce 845 f = new_file(proc, class, &sb, sym, assoc);
b813e8ce 846 }
99ce4250 847
99ce4250 848 file_init_content(f);
19299584 849
a125e2ee
MY
850 if (f->is_error)
851 return f;
852
5b4b9e9a
MY
853 if (is_association(f, NS_MNT)) {
854 proc->ns_mnt = find_mnt_ns(f->stat.st_ino);
855 if (proc->ns_mnt == NULL)
856 proc->ns_mnt = add_mnt_ns(f->stat.st_ino);
857 } else if (is_association(f, NS_NET))
0ee16e43 858 load_sock_xinfo(pc, name, f->stat.st_ino);
17adf20f 859
174baf3b
KZ
860 else if (assoc >= 0) {
861 /* file-descriptor based association */
862 FILE *fdinfo;
19299584 863
174baf3b
KZ
864 if (ul_path_stat(pc, &sb, AT_SYMLINK_NOFOLLOW, name) == 0)
865 f->mode = sb.st_mode;
19299584 866
0ee16e43
MY
867 if (is_nsfs_dev(f->stat.st_dev))
868 load_sock_xinfo(pc, name, f->stat.st_ino);
869
174baf3b
KZ
870 fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%d", assoc);
871 if (fdinfo) {
872 read_fdinfo(f, fdinfo);
873 fclose(fdinfo);
874 }
875 }
99ce4250 876
19299584 877 return f;
128beb71
MY
878}
879
174baf3b
KZ
880/* read symlinks from /proc/#/fd
881 */
0ece530d
MY
882static void collect_fd_files(struct path_cxt *pc, struct proc *proc,
883 bool sockets_only)
7388e0b3 884{
174baf3b
KZ
885 DIR *sub = NULL;
886 struct dirent *d = NULL;
887 char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))];
19299584 888
174baf3b
KZ
889 while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) {
890 uint64_t num;
19299584 891
174baf3b
KZ
892 if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */
893 continue;
19299584 894
174baf3b 895 snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num);
0ece530d 896 collect_file_symlink(pc, proc, path, num, sockets_only);
174baf3b 897 }
7388e0b3
MY
898}
899
e395a038 900static void parse_maps_line(struct path_cxt *pc, char *buf, struct proc *proc)
ecfd869e 901{
6230d411
KZ
902 uint64_t start, end, offset, ino;
903 unsigned long major, minor;
904 enum association assoc = ASSOC_MEM;
905 struct stat sb;
5593f1d1 906 struct file *f, *prev;
6230d411 907 char *path, modestr[5];
5593f1d1 908 dev_t devno;
ecfd869e 909
6230d411
KZ
910 /* read rest of the map */
911 if (sscanf(buf, "%"SCNx64 /* start */
912 "-%"SCNx64 /* end */
913 " %4[^ ]" /* mode */
914 " %"SCNx64 /* offset */
d967f8e6 915 " %lx:%lx" /* maj:min */
6230d411 916 " %"SCNu64, /* inode */
ecfd869e 917
6230d411
KZ
918 &start, &end, modestr, &offset,
919 &major, &minor, &ino) != 7)
920 return;
ecfd869e 921
e395a038
MY
922 /* Skip private anonymous mappings. */
923 if (major == 0 && minor == 0 && ino == 0)
924 return;
925
5593f1d1 926 devno = makedev(major, minor);
ecfd869e 927
5593f1d1
KZ
928 if (modestr[3] == 's')
929 assoc = ASSOC_SHM;
930
931 /* The map usually contains the same file more than once, try to reuse
932 * the previous file (if devno and ino are the same) to save stat() call.
933 */
934 prev = list_last_entry(&proc->files, struct file, files);
935
a125e2ee
MY
936 if (prev && (!prev->is_error)
937 && prev->stat.st_dev == devno && prev->stat.st_ino == ino)
82a688d0
MY
938 f = copy_file(prev, -assoc);
939 else if ((path = strchr(buf, '/'))) {
e395a038 940 rtrim_whitespace((unsigned char *) path);
5593f1d1 941 if (stat(path, &sb) < 0)
e395a038
MY
942 /* If a file is mapped but deleted from the file system,
943 * "stat by the file name" may not work. In that case,
944 */
945 goto try_map_files;
51b8adce 946 f = new_file(proc, stat2class(&sb), &sb, path, -assoc);
e395a038
MY
947 } else {
948 /* As used in tcpdump, AF_PACKET socket can be mmap'ed. */
115c02fe 949 char map_file[sizeof("map_files/0000000000000000-ffffffffffffffff")];
e395a038
MY
950 char sym[PATH_MAX] = { '\0' };
951
952 try_map_files:
045cd329 953 snprintf(map_file, sizeof(map_file), "map_files/%"PRIx64"-%"PRIx64, start, end);
e395a038 954 if (ul_path_readlink(pc, sym, sizeof(sym), map_file) < 0)
a125e2ee
MY
955 f = new_readlink_error_file(proc, errno, -assoc);
956 else if (ul_path_stat(pc, &sb, 0, map_file) < 0)
957 f = new_stat_error_file(proc, sym, errno, -assoc);
958 else
959 f = new_file(proc, stat2class(&sb), &sb, sym, -assoc);
5593f1d1 960 }
ecfd869e 961
6230d411
KZ
962 if (modestr[0] == 'r')
963 f->mode |= S_IRUSR;
964 if (modestr[1] == 'w')
965 f->mode |= S_IWUSR;
966 if (modestr[2] == 'x')
967 f->mode |= S_IXUSR;
ecfd869e 968
6230d411
KZ
969 f->map_start = start;
970 f->map_end = end;
971 f->pos = offset;
ecfd869e 972
6230d411 973 file_init_content(f);
ecfd869e
MY
974}
975
6230d411 976static void collect_mem_files(struct path_cxt *pc, struct proc *proc)
7388e0b3 977{
ecfd869e 978 FILE *fp;
6230d411 979 char buf[BUFSIZ];
ecfd869e 980
6230d411
KZ
981 fp = ul_path_fopen(pc, "r", "maps");
982 if (!fp)
983 return;
ecfd869e 984
6230d411 985 while (fgets(buf, sizeof(buf), fp))
e395a038 986 parse_maps_line(pc, buf, proc);
ecfd869e 987
6230d411 988 fclose(fp);
7388e0b3
MY
989}
990
c77e52b6
KZ
991static void collect_outofbox_files(struct path_cxt *pc,
992 struct proc *proc,
f1d0b0bb 993 enum association assocs[],
c77e52b6 994 const char *names[],
0ece530d
MY
995 size_t count,
996 bool sockets_only)
a4778cb5 997{
c77e52b6 998 size_t i;
d754885a 999
c77e52b6 1000 for (i = 0; i < count; i++)
0ece530d
MY
1001 collect_file_symlink(pc, proc, names[assocs[i]], assocs[i] * -1,
1002 sockets_only);
128beb71
MY
1003}
1004
0ece530d
MY
1005static void collect_execve_file(struct path_cxt *pc, struct proc *proc,
1006 bool sockets_only)
128beb71 1007{
c77e52b6
KZ
1008 enum association assocs[] = { ASSOC_EXE };
1009 const char *names[] = {
3d4b926a
MY
1010 [ASSOC_EXE] = "exe",
1011 };
0ece530d
MY
1012 collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
1013 sockets_only);
3d4b926a
MY
1014}
1015
0ece530d
MY
1016static void collect_fs_files(struct path_cxt *pc, struct proc *proc,
1017 bool sockets_only)
3d4b926a 1018{
f8a8cc2f 1019 enum association assocs[] = { ASSOC_CWD, ASSOC_ROOT };
c77e52b6 1020 const char *names[] = {
3d4b926a 1021 [ASSOC_CWD] = "cwd",
f1d0b0bb
MY
1022 [ASSOC_ROOT] = "root",
1023 };
0ece530d
MY
1024 collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
1025 sockets_only);
14785490 1026}
f1d0b0bb 1027
a99427e9 1028static void collect_namespace_files_tophalf(struct path_cxt *pc, struct proc *proc)
14785490 1029{
c77e52b6 1030 enum association assocs[] = {
f1d0b0bb
MY
1031 ASSOC_NS_CGROUP,
1032 ASSOC_NS_IPC,
1033 ASSOC_NS_MNT,
a99427e9
MY
1034 };
1035 const char *names[] = {
1036 [ASSOC_NS_CGROUP] = "ns/cgroup",
1037 [ASSOC_NS_IPC] = "ns/ipc",
1038 [ASSOC_NS_MNT] = "ns/mnt",
1039 };
1040 collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
1041 /* Namespace information is alwasys needed. */
1042 false);
1043}
1044
1045static void collect_namespace_files_bottomhalf(struct path_cxt *pc, struct proc *proc)
1046{
1047 enum association assocs[] = {
f1d0b0bb
MY
1048 ASSOC_NS_NET,
1049 ASSOC_NS_PID,
1050 ASSOC_NS_PID4C,
1051 ASSOC_NS_TIME,
1052 ASSOC_NS_TIME4C,
1053 ASSOC_NS_USER,
1054 ASSOC_NS_UTS,
1055 };
c77e52b6 1056 const char *names[] = {
c77e52b6
KZ
1057 [ASSOC_NS_NET] = "ns/net",
1058 [ASSOC_NS_PID] = "ns/pid",
1059 [ASSOC_NS_PID4C] = "ns/pid_for_children",
1060 [ASSOC_NS_TIME] = "ns/time",
1061 [ASSOC_NS_TIME4C] = "ns/time_for_children",
1062 [ASSOC_NS_USER] = "ns/user",
1063 [ASSOC_NS_UTS] = "ns/uts",
f1d0b0bb 1064 };
0ece530d
MY
1065 collect_outofbox_files(pc, proc, assocs, names, ARRAY_SIZE(assocs),
1066 /* Namespace information is alwasys needed. */
1067 false);
14785490
MY
1068}
1069
e1a79781
MY
1070static struct mnt_namespace *new_mnt_ns(ino_t id)
1071{
1072 struct mnt_namespace *mnt_ns = xmalloc(sizeof(*mnt_ns));
1073
1074 mnt_ns->id = id;
5b4b9e9a 1075 mnt_ns->read_mountinfo = false;
e1a79781
MY
1076
1077 return mnt_ns;
1078}
1079
1080static void free_mnt_ns(void *mnt_ns)
1081{
1082 free(mnt_ns);
1083}
1084
1085static int compare_mnt_ns(const void *a, const void *b)
1086{
1087 ino_t A = (((struct mnt_namespace *)a)->id);
1088 ino_t B = (((struct mnt_namespace *)b)->id);
1089
1090 if (A < B)
1091 return -1;
1092 else if (A == B)
1093 return 0;
1094 else
1095 return 1;
1096}
1097
1098static struct mnt_namespace *find_mnt_ns(ino_t id)
1099{
1100 struct mnt_namespace key = { .id = id };
1101
1102 struct mnt_namespace **mnt_ns = tfind(&key, &mnt_namespaces, compare_mnt_ns);
1103 if (mnt_ns)
1104 return *mnt_ns;
1105 return NULL;
1106}
1107
1108static struct mnt_namespace *add_mnt_ns(ino_t id)
1109{
1110
1111 struct mnt_namespace *mnt_ns = new_mnt_ns(id);
1112 if (tsearch(mnt_ns, &mnt_namespaces, compare_mnt_ns) == NULL)
1113 errx(EXIT_FAILURE, _("failed to allocate memory"));
1114
1115 return mnt_ns;
1116}
1117
95b4a789 1118static struct nodev *new_nodev(unsigned long minor, const char *filesystem)
269c425f 1119{
95b4a789
KZ
1120 struct nodev *nodev = xcalloc(1, sizeof(*nodev));
1121
1122 INIT_LIST_HEAD(&nodev->nodevs);
1123 nodev->minor = minor;
1124 nodev->filesystem = xstrdup(filesystem);
1125
1126 return nodev;
269c425f
MY
1127}
1128
95b4a789 1129static void free_nodev(struct nodev *nodev)
269c425f 1130{
95b4a789
KZ
1131 free(nodev->filesystem);
1132 free(nodev);
1133}
1134
d5ca01c0
MY
1135void add_nodev(unsigned long minor, const char *filesystem)
1136{
1137 struct nodev *nodev = new_nodev(minor, filesystem);
1138 unsigned long slot = nodev->minor % NODEV_TABLE_SIZE;
1139
1140 list_add_tail(&nodev->nodevs, &nodev_table.tables[slot]);
1141}
1142
95b4a789
KZ
1143static void initialize_nodevs(void)
1144{
024ca9ba
MY
1145 int i;
1146
1147 for (i = 0; i < NODEV_TABLE_SIZE; i++)
95b4a789
KZ
1148 INIT_LIST_HEAD(&nodev_table.tables[i]);
1149}
1150
1151static void finalize_nodevs(void)
1152{
024ca9ba
MY
1153 int i;
1154
1155 for (i = 0; i < NODEV_TABLE_SIZE; i++)
95b4a789 1156 list_free(&nodev_table.tables[i], struct nodev, nodevs, free_nodev);
a7f70ce1 1157
e1a79781 1158 tdestroy(mnt_namespaces, free_mnt_ns);
95b4a789
KZ
1159}
1160
1161const char *get_nodev_filesystem(unsigned long minor)
1162{
1163 struct list_head *n;
1164 int slot = minor % NODEV_TABLE_SIZE;
1165
1166 list_for_each (n, &nodev_table.tables[slot]) {
1167 struct nodev *nodev = list_entry(n, struct nodev, nodevs);
1168 if (nodev->minor == minor)
1169 return nodev->filesystem;
1170 }
1171 return NULL;
1172}
1173
d3553b60
MY
1174static void process_mountinfo_entry(unsigned long major, unsigned long minor,
1175 const char *filesystem)
1176{
1177 if (major != 0)
1178 return;
1179 if (get_nodev_filesystem(minor))
1180 return;
1181
1182 add_nodev(minor, filesystem);
1183}
1184
2d875b3c 1185static void read_mountinfo(FILE *mountinfo)
95b4a789
KZ
1186{
1187 /* This can be very long. A line in mountinfo can have more than 3
1188 * paths. */
269c425f 1189 char line[PATH_MAX * 3 + 256];
95b4a789 1190
2d875b3c 1191 while (fgets(line, sizeof(line), mountinfo)) {
269c425f
MY
1192 unsigned long major, minor;
1193 char filesystem[256];
1194
1195 /* 23 61 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:2 - sysfs sysfs rw,seclabel */
80b3c8cc 1196 if(sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s %*[^-] - %255s %*[^\n]",
269c425f
MY
1197 &major, &minor, filesystem) != 3)
1198 /* 1600 1458 0:55 / / rw,nodev,relatime - overlay overlay rw,context="s... */
80b3c8cc 1199 if (sscanf(line, "%*d %*d %lu:%lu %*s %*s %*s - %255s %*[^\n]",
269c425f
MY
1200 &major, &minor, filesystem) != 3)
1201 continue;
1202
d3553b60 1203 process_mountinfo_entry(major, minor, filesystem);
269c425f
MY
1204 }
1205}
1206
feb48e88
MY
1207static void initialize_ipc_table(void)
1208{
1209 for (int i = 0; i < IPC_TABLE_SIZE; i++)
1210 INIT_LIST_HEAD(ipc_table.tables + i);
1211}
1212
1213static void free_ipc(struct ipc *ipc)
1214{
1215 if (ipc->class->free)
1216 ipc->class->free(ipc);
679b347e 1217 free(ipc);
feb48e88
MY
1218}
1219
1220static void finalize_ipc_table(void)
1221{
1222 for (int i = 0; i < IPC_TABLE_SIZE; i++)
679b347e 1223 list_free(&ipc_table.tables[i], struct ipc, ipcs, free_ipc);
feb48e88
MY
1224}
1225
79ec864e
MY
1226struct ipc *new_ipc(const struct ipc_class *class)
1227{
1228 struct ipc *ipc = xcalloc(1, class->size);
1229 ipc->class = class;
1230 INIT_LIST_HEAD(&ipc->endpoints);
1231 INIT_LIST_HEAD(&ipc->ipcs);
1232 return ipc;
1233}
1234
feb48e88
MY
1235struct ipc *get_ipc(struct file *file)
1236{
1237 int slot;
1238 struct list_head *e;
54a06438 1239 const struct ipc_class *ipc_class;
feb48e88
MY
1240
1241 if (!file->class->get_ipc_class)
1242 return NULL;
1243
1244 ipc_class = file->class->get_ipc_class(file);
1245 if (!ipc_class)
1246 return NULL;
1247
1248 slot = ipc_class->get_hash(file) % IPC_TABLE_SIZE;
1249 list_for_each (e, &ipc_table.tables[slot]) {
1250 struct ipc *ipc = list_entry(e, struct ipc, ipcs);
1251 if (ipc->class != ipc_class)
1252 continue;
1253 if (ipc_class->is_suitable_ipc(ipc, file))
1254 return ipc;
1255 }
1256 return NULL;
1257}
1258
1259void add_ipc(struct ipc *ipc, unsigned int hash)
1260{
1261 int slot = hash % IPC_TABLE_SIZE;
1262 list_add(&ipc->ipcs, &ipc_table.tables[slot]);
1263}
1264
f9263edc
MY
1265void init_endpoint(struct ipc_endpoint *endpoint)
1266{
1267 INIT_LIST_HEAD(&endpoint->endpoints);
1268}
1269
1c2ab17e
MY
1270void add_endpoint(struct ipc_endpoint *endpoint, struct ipc *ipc)
1271{
1272 endpoint->ipc = ipc;
1273 list_add(&endpoint->endpoints, &ipc->endpoints);
1274}
1275
46eca86e 1276
128beb71
MY
1277static void fill_column(struct proc *proc,
1278 struct file *file,
1279 struct libscols_line *ln,
1280 int column_id,
1281 size_t column_index)
1282{
1283 const struct file_class *class = file->class;
1284
1285 while (class) {
1286 if (class->fill_column
1287 && class->fill_column(proc, file, ln,
1288 column_id, column_index))
1289 break;
1290 class = class->super;
1291 }
1292}
1293
46eca86e
KZ
1294static int filter_filler_cb(
1295 struct libscols_filter *fltr __attribute__((__unused__)),
1296 struct libscols_line *ln,
1297 size_t colnum,
1298 void *userdata)
1299{
1300 struct filler_data *fid = (struct filler_data *) userdata;
1301
1302 fill_column(fid->proc, fid->file, ln, get_column_id(colnum), colnum);
1303 return 0;
1304}
1305
d18b7b75 1306static void convert_file(struct proc *proc,
128beb71
MY
1307 struct file *file,
1308 struct libscols_line *ln)
1309
1310{
024ca9ba
MY
1311 size_t i;
1312
46eca86e
KZ
1313 for (i = 0; i < ncolumns; i++) {
1314 if (scols_line_is_filled(ln, i))
1315 continue;
128beb71 1316 fill_column(proc, file, ln, get_column_id(i), i);
46eca86e 1317 }
128beb71
MY
1318}
1319
1320static void convert(struct list_head *procs, struct lsfd_control *ctl)
1321{
1322 struct list_head *p;
1323
1324 list_for_each (p, procs) {
1325 struct proc *proc = list_entry(p, struct proc, procs);
1326 struct list_head *f;
1327
1328 list_for_each (f, &proc->files) {
1329 struct file *file = list_entry(f, struct file, files);
1330 struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL);
46eca86e 1331 struct libscols_filter **ct_fltr = NULL;
7d8c81d7 1332
128beb71
MY
1333 if (!ln)
1334 err(EXIT_FAILURE, _("failed to allocate output line"));
46eca86e
KZ
1335 if (ctl->filter) {
1336 int status = 0;
1337 struct filler_data fid = {
1338 .proc = proc,
1339 .file = file
1340 };
1341
1342 scols_filter_set_filler_cb(ctl->filter,
1343 filter_filler_cb, (void *) &fid);
1344 if (scols_line_apply_filter(ln, ctl->filter, &status))
1345 err(EXIT_FAILURE, _("failed to apply filter"));
1346 if (status == 0) {
1347 scols_table_remove_line(ctl->tb, ln);
1348 continue;
1349 }
1350 }
d18b7b75
KZ
1351
1352 convert_file(proc, file, ln);
52ab46da 1353
46eca86e 1354 if (!ctl->ct_filters)
7d8c81d7
MY
1355 continue;
1356
46eca86e
KZ
1357 for (ct_fltr = ctl->ct_filters; *ct_fltr; ct_fltr++)
1358 scols_line_apply_filter(ln, *ct_fltr, NULL);
128beb71
MY
1359 }
1360 }
1361}
1362
1363static void delete(struct list_head *procs, struct lsfd_control *ctl)
1364{
7dc7b6f4
MY
1365 struct list_head *p;
1366
1367 list_for_each (p, procs) {
1368 struct proc *proc = list_entry(p, struct proc, procs);
1369 tdelete(proc, &proc_tree, proc_tree_compare);
1370 }
128beb71
MY
1371 list_free(procs, struct proc, procs, free_proc);
1372
1373 scols_unref_table(ctl->tb);
46eca86e
KZ
1374 scols_unref_filter(ctl->filter);
1375
1376 if (ctl->ct_filters) {
1377 struct libscols_filter **ct_fltr;
1378 for (ct_fltr = ctl->ct_filters; *ct_fltr; ct_fltr++)
1379 scols_unref_filter(*ct_fltr);
1380 free(ctl->ct_filters);
7d8c81d7 1381 }
128beb71
MY
1382}
1383
1384static void emit(struct lsfd_control *ctl)
1385{
1386 scols_print_table(ctl->tb);
1387}
1388
128beb71 1389
4230e09f
MY
1390static void initialize_class(const struct file_class *class)
1391{
1392 if (class->initialize_class)
1393 class->initialize_class();
1394}
1395
1396static void initialize_classes(void)
1397{
28bce81a 1398 initialize_class(&abst_class);
4230e09f
MY
1399 initialize_class(&file_class);
1400 initialize_class(&cdev_class);
1401 initialize_class(&bdev_class);
1402 initialize_class(&sock_class);
1403 initialize_class(&unkn_class);
1404}
1405
1406static void finalize_class(const struct file_class *class)
1407{
1408 if (class->finalize_class)
1409 class->finalize_class();
1410}
1411
1412static void finalize_classes(void)
1413{
1414 finalize_class(&file_class);
1415 finalize_class(&cdev_class);
1416 finalize_class(&bdev_class);
1417 finalize_class(&sock_class);
1418 finalize_class(&unkn_class);
1419}
1420
3c007400
MY
1421static struct devdrv *new_devdrv(unsigned long major, const char *name)
1422{
1423 struct devdrv *devdrv = xcalloc(1, sizeof(*devdrv));
1424
1425 INIT_LIST_HEAD(&devdrv->devdrvs);
1426
1427 devdrv->major = major;
1428 devdrv->name = xstrdup(name);
1429
1430 return devdrv;
1431}
1432
1433static void free_devdrv(struct devdrv *devdrv)
1434{
1435 free(devdrv->name);
1436 free(devdrv);
1437}
1438
00726e78
MY
1439#define READ_DEVICES_LINE_LEN 256
1440static struct devdrv *read_devdrv(const char *line)
3c007400
MY
1441{
1442 unsigned long major;
00726e78 1443 char name[READ_DEVICES_LINE_LEN];
3c007400 1444
00726e78
MY
1445 if (sscanf(line, "%lu %s", &major, name) != 2)
1446 return NULL;
1447
1448 return new_devdrv(major, name);
1449}
3c007400 1450
00726e78
MY
1451static void read_devices(struct list_head *chrdrvs_list,
1452 struct list_head *blkdrvs_list, FILE *devices_fp)
1453{
1454 char line[READ_DEVICES_LINE_LEN];
1455
1456 /* Skip to the line "Character devices:". */
1457 while (fgets(line, sizeof(line), devices_fp)) {
3c007400 1458 if (line[0] == 'C')
3c007400 1459 break;
00726e78
MY
1460 continue;
1461 }
a9109434 1462
00726e78
MY
1463 while (fgets(line, sizeof(line), devices_fp)) {
1464 /* Find the blank line before "Block devices:" line. */
1465 if (line[0] == '\n')
1466 break;
1467
1468 /* Read the character device drivers */
1469 struct devdrv *devdrv = read_devdrv(line);
1470 if (devdrv)
1471 list_add_tail(&devdrv->devdrvs, chrdrvs_list);
1472 }
1473
1474 /* Skip to the line "Block devices:". */
1475 while (fgets(line, sizeof(line), devices_fp)) {
1476 if (line[0] == 'B')
1477 break;
1478 continue;
1479 }
1480
1481 /* Read the block device drivers */
1482 while (fgets(line, sizeof(line), devices_fp)) {
1483 struct devdrv *devdrv = read_devdrv(line);
1484 if (devdrv)
1485 list_add_tail(&devdrv->devdrvs, blkdrvs_list);
3c007400
MY
1486 }
1487}
1488
1489static void initialize_devdrvs(void)
1490{
1491 FILE *devices_fp;
1492
1493 INIT_LIST_HEAD(&chrdrvs);
00726e78 1494 INIT_LIST_HEAD(&blkdrvs);
3c007400
MY
1495
1496 devices_fp = fopen("/proc/devices", "r");
1497 if (devices_fp) {
00726e78 1498 read_devices(&chrdrvs, &blkdrvs, devices_fp);
3c007400
MY
1499 fclose(devices_fp);
1500 }
1501}
1502
1503static void finalize_devdrvs(void)
1504{
00726e78 1505 list_free(&blkdrvs, struct devdrv, devdrvs, free_devdrv);
3c007400
MY
1506 list_free(&chrdrvs, struct devdrv, devdrvs, free_devdrv);
1507}
1508
00726e78 1509static const char *get_devdrv(struct list_head *devdrvs_list, unsigned long major)
3c007400
MY
1510{
1511 struct list_head *c;
00726e78 1512 list_for_each(c, devdrvs_list) {
3c007400
MY
1513 struct devdrv *devdrv = list_entry(c, struct devdrv, devdrvs);
1514 if (devdrv->major == major)
1515 return devdrv->name;
1516 }
1517 return NULL;
1518}
a9109434 1519
00726e78
MY
1520const char *get_chrdrv(unsigned long major)
1521{
1522 return get_devdrv(&chrdrvs, major);
1523}
1524
1525const char *get_blkdrv(unsigned long major)
1526{
1527 return get_devdrv(&blkdrvs, major);
1528}
1529
a9109434
KZ
1530struct name_manager *new_name_manager(void)
1531{
1532 struct name_manager *nm = xcalloc(1, sizeof(struct name_manager));
1533
1534 nm->cache = new_idcache();
1535 if (!nm->cache)
1536 err(EXIT_FAILURE, _("failed to allocate an idcache"));
1537
1538 nm->next_id = 1; /* 0 is never issued as id. */
1539 return nm;
1540}
1541
1542void free_name_manager(struct name_manager *nm)
1543{
1544 free_idcache(nm->cache);
1545 free(nm);
1546}
1547
1548const char *get_name(struct name_manager *nm, unsigned long id)
1549{
1550 struct identry *e;
1551
1552 e = get_id(nm->cache, id);
1553
1554 return e? e->name: NULL;
1555}
1556
1557unsigned long add_name(struct name_manager *nm, const char *name)
1558{
1559 struct identry *e = NULL, *tmp;
1560
1561 for (tmp = nm->cache->ent; tmp; tmp = tmp->next) {
1562 if (strcmp(tmp->name, name) == 0) {
1563 e = tmp;
1564 break;
1565 }
1566 }
1567
1568 if (e)
1569 return e->id;
1570
1571 e = xmalloc(sizeof(struct identry));
1572 e->name = xstrdup(name);
1573 e->id = nm->next_id++;
1574 e->next = nm->cache->ent;
1575 nm->cache->ent = e;
1576
1577 return e->id;
1578}
1579
174933b4
MY
1580static void walk_threads(struct lsfd_control *ctl, struct path_cxt *pc,
1581 pid_t pid, struct proc *proc,
1582 void (*cb)(struct lsfd_control *, struct path_cxt *,
1583 pid_t, struct proc *))
1584{
1585 DIR *sub = NULL;
1586 pid_t tid = 0;
1587
1588 while (procfs_process_next_tid(pc, &sub, &tid) == 0) {
1589 if (tid == pid)
1590 continue;
1591 (*cb)(ctl, pc, tid, proc);
1592 }
1593}
1594
adf10f7d
MY
1595static int pollfdcmp(const void *a, const void *b)
1596{
1597 const struct pollfd *apfd = a, *bpfd = b;
1598
1599 return apfd->fd - bpfd->fd;
1600}
1601
1602static void mark_poll_fds_as_multiplexed(char *buf,
1603 pid_t pid, struct proc *proc)
1604{
1605 long fds;
1606 long nfds;
1607
1608 struct iovec local;
1609 struct iovec remote;
1610 ssize_t n;
1611
1612 struct list_head *f;
1613
1614 if (sscanf(buf, "%lx %lx", &fds, &nfds) != 2)
1615 return;
1616
1617 if (nfds == 0)
1618 return;
1619
1620 local.iov_len = sizeof(struct pollfd) * nfds;
1621 local.iov_base = xmalloc(local.iov_len);
1622 remote.iov_len = local.iov_len;
1623 remote.iov_base = (void *)fds;
1624
1625 n = process_vm_readv(pid, &local, 1, &remote, 1, 0);
1626 if (n < 0 || ((size_t)n) != local.iov_len)
1627 goto out;
1628
1629 qsort(local.iov_base, nfds, sizeof(struct pollfd), pollfdcmp);
1630
1631 list_for_each (f, &proc->files) {
1632 struct file *file = list_entry(f, struct file, files);
1633 if (is_opened_file(file) && !file->multiplexed) {
1634 int fd = file->association;
1635 if (bsearch(&(struct pollfd){.fd = fd,}, local.iov_base,
1636 nfds, sizeof(struct pollfd), pollfdcmp))
1637 file->multiplexed = 1;
1638 }
1639 }
1640
1641 out:
7ce88537 1642 free(local.iov_base);
adf10f7d
MY
1643}
1644
036e5886
MY
1645static void mark_select_fds_as_multiplexed(char *buf,
1646 pid_t pid, struct proc *proc)
1647{
1648 long nfds;
1649 long fds[3];
1650
1651 struct iovec local[3];
1652 fd_set local_set[3];
1653 struct iovec remote[3];
1654 ssize_t n;
1655 ssize_t expected_n = 0;
1656
1657 struct list_head *f;
1658
1659 if (sscanf(buf, "%lx %lx %lx %lx", &nfds, fds + 0, fds + 1, fds + 2) != 4)
1660 return;
1661
1662 if (nfds == 0)
1663 return;
1664
1665 for (int i = 0; i < 3; i++) {
1666 /* If the remote address for the fd_set is 0x0, no set is tehre. */
1667 remote[i].iov_len = local[i].iov_len = fds[i]? sizeof(local_set[i]): 0;
1668 expected_n += (ssize_t)local[i].iov_len;
1669 local[i].iov_base = local_set + i;
1670 remote[i].iov_base = (void *)(fds[i]);
1671 }
1672
1673 n = process_vm_readv(pid, local, 3, remote, 3, 0);
1674 if (n < 0 || n != expected_n)
1675 return;
1676
1677 list_for_each (f, &proc->files) {
1678 struct file *file = list_entry(f, struct file, files);
1679 if (is_opened_file(file) && !file->multiplexed) {
1680 int fd = file->association;
1681 if (nfds <= fd)
1682 continue;
1683 if ((fds[0] && FD_ISSET(fd, (fd_set *)local[0].iov_base))
1684 || (fds[1] && FD_ISSET(fd, (fd_set *)local[1].iov_base))
1685 || (fds[2] && FD_ISSET(fd, (fd_set *)local[2].iov_base)))
1686 file->multiplexed = 1;
1687 }
1688 }
1689}
1690
adf10f7d
MY
1691static void parse_proc_syscall(struct lsfd_control *ctl __attribute__((__unused__)),
1692 struct path_cxt *pc, pid_t pid, struct proc *proc)
1693{
1694 char buf[BUFSIZ];
1695 char *ptr = NULL;
1696 long scn;
1697
1698 if (procfs_process_get_syscall(pc, buf, sizeof(buf)) <= 0)
1699 return;
1700
1701 errno = 0;
1702 scn = strtol(buf, &ptr, 10);
1703 if (errno)
1704 return;
1705 if (scn < 0)
1706 return;
1707
1708 switch (scn) {
d967f8e6 1709#ifdef SYS_poll
adf10f7d
MY
1710 case SYS_poll:
1711 mark_poll_fds_as_multiplexed(ptr, pid, proc);
1712 break;
1713#endif
d967f8e6 1714#ifdef SYS_ppoll
adf10f7d
MY
1715 case SYS_ppoll:
1716 mark_poll_fds_as_multiplexed(ptr, pid, proc);
1717 break;
1718#endif
d967f8e6 1719#ifdef SYS_ppoll_time64
adf10f7d
MY
1720 case SYS_ppoll_time64:
1721 mark_poll_fds_as_multiplexed(ptr, pid, proc);
1722 break;
1723#endif
036e5886
MY
1724
1725#ifdef SYS_select
1726 case SYS_select:
1727 mark_select_fds_as_multiplexed(ptr, pid, proc);
1728 break;
1729#endif
1730#ifdef SYS_pselect6
1731 case SYS_pselect6:
1732 mark_select_fds_as_multiplexed(ptr, pid, proc);
1733 break;
1734#endif
d967f8e6 1735#ifdef SYS_pselect6_time64
036e5886
MY
1736 case SYS_pselect6_time64:
1737 mark_select_fds_as_multiplexed(ptr, pid, proc);
1738 break;
1739#endif
adf10f7d
MY
1740 }
1741}
1742
cb6f0c4a
KZ
1743static void read_process(struct lsfd_control *ctl, struct path_cxt *pc,
1744 pid_t pid, struct proc *leader)
1745{
1746 char buf[BUFSIZ];
1747 struct proc *proc;
cb6f0c4a
KZ
1748
1749 if (procfs_process_init_path(pc, pid) != 0)
1750 return;
1751
717dfc23 1752 proc = new_proc(pid, leader);
c77e52b6 1753 proc->command = procfs_process_get_cmdname(pc, buf, sizeof(buf)) > 0 ?
cb6f0c4a 1754 xstrdup(buf) : xstrdup(_("(unknown)"));
35186bf0 1755 procfs_process_get_uid(pc, &proc->uid);
cb6f0c4a 1756
f26ddd08
MY
1757 if (procfs_process_get_stat(pc, buf, sizeof(buf)) > 0) {
1758 char *p;
1759 unsigned int flags;
1760 char *pat = NULL;
1761
1762 /* See proc(5) about the column in the line. */
1763 xstrappend(&pat, "%*d (");
1764 for (p = proc->command; *p != '\0'; p++) {
1765 if (*p == '%')
1766 xstrappend(&pat, "%%");
1767 else
1768 xstrputc(&pat, *p);
1769 }
1770 xstrappend(&pat, ") %*c %*d %*d %*d %*d %*d %u %*[^\n]");
1771 if (sscanf(buf, pat, &flags) == 1)
1772 proc->kthread = !!(flags & PF_KTHREAD);
1773 free(pat);
1774 }
c33da15e
MY
1775 if (proc->kthread && !ctl->threads) {
1776 free_proc(proc);
1777 goto out;
1778 }
f26ddd08 1779
0ece530d 1780 collect_execve_file(pc, proc, ctl->sockets_only);
007f02ec 1781
cb6f0c4a
KZ
1782 if (proc->pid == proc->leader->pid
1783 || kcmp(proc->leader->pid, proc->pid, KCMP_FS, 0, 0) != 0)
0ece530d 1784 collect_fs_files(pc, proc, ctl->sockets_only);
cb6f0c4a 1785
a99427e9
MY
1786 /* Reading /proc/$pid/mountinfo is expensive.
1787 * mnt_namespaces is a table for avoiding reading mountinfo files
1788 * for an identical mnt namespace.
1789 *
1790 * After reading a mountinfo file for a mnt namespace, we store $mnt_id
1791 * identifying the mnt namespace to mnt_namespaces.
1792 *
1793 * Before reading a mountinfo, we look up the mnt_namespaces with $mnt_id
1794 * as a key. If we find the key, we can skip the reading.
1795 *
1796 * To utilize mnt_namespaces, we need $mnt_id.
1797 * So we read /proc/$pid/ns/mnt here. However, we should not read
1798 * /proc/$pid/ns/net here. When reading /proc/$pid/ns/net, we need
1799 * the information about backing device of "nsfs" file system.
1800 * The information is available in a mountinfo file.
1801 */
1802
5b4b9e9a
MY
1803 /* 1/3. Read /proc/$pid/ns/mnt */
1804 if (proc->ns_mnt == NULL)
a99427e9
MY
1805 collect_namespace_files_tophalf(pc, proc);
1806
5b4b9e9a
MY
1807 /* 2/3. read /proc/$pid/mountinfo unless we have read it already.
1808 * The backing device for "nsfs" is solved here.
1809 */
1810 if (proc->ns_mnt == NULL || !proc->ns_mnt->read_mountinfo) {
2d875b3c
MY
1811 FILE *mountinfo = ul_path_fopen(pc, "r", "mountinfo");
1812 if (mountinfo) {
1813 read_mountinfo(mountinfo);
a7f70ce1 1814 if (proc->ns_mnt)
5b4b9e9a 1815 proc->ns_mnt->read_mountinfo = true;
2d875b3c 1816 fclose(mountinfo);
a7f70ce1 1817 }
cb6f0c4a
KZ
1818 }
1819
5b4b9e9a
MY
1820 /* 3/3. read /proc/$pid/ns/{the other namespaces including net}
1821 * When reading the information about the net namespace,
1822 * backing device for "nsfs" must be solved.
1823 */
a99427e9 1824 collect_namespace_files_bottomhalf(pc, proc);
fba9898f 1825
cb6f0c4a 1826 /* If kcmp is not available,
b7194a50 1827 * there is no way to know whether threads share resources.
cb6f0c4a
KZ
1828 * In such cases, we must pay the costs: call collect_mem_files()
1829 * and collect_fd_files().
1830 */
0ece530d
MY
1831 if ((!ctl->sockets_only)
1832 && (proc->pid == proc->leader->pid
1833 || kcmp(proc->leader->pid, proc->pid, KCMP_VM, 0, 0) != 0))
6230d411 1834 collect_mem_files(pc, proc);
cb6f0c4a
KZ
1835
1836 if (proc->pid == proc->leader->pid
1837 || kcmp(proc->leader->pid, proc->pid, KCMP_FILES, 0, 0) != 0)
0ece530d 1838 collect_fd_files(pc, proc, ctl->sockets_only);
cb6f0c4a
KZ
1839
1840 list_add_tail(&proc->procs, &ctl->procs);
7dc7b6f4
MY
1841 if (tsearch(proc, &proc_tree, proc_tree_compare) == NULL)
1842 errx(EXIT_FAILURE, _("failed to allocate memory"));
cb6f0c4a 1843
adf10f7d
MY
1844 if (ctl->show_xmode)
1845 parse_proc_syscall(ctl, pc, pid, proc);
1846
cb6f0c4a
KZ
1847 /* The tasks collecting overwrites @pc by /proc/<task-pid>/. Keep it as
1848 * the last path based operation in read_process()
1849 */
174933b4
MY
1850 if (ctl->threads && leader == NULL)
1851 walk_threads(ctl, pc, pid, proc, read_process);
adf10f7d
MY
1852 else if (ctl->show_xmode)
1853 walk_threads(ctl, pc, pid, proc, parse_proc_syscall);
cb6f0c4a 1854
c33da15e 1855 out:
cb6f0c4a 1856 /* Let's be careful with number of open files */
d967f8e6 1857 ul_path_close_dirfd(pc);
cb6f0c4a
KZ
1858}
1859
c647ebc2 1860static void parse_pids(const char *str, pid_t **pids, int *count)
4cfabab2
MY
1861{
1862 long v;
1863 char *next = NULL;
1864
4cfabab2
MY
1865 if (*str == '\0')
1866 return;
1867
1868 errno = 0;
1869 v = strtol(str, &next, 10);
1870 if (errno)
1871 err(EXIT_FAILURE, _("unexpected value for pid specification: %s"), str);
1872 if (next == str)
1873 errx(EXIT_FAILURE, _("garbage at the end of pid specification: %s"), str);
1874 if (v < 0)
1875 errx(EXIT_FAILURE, _("out of range value for pid specification: %ld"), v);
c647ebc2
MY
1876
1877 (*count)++;
64d6d400 1878 *pids = xreallocarray(*pids, *count, sizeof(**pids));
c647ebc2 1879 (*pids)[*count - 1] = (pid_t)v;
4cfabab2
MY
1880
1881 while (next && *next != '\0'
1882 && (isspace((unsigned char)*next) || *next == ','))
1883 next++;
1884 if (*next != '\0')
c647ebc2 1885 parse_pids(next, pids, count);
4cfabab2
MY
1886}
1887
1888static int pidcmp(const void *a, const void *b)
1889{
1890 pid_t pa = *(pid_t *)a;
1891 pid_t pb = *(pid_t *)b;
1892
1893 if (pa < pb)
1894 return -1;
1895 else if (pa == pb)
1896 return 0;
1897 else
1898 return 1;
1899}
1900
1901static void sort_pids(pid_t pids[], const int count)
1902{
1903 qsort(pids, count, sizeof(pid_t), pidcmp);
1904}
1905
1906static bool member_pids(const pid_t pid, const pid_t pids[], const int count)
1907{
1908 return bsearch(&pid, pids, count, sizeof(pid_t), pidcmp)? true: false;
1909}
1910
1911static void collect_processes(struct lsfd_control *ctl, const pid_t pids[], int n_pids)
cb6f0c4a
KZ
1912{
1913 DIR *dir;
1914 struct dirent *d;
1915 struct path_cxt *pc = NULL;
1916
1917 pc = ul_new_path(NULL);
1918 if (!pc)
1919 err(EXIT_FAILURE, _("failed to alloc procfs handler"));
1920
1921 dir = opendir(_PATH_PROC);
1922 if (!dir)
1923 err(EXIT_FAILURE, _("failed to open /proc"));
1924
1925 while ((d = readdir(dir))) {
1926 pid_t pid;
1927
1928 if (procfs_dirent_get_pid(d, &pid) != 0)
1929 continue;
4cfabab2
MY
1930 if (n_pids == 0 || member_pids(pid, pids, n_pids))
1931 read_process(ctl, pc, pid, 0);
cb6f0c4a
KZ
1932 }
1933
1934 closedir(dir);
1935 ul_unref_path(pc);
1936}
1937
0cd21cf6
MY
1938static void __attribute__((__noreturn__)) list_colunms(const char *table_name,
1939 FILE *out,
1940 int raw,
1941 int json)
e0cdf606 1942{
0cd21cf6
MY
1943 struct libscols_table *col_tb = xcolumn_list_table_new(table_name, out, raw, json);
1944
e0cdf606 1945 for (size_t i = 0; i < ARRAY_SIZE(infos); i++)
0cd21cf6
MY
1946 xcolumn_list_table_append_line(col_tb, infos[i].name,
1947 infos[i].json_type, "<boolean>",
1948 _(infos[i].help));
1949
1950 scols_print_table(col_tb);
1951 scols_unref_table(col_tb);
1952
e0cdf606
MY
1953 exit(EXIT_SUCCESS);
1954}
1955
9d1714a3
MY
1956static void print_columns(FILE *out, const char *prefix, const int cols[], size_t n_cols)
1957{
1958 fprintf(out, "%15s: ", prefix);
1959 for (size_t i = 0; i < n_cols; i++) {
1960 if (i)
1961 fputc(',', out);
1962 fputs(infos[cols[i]].name, out);
1963 }
1964 fputc('\n', out);
1965}
1966
a9109434
KZ
1967static void __attribute__((__noreturn__)) usage(void)
1968{
1969 FILE *out = stdout;
a9109434
KZ
1970
1971 fputs(USAGE_HEADER, out);
1972 fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
1973
1974 fputs(USAGE_OPTIONS, out);
d5f04129
MY
1975 fputs(_(" -l, --threads list in threads level\n"), out);
1976 fputs(_(" -J, --json use JSON output format\n"), out);
1977 fputs(_(" -n, --noheadings don't print headings\n"), out);
f5a81caf 1978 fputs(_(" -o, --output <list> output columns (see --list-columns)\n"), out);
d5f04129
MY
1979 fputs(_(" -r, --raw use raw output format\n"), out);
1980 fputs(_(" -u, --notruncate don't truncate text in columns\n"), out);
1981 fputs(_(" -p, --pid <pid(s)> collect information only specified processes\n"), out);
1982 fputs(_(" -i[4|6], --inet[=4|=6] list only IPv4 and/or IPv6 sockets\n"), out);
1983 fputs(_(" -Q, --filter <expr> apply display filter\n"), out);
1984 fputs(_(" --debug-filter dump the internal data structure of filter and exit\n"), out);
1985 fputs(_(" -C, --counter <name>:<expr> define custom counter for --summary output\n"), out);
1986 fputs(_(" --dump-counters dump counter definitions\n"), out);
1987 fputs(_(" --summary[=<when>] print summary information (only, append, or never)\n"), out);
bd38c759 1988 fputs(_(" --_drop-privilege (testing purpose) do setuid(1) just after starting\n"), out);
a9109434
KZ
1989
1990 fputs(USAGE_SEPARATOR, out);
e0cdf606 1991 fputs(_(" -H, --list-columns list the available columns\n"), out);
4916d45e 1992 fprintf(out, USAGE_HELP_OPTIONS(30));
a9109434 1993
9d1714a3
MY
1994 fputs(USAGE_DEFAULT_COLUMNS, out);
1995 print_columns(out, _("Default"), default_columns, ARRAY_SIZE(default_columns));
1996 print_columns(out, _("With --threads"), default_threads_columns, ARRAY_SIZE(default_threads_columns));
1997
4916d45e 1998 fprintf(out, USAGE_MAN_TAIL("lsfd(1)"));
a9109434
KZ
1999
2000 exit(EXIT_SUCCESS);
2001}
2002
52ab46da
MY
2003static void append_filter_expr(char **a, const char *b, bool and)
2004{
2005 if (*a == NULL) {
2006 *a = xstrdup(b);
2007 return;
2008 }
2009
2010 char *tmp = *a;
2011 *a = NULL;
2012
2013 xstrappend(a, "(");
2014 xstrappend(a, tmp);
2015 xstrappend(a, ")");
2016 if (and)
2017 xstrappend(a, "and(");
2018 else
2019 xstrappend(a, "or(");
2020 xstrappend(a, b);
2021 xstrappend(a, ")");
53bcec4b
KZ
2022
2023 free(tmp);
52ab46da
MY
2024}
2025
46eca86e 2026static struct libscols_filter *new_filter(const char *expr, bool debug, struct lsfd_control *ctl)
4bacc84a 2027{
46eca86e
KZ
2028 struct libscols_filter *f;
2029 struct libscols_iter *itr;
2030 int nerrs = 0;
2031 const char *name = NULL;
4bacc84a 2032
46eca86e
KZ
2033 f = scols_new_filter(NULL);
2034 if (!f)
2035 err(EXIT_FAILURE, _("failed to allocate filter"));
2036 if (expr && scols_filter_parse_string(f, expr) != 0)
2037 errx(EXIT_FAILURE, _("failed to parse \"%s\": %s"), expr,
2038 scols_filter_get_errmsg(f));
2039
2040 itr = scols_new_iter(SCOLS_ITER_FORWARD);
2041 if (!itr)
2042 err(EXIT_FAILURE, _("failed to allocate iterator"));
2043
2044 while (scols_filter_next_holder(f, itr, &name, 0) == 0) {
2045 struct libscols_column *col = scols_table_get_column_by_name(ctl->tb, name);
2046
2047 if (!col) {
2048 int id = column_name_to_id(name, strlen(name));
2049 if (id >= 0)
e8f945e4 2050 col = add_column_by_id(ctl, id, SCOLS_FL_HIDDEN);
46eca86e
KZ
2051 if (!col) {
2052 nerrs++; /* report all unknown columns */
2053 continue;
2054 }
2055 }
2056 scols_filter_assign_column(f, itr, name, col);
4bacc84a
MY
2057 }
2058
46eca86e
KZ
2059 scols_free_iter(itr);
2060
2061 if (debug)
2062 scols_dump_filter(f, stdout);
2063 if (nerrs)
2064 exit(EXIT_FAILURE);
2065 if (debug)
2066 exit(EXIT_SUCCESS);
2067
2068 return f;
4bacc84a
MY
2069}
2070
7d8c81d7
MY
2071static struct counter_spec *new_counter_spec(const char *spec_str)
2072{
2073 char *sep;
2074 struct counter_spec *spec;
2075
2076 if (spec_str[0] == '\0')
2077 errx(EXIT_FAILURE,
2078 _("too short counter specification: -C/--counter %s"),
2079 spec_str);
2080 if (spec_str[0] == ':')
2081 errx(EXIT_FAILURE,
2082 _("no name for counter: -C/--counter %s"),
2083 spec_str);
2084
2085 sep = strchr(spec_str, ':');
2086 if (sep == NULL)
2087 errx(EXIT_FAILURE,
2088 _("no name for counter: -C/--counter %s"),
2089 spec_str);
2090 if (sep[1] == '\0')
2091 errx(EXIT_FAILURE,
0b77077c 2092 _("empty counter expression given: -C/--counter %s"),
7d8c81d7
MY
2093 spec_str);
2094
2095 /* Split the spec_str in to name and expr. */
2096 *sep = '\0';
2097
2098 if (strchr(spec_str, '{'))
2099 errx(EXIT_FAILURE,
2100 _("don't use `{' in the name of a counter: %s"),
2101 spec_str);
2102
2103 spec = xmalloc(sizeof(struct counter_spec));
2104 INIT_LIST_HEAD(&spec->specs);
2105 spec->name = spec_str;
2106 spec->expr = sep + 1;
2107
2108 return spec;
2109}
2110
2111static void free_counter_spec(struct counter_spec *counter_spec)
2112{
2113 free(counter_spec);
2114}
2115
46eca86e 2116static struct libscols_filter *new_counter(const struct counter_spec *spec, struct lsfd_control *ctl)
7d8c81d7 2117{
46eca86e
KZ
2118 struct libscols_filter *f;
2119 struct libscols_counter *ct;
7d8c81d7 2120
46eca86e 2121 f = new_filter(spec->expr, false, ctl);
7d8c81d7 2122
46eca86e
KZ
2123 ct = scols_filter_new_counter(f);
2124 if (!ct)
2125 err(EXIT_FAILURE, _("failed to allocate counter"));
2126
2127 scols_counter_set_name(ct, spec->name);
2128 scols_counter_set_func(ct, SCOLS_COUNTER_COUNT);
2129
2130 return f;
7d8c81d7
MY
2131}
2132
46eca86e 2133static struct libscols_filter **new_counters(struct list_head *specs, struct lsfd_control *ctl)
7d8c81d7 2134{
46eca86e 2135 struct libscols_filter **ct_filters;
7d8c81d7
MY
2136 size_t len = list_count_entries(specs);
2137 size_t i = 0;
2138 struct list_head *s;
2139
46eca86e 2140 ct_filters = xcalloc(len + 1, sizeof(struct libscols_filter *));
7d8c81d7
MY
2141 list_for_each(s, specs) {
2142 struct counter_spec *spec = list_entry(s, struct counter_spec, specs);
46eca86e 2143 ct_filters[i++] = new_counter(spec, ctl);
7d8c81d7 2144 }
46eca86e 2145 assert(ct_filters[len] == NULL);
7d8c81d7 2146
46eca86e 2147 return ct_filters;
7d8c81d7
MY
2148}
2149
46eca86e 2150static struct libscols_filter **new_default_counters(struct lsfd_control *ctl)
7d8c81d7 2151{
46eca86e 2152 struct libscols_filter **ct_filters;
7d8c81d7
MY
2153 size_t len = ARRAY_SIZE(default_counter_specs);
2154 size_t i;
2155
46eca86e 2156 ct_filters = xcalloc(len + 1, sizeof(struct libscols_filter *));
7d8c81d7 2157 for (i = 0; i < len; i++) {
54a06438 2158 const struct counter_spec *spec = default_counter_specs + i;
46eca86e 2159 ct_filters[i] = new_counter(spec, ctl);
7d8c81d7 2160 }
46eca86e 2161 assert(ct_filters[len] == NULL);
7d8c81d7 2162
46eca86e 2163 return ct_filters;
7d8c81d7
MY
2164}
2165
2b0a4de3
MY
2166static void dump_default_counter_specs(void)
2167{
2168 size_t len = ARRAY_SIZE(default_counter_specs);
2169 size_t i;
2170
2171 puts("default counter specs:");
2172 for (i = 0; i < len; i++) {
54a06438 2173 const struct counter_spec *spec = default_counter_specs + i;
2b0a4de3
MY
2174 printf("\t%s:%s\n", spec->name, spec->expr);
2175 }
2176}
2177
2178static void dump_counter_specs(struct list_head *specs)
2179{
2180 struct list_head *s;
2181
2182 puts("custom counter specs:");
2183 list_for_each(s, specs) {
2184 struct counter_spec *spec = list_entry(s, struct counter_spec, specs);
2185 printf("\t%s:%s\n", spec->name, spec->expr);
2186 }
2187}
2188
7d8c81d7
MY
2189static struct libscols_table *new_summary_table(struct lsfd_control *ctl)
2190{
2191 struct libscols_table *tb = scols_new_table();
2192
2193 struct libscols_column *name_cl, *value_cl;
2194
2195 if (!tb)
2196 err(EXIT_FAILURE, _("failed to allocate summary table"));
2197
2198 scols_table_enable_noheadings(tb, ctl->noheadings);
2199 scols_table_enable_raw(tb, ctl->raw);
2200 scols_table_enable_json(tb, ctl->json);
2201
2202 if(ctl->json)
2203 scols_table_set_name(tb, "lsfd-summary");
2204
2205
2206 value_cl = scols_table_new_column(tb, _("VALUE"), 0, SCOLS_FL_RIGHT);
2207 if (!value_cl)
2208 err(EXIT_FAILURE, _("failed to allocate summary column"));
2209 if (ctl->json)
2210 scols_column_set_json_type(value_cl, SCOLS_JSON_NUMBER);
2211
2212 name_cl = scols_table_new_column(tb, _("COUNTER"), 0, 0);
2213 if (!name_cl)
2214 err(EXIT_FAILURE, _("failed to allocate summary column"));
2215 if (ctl->json)
2216 scols_column_set_json_type(name_cl, SCOLS_JSON_STRING);
2217
2218 return tb;
2219}
2220
46eca86e 2221static void emit_summary(struct lsfd_control *ctl)
7d8c81d7 2222{
46eca86e
KZ
2223 struct libscols_iter *itr;
2224 struct libscols_filter **ct_fltr;
2225 struct libscols_table *tb = new_summary_table(ctl);
7d8c81d7 2226
46eca86e 2227 itr = scols_new_iter(SCOLS_ITER_FORWARD);
7d8c81d7 2228
46eca86e
KZ
2229 for (ct_fltr = ctl->ct_filters; *ct_fltr; ct_fltr++) {
2230 struct libscols_counter *ct = NULL;
7d8c81d7 2231
46eca86e
KZ
2232 scols_reset_iter(itr, SCOLS_ITER_FORWARD);
2233 while (scols_filter_next_counter(*ct_fltr, itr, &ct) == 0) {
2234 char *str = NULL;
2235 struct libscols_line *ln;
2236
2237 ln = scols_table_new_line(tb, NULL);
2238 if (!ln)
2239 err(EXIT_FAILURE, _("failed to allocate summary line"));
7d8c81d7 2240
46eca86e
KZ
2241 xasprintf(&str, "%llu", scols_counter_get_result(ct));
2242 if (scols_line_refer_data(ln, 0, str))
2243 err(EXIT_FAILURE, _("failed to add summary data"));
2244 if (scols_line_set_data(ln, 1, scols_counter_get_name(ct)))
2245 err(EXIT_FAILURE, _("failed to add summary data"));
2246 }
7d8c81d7 2247 }
46eca86e
KZ
2248
2249 scols_free_iter(itr);
7d8c81d7
MY
2250 scols_print_table(tb);
2251
2252 scols_unref_table(tb);
2253}
2254
0ee16e43
MY
2255static void attach_xinfos(struct list_head *procs)
2256{
2257 struct list_head *p;
2258
2259 list_for_each (p, procs) {
2260 struct proc *proc = list_entry(p, struct proc, procs);
2261 struct list_head *f;
2262
2263 list_for_each (f, &proc->files) {
2264 struct file *file = list_entry(f, struct file, files);
2265 if (file->class->attach_xinfo)
2266 file->class->attach_xinfo(file);
2267 }
2268 }
2269}
2270
da901314
MY
2271static void set_multiplexed_flags(struct list_head *procs)
2272{
2273 struct list_head *p;
2274 list_for_each (p, procs) {
2275 struct proc *proc = list_entry(p, struct proc, procs);
2276 struct list_head *f;
2277 list_for_each (f, &proc->files) {
2278 struct file *file = list_entry(f, struct file, files);
2279 if (is_opened_file(file) && !file->multiplexed) {
2280 int fd = file->association;
2281 if (is_multiplexed_by_eventpoll(fd, &proc->eventpolls))
2282 file->multiplexed = 1;
2283 }
2284 }
2285 }
2286}
2287
cd7f9b86
MY
2288/* Filter expressions for implementing -i option.
2289 *
2290 * To list up the protocol names, use the following command line
2291 *
2292 * cd linux/net;
2293 * find . -type f -exec grep -A 1 --color=auto -nH --null -e 'struct proto .*{' \{\} +
2294 *
2295 */
2296#define INET_SUBEXP_BEGIN "(SOCK.PROTONAME =~ \"^("
2297#define INET4_REG "TCP|UDP|RAW|PING|UDP-Lite|SCTP|DCCP|L2TP/IP|SMC"
2298#define INET6_REG "TCPv6|UDPv6|RAWv6|PINGv6|UDPLITEv6|SCTPv6|DCCPv6|L2TP/IPv6|SMC6"
2299#define INET_SUBEXP_END ")$\")"
2300
2301static const char *inet4_subexpr = INET_SUBEXP_BEGIN
2302 INET4_REG
2303 INET_SUBEXP_END;
2304static const char *inet6_subexpr = INET_SUBEXP_BEGIN
2305 INET6_REG
2306 INET_SUBEXP_END;
2307static const char *inet46_subexpr = INET_SUBEXP_BEGIN
2308 INET4_REG "|" INET6_REG
2309 INET_SUBEXP_END;
2310
128beb71
MY
2311int main(int argc, char *argv[])
2312{
f5a81caf 2313 int c, collist = 0;
78f20159 2314 size_t i;
128beb71 2315 char *outarg = NULL;
52ab46da 2316 char *filter_expr = NULL;
cae0cf10 2317 bool debug_filter = false;
2b0a4de3 2318 bool dump_counters = false;
c647ebc2 2319 pid_t *pids = NULL;
4cfabab2 2320 int n_pids = 0;
7d8c81d7
MY
2321 struct list_head counter_specs;
2322
0d681980
KZ
2323 struct lsfd_control ctl = {
2324 .show_main = 1
2325 };
2326
7d8c81d7 2327 INIT_LIST_HEAD(&counter_specs);
128beb71 2328
6fb6f549 2329 enum {
7d8c81d7
MY
2330 OPT_DEBUG_FILTER = CHAR_MAX + 1,
2331 OPT_SUMMARY,
2b0a4de3 2332 OPT_DUMP_COUNTERS,
bd38c759 2333 OPT_DROP_PRIVILEGE,
6fb6f549 2334 };
128beb71
MY
2335 static const struct option longopts[] = {
2336 { "noheadings", no_argument, NULL, 'n' },
2337 { "output", required_argument, NULL, 'o' },
2338 { "version", no_argument, NULL, 'V' },
2339 { "help", no_argument, NULL, 'h' },
2340 { "json", no_argument, NULL, 'J' },
2341 { "raw", no_argument, NULL, 'r' },
6008b460 2342 { "threads", no_argument, NULL, 'l' },
73ee7d4c 2343 { "notruncate", no_argument, NULL, 'u' },
4cfabab2 2344 { "pid", required_argument, NULL, 'p' },
cd7f9b86 2345 { "inet", optional_argument, NULL, 'i' },
52ab46da 2346 { "filter", required_argument, NULL, 'Q' },
cae0cf10 2347 { "debug-filter",no_argument, NULL, OPT_DEBUG_FILTER },
7d8c81d7
MY
2348 { "summary", optional_argument, NULL, OPT_SUMMARY },
2349 { "counter", required_argument, NULL, 'C' },
2b0a4de3 2350 { "dump-counters",no_argument, NULL, OPT_DUMP_COUNTERS },
e0cdf606 2351 { "list-columns",no_argument, NULL, 'H' },
bd38c759 2352 { "_drop-privilege",no_argument,NULL,OPT_DROP_PRIVILEGE },
128beb71
MY
2353 { NULL, 0, NULL, 0 },
2354 };
2355
2356 setlocale(LC_ALL, "");
2357 bindtextdomain(PACKAGE, LOCALEDIR);
2358 textdomain(PACKAGE);
2359 close_stdout_atexit();
2360
e0cdf606 2361 while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:i::C:sH", longopts, NULL)) != -1) {
128beb71
MY
2362 switch (c) {
2363 case 'n':
2364 ctl.noheadings = 1;
2365 break;
2366 case 'o':
2367 outarg = optarg;
2368 break;
2369 case 'J':
2370 ctl.json = 1;
2371 break;
2372 case 'r':
2373 ctl.raw = 1;
2374 break;
6008b460
MY
2375 case 'l':
2376 ctl.threads = 1;
2377 break;
73ee7d4c
KZ
2378 case 'u':
2379 ctl.notrunc = 1;
2380 break;
4cfabab2 2381 case 'p':
c647ebc2 2382 parse_pids(optarg, &pids, &n_pids);
4cfabab2 2383 break;
cd7f9b86
MY
2384 case 'i': {
2385 const char *subexpr = NULL;
0ece530d
MY
2386
2387 ctl.sockets_only = 1;
cd7f9b86
MY
2388 if (optarg == NULL)
2389 subexpr = inet46_subexpr;
2390 else if (strcmp(optarg, "4") == 0)
2391 subexpr = inet4_subexpr;
2392 else if (strcmp(optarg, "6") == 0)
2393 subexpr = inet6_subexpr;
2394 else
2395 errx(EXIT_FAILURE,
2396 _("unknown -i/--inet argument: %s"),
2397 optarg);
2398
2399 append_filter_expr(&filter_expr, subexpr, true);
2400 break;
2401 }
52ab46da
MY
2402 case 'Q':
2403 append_filter_expr(&filter_expr, optarg, true);
2404 break;
7d8c81d7
MY
2405 case 'C': {
2406 struct counter_spec *c = new_counter_spec(optarg);
2407 list_add_tail(&c->specs, &counter_specs);
2408 break;
2409 }
cae0cf10
MY
2410 case OPT_DEBUG_FILTER:
2411 debug_filter = true;
2412 break;
7d8c81d7
MY
2413 case OPT_SUMMARY:
2414 if (optarg) {
2415 if (strcmp(optarg, "never") == 0)
0d681980 2416 ctl.show_summary = 0, ctl.show_main = 1;
7d8c81d7 2417 else if (strcmp(optarg, "only") == 0)
0d681980
KZ
2418 ctl.show_summary = 1, ctl.show_main = 0;
2419 else if (strcmp(optarg, "append") == 0)
2420 ctl.show_summary = 1, ctl.show_main = 1;
7d8c81d7
MY
2421 else
2422 errx(EXIT_FAILURE, _("unsupported --summary argument"));
2423 } else
0d681980 2424 ctl.show_summary = 1, ctl.show_main = 0;
7d8c81d7 2425 break;
2b0a4de3
MY
2426 case OPT_DUMP_COUNTERS:
2427 dump_counters = true;
2428 break;
bd38c759
MY
2429 case OPT_DROP_PRIVILEGE:
2430 if (setuid(1) == -1)
2431 err(EXIT_FAILURE, _("failed to drop privilege"));
2432 break;
128beb71
MY
2433 case 'V':
2434 print_version(EXIT_SUCCESS);
2435 case 'h':
2436 usage();
e0cdf606 2437 case 'H':
f5a81caf
KZ
2438 collist = 1;
2439 break;
128beb71
MY
2440 default:
2441 errtryhelp(EXIT_FAILURE);
2442 }
2443 }
f5a81caf
KZ
2444
2445 if (collist)
2446 list_colunms("lsfd-columns", stdout, ctl.raw, ctl.json); /* print and exit */
2447
4a2d7c47 2448 if (argv[optind])
2449 errtryhelp(EXIT_FAILURE);
128beb71 2450
6008b460 2451#define INITIALIZE_COLUMNS(COLUMN_SPEC) \
78f20159 2452 for (i = 0; i < ARRAY_SIZE(COLUMN_SPEC); i++) \
6008b460
MY
2453 columns[ncolumns++] = COLUMN_SPEC[i]
2454 if (!ncolumns) {
2455 if (ctl.threads)
2456 INITIALIZE_COLUMNS(default_threads_columns);
2457 else
2458 INITIALIZE_COLUMNS(default_columns);
2459 }
128beb71
MY
2460
2461 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
2462 &ncolumns, column_name_to_id) < 0)
2463 return EXIT_FAILURE;
2464
2465 scols_init_debug(0);
128beb71 2466
cb6f0c4a
KZ
2467 INIT_LIST_HEAD(&ctl.procs);
2468
6fb6f549 2469 /* inilialize scols table */
78f20159 2470 ctl.tb = scols_new_table();
128beb71
MY
2471 if (!ctl.tb)
2472 err(EXIT_FAILURE, _("failed to allocate output table"));
2473
2474 scols_table_enable_noheadings(ctl.tb, ctl.noheadings);
2475 scols_table_enable_raw(ctl.tb, ctl.raw);
2476 scols_table_enable_json(ctl.tb, ctl.json);
2477 if (ctl.json)
2478 scols_table_set_name(ctl.tb, "lsfd");
2479
6fb6f549 2480 /* create output columns */
78f20159 2481 for (i = 0; i < ncolumns; i++) {
128beb71 2482 const struct colinfo *col = get_column_info(i);
e8f945e4 2483 struct libscols_column *cl = add_column(ctl.tb, col, 0);
128beb71 2484
128beb71
MY
2485 if (!cl)
2486 err(EXIT_FAILURE, _("failed to allocate output column"));
2487
52ab46da
MY
2488 if (ctl.notrunc) {
2489 int flags = scols_column_get_flags(cl);
2490 flags &= ~SCOLS_FL_TRUNC;
2491 scols_column_set_flags(cl, flags);
2492 }
2493 }
2494
46eca86e 2495 /* make filter */
52ab46da 2496 if (filter_expr) {
46eca86e 2497 ctl.filter = new_filter(filter_expr, debug_filter, &ctl);
52ab46da 2498 free(filter_expr);
128beb71
MY
2499 }
2500
2b0a4de3
MY
2501 if (dump_counters) {
2502 if (list_empty(&counter_specs))
2503 dump_default_counter_specs();
2504 else
2505 dump_counter_specs(&counter_specs);
2506 return 0;
2507 }
2508
7d8c81d7 2509 /* make counters */
0d681980 2510 if (ctl.show_summary) {
7d8c81d7 2511 if (list_empty(&counter_specs))
46eca86e 2512 ctl.ct_filters = new_default_counters(&ctl);
7d8c81d7 2513 else {
46eca86e 2514 ctl.ct_filters = new_counters(&counter_specs, &ctl);
7d8c81d7
MY
2515 list_free(&counter_specs, struct counter_spec, specs,
2516 free_counter_spec);
2517 }
2518 }
2519
4cfabab2
MY
2520 if (n_pids > 0)
2521 sort_pids(pids, n_pids);
2522
c3f40eeb
MY
2523 if (scols_table_get_column_by_name(ctl.tb, "XMODE"))
2524 ctl.show_xmode = 1;
2525
b20ab277
MY
2526 /* collect data
2527 *
2528 * The call initialize_ipc_table() must come before
2529 * initialize_classes.
2530 */
bc9fd537 2531 initialize_nodevs();
b20ab277 2532 initialize_ipc_table();
4230e09f 2533 initialize_classes();
3c007400 2534 initialize_devdrvs();
4230e09f 2535
4cfabab2 2536 collect_processes(&ctl, pids, n_pids);
c647ebc2 2537 free(pids);
128beb71 2538
0ee16e43 2539 attach_xinfos(&ctl.procs);
c3f40eeb 2540 if (ctl.show_xmode)
da901314
MY
2541 set_multiplexed_flags(&ctl.procs);
2542
0ee16e43 2543
cb6f0c4a 2544 convert(&ctl.procs, &ctl);
0d681980
KZ
2545
2546 /* print */
2547 if (ctl.show_main)
7d8c81d7 2548 emit(&ctl);
0d681980 2549
46eca86e
KZ
2550 if (ctl.show_summary && ctl.ct_filters)
2551 emit_summary(&ctl);
6fb6f549 2552
c8f43545 2553 /* cleanup */
cb6f0c4a 2554 delete(&ctl.procs, &ctl);
128beb71 2555
3c007400 2556 finalize_devdrvs();
4230e09f 2557 finalize_classes();
837c9eec 2558 finalize_ipc_table();
bc9fd537 2559 finalize_nodevs();
4230e09f 2560
128beb71
MY
2561 return 0;
2562}