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