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