]>
Commit | Line | Data |
---|---|---|
8a204562 | 1 | /* |
9abd5e4b KZ |
2 | * SPDX-License-Identifier: GPL-2.0-or-later |
3 | * | |
8a204562 KZ |
4 | * lsns(8) - list system namespaces |
5 | * | |
6 | * Copyright (C) 2015 Karel Zak <kzak@redhat.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
8a204562 KZ |
12 | */ |
13 | #include <stdio.h> | |
14 | #include <string.h> | |
15 | #include <getopt.h> | |
16 | #include <stdlib.h> | |
17 | #include <assert.h> | |
18 | #include <dirent.h> | |
19 | #include <unistd.h> | |
20 | #include <sys/stat.h> | |
21 | #include <sys/types.h> | |
a52852d3 | 22 | #include <wchar.h> |
8a204562 | 23 | #include <libsmartcols.h> |
74d2056a | 24 | #include <libmount.h> |
1467a668 | 25 | # include <stdbool.h> |
8a204562 | 26 | |
4195756e | 27 | #ifdef HAVE_LINUX_NET_NAMESPACE_H |
e9de8d3b KZ |
28 | # include <sys/socket.h> |
29 | # include <linux/netlink.h> | |
30 | # include <linux/rtnetlink.h> | |
31 | # include <linux/net_namespace.h> | |
4195756e MY |
32 | #endif |
33 | ||
d652d4c6 | 34 | #ifdef HAVE_LINUX_NSFS_H |
e9de8d3b | 35 | # include <linux/nsfs.h> |
fc686823 KZ |
36 | # if defined(NS_GET_NSTYPE) && defined(NS_GET_OWNER_UID) |
37 | # define USE_NS_GET_API 1 | |
38 | # endif | |
d652d4c6 MY |
39 | #endif |
40 | ||
8a204562 KZ |
41 | #include "pathnames.h" |
42 | #include "nls.h" | |
43 | #include "xalloc.h" | |
44 | #include "c.h" | |
45 | #include "list.h" | |
46 | #include "closestream.h" | |
47 | #include "optutils.h" | |
f7f6cc63 | 48 | #include "procfs.h" |
8a204562 KZ |
49 | #include "strutils.h" |
50 | #include "namespace.h" | |
a52852d3 | 51 | #include "idcache.h" |
f7f6cc63 | 52 | #include "fileutils.h" |
1467a668 | 53 | #include "column-list-table.h" |
8a204562 KZ |
54 | |
55 | #include "debug.h" | |
56 | ||
2ba641e5 | 57 | static UL_DEBUG_DEFINE_MASK(lsns); |
8a204562 KZ |
58 | UL_DEBUG_DEFINE_MASKNAMES(lsns) = UL_DEBUG_EMPTY_MASKNAMES; |
59 | ||
60 | #define LSNS_DEBUG_INIT (1 << 1) | |
61 | #define LSNS_DEBUG_PROC (1 << 2) | |
62 | #define LSNS_DEBUG_NS (1 << 3) | |
592c1d95 | 63 | #define LSNS_DEBUG_FILTER (1 << 4) |
8a204562 KZ |
64 | #define LSNS_DEBUG_ALL 0xFFFF |
65 | ||
4195756e MY |
66 | #define LSNS_NETNS_UNUSABLE -2 |
67 | ||
8a204562 KZ |
68 | #define DBG(m, x) __UL_DBG(lsns, LSNS_DEBUG_, m, x) |
69 | #define ON_DBG(m, x) __UL_DBG_CALL(lsns, LSNS_DEBUG_, m, x) | |
70 | ||
249ba69c TW |
71 | #define lsns_ioctl(fildes, request, ...) __extension__ ({ \ |
72 | int ret = ioctl(fildes, request, ##__VA_ARGS__); \ | |
73 | if (ret == -1 && errno == ENOTTY) \ | |
74 | warnx("Unsupported ioctl %s", #request); \ | |
75 | ret; }) | |
76 | ||
6d00cfb2 KZ |
77 | #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(lsns) |
78 | #include "debugobj.h" | |
79 | ||
249ba69c TW |
80 | #define EXIT_UNSUPPORTED_IOCTL 2 |
81 | ||
2ba641e5 | 82 | static struct idcache *uid_cache = NULL; |
a52852d3 | 83 | |
8a204562 KZ |
84 | /* column IDs */ |
85 | enum { | |
86 | COL_NS = 0, | |
87 | COL_TYPE, | |
88 | COL_PATH, | |
89 | COL_NPROCS, | |
90 | COL_PID, | |
969d3d15 | 91 | COL_PPID, |
a52852d3 KZ |
92 | COL_COMMAND, |
93 | COL_UID, | |
4195756e | 94 | COL_USER, |
74d2056a MY |
95 | COL_NETNSID, |
96 | COL_NSFS, | |
d652d4c6 MY |
97 | COL_PNS, /* parent namespace */ |
98 | COL_ONS, /* owner namespace */ | |
8a204562 KZ |
99 | }; |
100 | ||
101 | /* column names */ | |
102 | struct colinfo { | |
103 | const char *name; /* header */ | |
104 | double whint; /* width hint (N < 1 is in percent of termwidth) */ | |
105 | int flags; /* SCOLS_FL_* */ | |
106 | const char *help; | |
31b25347 | 107 | int json_type; |
8a204562 KZ |
108 | }; |
109 | ||
110 | /* columns descriptions */ | |
969d3d15 | 111 | static const struct colinfo infos[] = { |
31b25347 | 112 | [COL_NS] = { "NS", 10, SCOLS_FL_RIGHT, N_("namespace identifier (inode number)"), SCOLS_JSON_NUMBER }, |
8a204562 KZ |
113 | [COL_TYPE] = { "TYPE", 5, 0, N_("kind of namespace") }, |
114 | [COL_PATH] = { "PATH", 0, 0, N_("path to the namespace")}, | |
31b25347 KZ |
115 | [COL_NPROCS] = { "NPROCS", 5, SCOLS_FL_RIGHT, N_("number of processes in the namespace"), SCOLS_JSON_NUMBER }, |
116 | [COL_PID] = { "PID", 5, SCOLS_FL_RIGHT, N_("lowest PID in the namespace"), SCOLS_JSON_NUMBER }, | |
117 | [COL_PPID] = { "PPID", 5, SCOLS_FL_RIGHT, N_("PPID of the PID"), SCOLS_JSON_NUMBER }, | |
a52852d3 | 118 | [COL_COMMAND] = { "COMMAND", 0, SCOLS_FL_TRUNC, N_("command line of the PID")}, |
31b25347 | 119 | [COL_UID] = { "UID", 0, SCOLS_FL_RIGHT, N_("UID of the PID"), SCOLS_JSON_NUMBER}, |
4195756e | 120 | [COL_USER] = { "USER", 0, 0, N_("username of the PID")}, |
f593e279 | 121 | [COL_NETNSID] = { "NETNSID", 0, SCOLS_FL_RIGHT, N_("namespace ID as used by network subsystem")}, |
d652d4c6 MY |
122 | [COL_NSFS] = { "NSFS", 0, SCOLS_FL_WRAP, N_("nsfs mountpoint (usually used network subsystem)")}, |
123 | [COL_PNS] = { "PNS", 10, SCOLS_FL_RIGHT, N_("parent namespace identifier (inode number)"), SCOLS_JSON_NUMBER }, | |
124 | [COL_ONS] = { "ONS", 10, SCOLS_FL_RIGHT, N_("owner namespace identifier (inode number)"), SCOLS_JSON_NUMBER }, | |
8a204562 KZ |
125 | }; |
126 | ||
127 | static int columns[ARRAY_SIZE(infos) * 2]; | |
128 | static size_t ncolumns; | |
129 | ||
130 | enum { | |
131 | LSNS_ID_MNT = 0, | |
132 | LSNS_ID_NET, | |
133 | LSNS_ID_PID, | |
134 | LSNS_ID_UTS, | |
135 | LSNS_ID_IPC, | |
2b8889c4 | 136 | LSNS_ID_USER, |
96dc4f80 AR |
137 | LSNS_ID_CGROUP, |
138 | LSNS_ID_TIME | |
8a204562 KZ |
139 | }; |
140 | ||
141 | static char *ns_names[] = { | |
142 | [LSNS_ID_MNT] = "mnt", | |
143 | [LSNS_ID_NET] = "net", | |
144 | [LSNS_ID_PID] = "pid", | |
145 | [LSNS_ID_UTS] = "uts", | |
146 | [LSNS_ID_IPC] = "ipc", | |
2b8889c4 | 147 | [LSNS_ID_USER] = "user", |
96dc4f80 AR |
148 | [LSNS_ID_CGROUP] = "cgroup", |
149 | [LSNS_ID_TIME] = "time" | |
8a204562 KZ |
150 | }; |
151 | ||
c67b83c1 MY |
152 | enum { |
153 | RELA_PARENT, | |
154 | RELA_OWNER, | |
155 | MAX_RELA | |
156 | }; | |
157 | ||
8a204562 KZ |
158 | struct lsns_namespace { |
159 | ino_t id; | |
160 | int type; /* LSNS_* */ | |
161 | int nprocs; | |
4195756e | 162 | int netnsid; |
c67b83c1 | 163 | ino_t related_id[MAX_RELA]; |
a52852d3 KZ |
164 | |
165 | struct lsns_process *proc; | |
8a204562 | 166 | |
c67b83c1 | 167 | struct lsns_namespace *related_ns[MAX_RELA]; |
e8c56ae2 | 168 | struct libscols_line *ns_outline; |
f91ffe44 | 169 | uid_t uid_fallback; /* refer this member if `proc' is NULL. */ |
e8c56ae2 | 170 | |
8a204562 KZ |
171 | struct list_head namespaces; /* lsns->processes member */ |
172 | struct list_head processes; /* head of lsns_process *siblings */ | |
173 | }; | |
174 | ||
175 | struct lsns_process { | |
176 | pid_t pid; /* process PID */ | |
177 | pid_t ppid; /* parent's PID */ | |
178 | pid_t tpid; /* thread group */ | |
179 | char state; | |
a52852d3 | 180 | uid_t uid; |
8a204562 KZ |
181 | |
182 | ino_t ns_ids[ARRAY_SIZE(ns_names)]; | |
d652d4c6 MY |
183 | ino_t ns_pids[ARRAY_SIZE(ns_names)]; |
184 | ino_t ns_oids[ARRAY_SIZE(ns_names)]; | |
185 | ||
8a204562 KZ |
186 | struct list_head ns_siblings[ARRAY_SIZE(ns_names)]; |
187 | ||
8a204562 | 188 | struct list_head processes; /* list of processes */ |
969d3d15 KZ |
189 | |
190 | struct libscols_line *outline; | |
191 | struct lsns_process *parent; | |
4195756e MY |
192 | |
193 | int netnsid; | |
8a204562 KZ |
194 | }; |
195 | ||
8d27b605 | 196 | |
e8c56ae2 | 197 | enum { |
3387ad72 | 198 | LSNS_TREE_NONE, |
8d27b605 MY |
199 | LSNS_TREE_PROCESS, |
200 | LSNS_TREE_OWNER, | |
201 | LSNS_TREE_PARENT, | |
e8c56ae2 MY |
202 | }; |
203 | ||
8a204562 KZ |
204 | struct lsns { |
205 | struct list_head processes; | |
206 | struct list_head namespaces; | |
207 | ||
304fbe8b KZ |
208 | pid_t fltr_pid; /* filter out by PID */ |
209 | ino_t fltr_ns; /* filter out by namespace */ | |
210 | int fltr_types[ARRAY_SIZE(ns_names)]; | |
211 | int fltr_ntypes; | |
8a204562 KZ |
212 | |
213 | unsigned int raw : 1, | |
214 | json : 1, | |
8d27b605 | 215 | tree : 2, |
eabbd8b7 | 216 | persist : 1, |
deb3f518 | 217 | no_trunc : 1, |
0a32d39a | 218 | no_headings: 1, |
8d27b605 | 219 | no_wrap : 1; |
e8c56ae2 | 220 | |
7d5036fd | 221 | dev_t nsfs_dev; |
74d2056a MY |
222 | |
223 | struct libmnt_table *tab; | |
592c1d95 | 224 | struct libscols_filter *filter; |
8a204562 KZ |
225 | }; |
226 | ||
4195756e MY |
227 | struct netnsid_cache { |
228 | ino_t ino; | |
229 | int id; | |
230 | struct list_head netnsids; | |
231 | }; | |
232 | ||
592c1d95 MY |
233 | /* "userdata" used by callback for libsmartcols filter */ |
234 | struct filler_data { | |
235 | struct lsns *ls; | |
236 | struct lsns_namespace *ns; | |
237 | struct lsns_process *proc; | |
238 | }; | |
239 | ||
4195756e MY |
240 | static struct list_head netnsids_cache; |
241 | ||
242 | static int netlink_fd = -1; | |
243 | ||
8a204562 KZ |
244 | static void lsns_init_debug(void) |
245 | { | |
a15dca2f | 246 | __UL_INIT_DEBUG_FROM_ENV(lsns, LSNS_DEBUG_, 0, LSNS_DEBUG); |
8a204562 KZ |
247 | } |
248 | ||
304fbe8b KZ |
249 | static int ns_name2type(const char *name) |
250 | { | |
251 | size_t i; | |
252 | ||
253 | for (i = 0; i < ARRAY_SIZE(ns_names); i++) { | |
254 | if (strcmp(ns_names[i], name) == 0) | |
255 | return i; | |
256 | } | |
257 | return -1; | |
258 | } | |
259 | ||
8a204562 KZ |
260 | static int column_name_to_id(const char *name, size_t namesz) |
261 | { | |
262 | size_t i; | |
263 | ||
264 | assert(name); | |
265 | ||
266 | for (i = 0; i < ARRAY_SIZE(infos); i++) { | |
267 | const char *cn = infos[i].name; | |
268 | ||
269 | if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
270 | return i; | |
271 | } | |
272 | warnx(_("unknown column: %s"), name); | |
273 | return -1; | |
274 | } | |
275 | ||
f593e279 KZ |
276 | static int has_column(int id) |
277 | { | |
278 | size_t i; | |
279 | ||
280 | for (i = 0; i < ncolumns; i++) { | |
281 | if (columns[i] == id) | |
282 | return 1; | |
283 | } | |
284 | return 0; | |
285 | } | |
286 | ||
8a204562 KZ |
287 | static inline int get_column_id(int num) |
288 | { | |
289 | assert(num >= 0); | |
290 | assert((size_t) num < ncolumns); | |
291 | assert(columns[num] < (int) ARRAY_SIZE(infos)); | |
292 | ||
293 | return columns[num]; | |
294 | } | |
295 | ||
969d3d15 | 296 | static inline const struct colinfo *get_column_info(unsigned num) |
8a204562 KZ |
297 | { |
298 | return &infos[ get_column_id(num) ]; | |
299 | } | |
300 | ||
d6cdbf8d | 301 | static int get_ns_ino(struct path_cxt *pc, const char *nsname, ino_t *ino, ino_t *pino, ino_t *oino) |
8a204562 KZ |
302 | { |
303 | struct stat st; | |
304 | char path[16]; | |
305 | ||
306 | snprintf(path, sizeof(path), "ns/%s", nsname); | |
307 | ||
d6cdbf8d | 308 | if (ul_path_stat(pc, &st, 0, path) != 0) |
8a204562 KZ |
309 | return -errno; |
310 | *ino = st.st_ino; | |
d652d4c6 MY |
311 | |
312 | *pino = 0; | |
313 | *oino = 0; | |
314 | ||
e9de8d3b | 315 | #ifdef USE_NS_GET_API |
d652d4c6 | 316 | int fd, pfd, ofd; |
d6cdbf8d | 317 | fd = ul_path_open(pc, 0, path); |
d652d4c6 MY |
318 | if (fd < 0) |
319 | return -errno; | |
320 | if (strcmp(nsname, "pid") == 0 || strcmp(nsname, "user") == 0) { | |
249ba69c | 321 | if ((pfd = lsns_ioctl(fd, NS_GET_PARENT)) < 0) { |
d652d4c6 MY |
322 | if (errno == EPERM) |
323 | goto user; | |
324 | close(fd); | |
325 | return -errno; | |
326 | } | |
327 | if (fstat(pfd, &st) < 0) { | |
328 | close(pfd); | |
329 | close(fd); | |
330 | return -errno; | |
331 | } | |
332 | *pino = st.st_ino; | |
333 | close(pfd); | |
334 | } | |
335 | user: | |
249ba69c | 336 | if ((ofd = lsns_ioctl(fd, NS_GET_USERNS)) < 0) { |
d652d4c6 MY |
337 | if (errno == EPERM) |
338 | goto out; | |
339 | close(fd); | |
340 | return -errno; | |
341 | } | |
342 | if (fstat(ofd, &st) < 0) { | |
343 | close(ofd); | |
344 | close(fd); | |
345 | return -errno; | |
346 | } | |
347 | *oino = st.st_ino; | |
348 | close(ofd); | |
349 | out: | |
350 | close(fd); | |
351 | #endif | |
8a204562 KZ |
352 | return 0; |
353 | } | |
354 | ||
d6cdbf8d | 355 | static int parse_proc_stat(char *line, pid_t *pid, char *state, pid_t *ppid) |
3fcbd797 | 356 | { |
d6cdbf8d | 357 | char *p; |
3fcbd797 OH |
358 | int rc; |
359 | ||
3fcbd797 OH |
360 | p = strrchr(line, ')'); |
361 | if (p == NULL || | |
362 | sscanf(line, "%d (", pid) != 1 || | |
363 | sscanf(p, ") %c %d*[^\n]", state, ppid) != 2) { | |
364 | rc = -EINVAL; | |
365 | goto error; | |
366 | } | |
367 | rc = 0; | |
368 | ||
369 | error: | |
3fcbd797 OH |
370 | return rc; |
371 | } | |
304fbe8b | 372 | |
4195756e | 373 | #ifdef HAVE_LINUX_NET_NAMESPACE_H |
7eda2400 | 374 | static int netnsid_cache_find(ino_t netino, int *netnsid) |
4195756e MY |
375 | { |
376 | struct list_head *p; | |
377 | ||
378 | list_for_each(p, &netnsids_cache) { | |
379 | struct netnsid_cache *e = list_entry(p, | |
380 | struct netnsid_cache, | |
381 | netnsids); | |
382 | if (e->ino == netino) { | |
383 | *netnsid = e->id; | |
7eda2400 | 384 | return 1; |
4195756e MY |
385 | } |
386 | } | |
387 | ||
7eda2400 | 388 | return 0; |
4195756e MY |
389 | } |
390 | ||
391 | static void netnsid_cache_add(ino_t netino, int netnsid) | |
392 | { | |
393 | struct netnsid_cache *e; | |
394 | ||
395 | e = xcalloc(1, sizeof(*e)); | |
396 | e->ino = netino; | |
397 | e->id = netnsid; | |
398 | INIT_LIST_HEAD(&e->netnsids); | |
399 | list_add(&e->netnsids, &netnsids_cache); | |
400 | } | |
401 | ||
402 | static int get_netnsid_via_netlink_send_request(int target_fd) | |
403 | { | |
404 | unsigned char req[NLMSG_SPACE(sizeof(struct rtgenmsg)) | |
405 | + RTA_SPACE(sizeof(int32_t))]; | |
406 | ||
407 | struct nlmsghdr *nlh = (struct nlmsghdr *)req; | |
408 | struct rtgenmsg *rt = NLMSG_DATA(req); | |
409 | struct rtattr *rta = (struct rtattr *) | |
410 | (req + NLMSG_SPACE(sizeof(struct rtgenmsg))); | |
411 | int32_t *fd = RTA_DATA(rta); | |
412 | ||
413 | nlh->nlmsg_len = sizeof(req); | |
414 | nlh->nlmsg_flags = NLM_F_REQUEST; | |
415 | nlh->nlmsg_type = RTM_GETNSID; | |
416 | rt->rtgen_family = AF_UNSPEC; | |
417 | rta->rta_type = NETNSA_FD; | |
418 | rta->rta_len = RTA_SPACE(sizeof(int32_t)); | |
419 | *fd = target_fd; | |
420 | ||
421 | if (send(netlink_fd, req, sizeof(req), 0) < 0) | |
422 | return -1; | |
423 | return 0; | |
424 | } | |
425 | ||
426 | static int get_netnsid_via_netlink_recv_response(int *netnsid) | |
427 | { | |
428 | unsigned char res[NLMSG_SPACE(sizeof(struct rtgenmsg)) | |
429 | + ((RTA_SPACE(sizeof(int32_t)) | |
430 | < RTA_SPACE(sizeof(struct nlmsgerr))) | |
431 | ? RTA_SPACE(sizeof(struct nlmsgerr)) | |
432 | : RTA_SPACE(sizeof(int32_t)))]; | |
58b29eed RM |
433 | int rtalen; |
434 | ssize_t reslen; | |
4195756e MY |
435 | |
436 | struct nlmsghdr *nlh; | |
437 | struct rtattr *rta; | |
438 | ||
439 | reslen = recv(netlink_fd, res, sizeof(res), 0); | |
440 | if (reslen < 0) | |
441 | return -1; | |
442 | ||
443 | nlh = (struct nlmsghdr *)res; | |
58b29eed | 444 | if (!(NLMSG_OK(nlh, (size_t)reslen) |
4195756e MY |
445 | && nlh->nlmsg_type == RTM_NEWNSID)) |
446 | return -1; | |
447 | ||
448 | rtalen = NLMSG_PAYLOAD(nlh, sizeof(struct rtgenmsg)); | |
449 | rta = (struct rtattr *)(res + NLMSG_SPACE(sizeof(struct rtgenmsg))); | |
450 | if (!(RTA_OK(rta, rtalen) | |
451 | && rta->rta_type == NETNSA_NSID)) | |
452 | return -1; | |
453 | ||
454 | *netnsid = *(int *)RTA_DATA(rta); | |
455 | ||
456 | return 0; | |
457 | } | |
458 | ||
d6cdbf8d | 459 | static int get_netnsid_via_netlink(struct path_cxt *pc, const char *path) |
4195756e MY |
460 | { |
461 | int netnsid; | |
462 | int target_fd; | |
463 | ||
464 | if (netlink_fd < 0) | |
465 | return LSNS_NETNS_UNUSABLE; | |
466 | ||
d6cdbf8d | 467 | target_fd = ul_path_open(pc, O_RDONLY, path); |
4195756e MY |
468 | if (target_fd < 0) |
469 | return LSNS_NETNS_UNUSABLE; | |
470 | ||
471 | if (get_netnsid_via_netlink_send_request(target_fd) < 0) { | |
472 | netnsid = LSNS_NETNS_UNUSABLE; | |
473 | goto out; | |
474 | } | |
475 | ||
476 | if (get_netnsid_via_netlink_recv_response(&netnsid) < 0) { | |
477 | netnsid = LSNS_NETNS_UNUSABLE; | |
478 | goto out; | |
479 | } | |
480 | ||
481 | out: | |
482 | close(target_fd); | |
483 | return netnsid; | |
484 | } | |
485 | ||
d6cdbf8d | 486 | static int get_netnsid(struct path_cxt *pc, ino_t netino) |
4195756e MY |
487 | { |
488 | int netnsid; | |
489 | ||
490 | if (!netnsid_cache_find(netino, &netnsid)) { | |
d6cdbf8d | 491 | netnsid = get_netnsid_via_netlink(pc, "ns/net"); |
4195756e MY |
492 | netnsid_cache_add(netino, netnsid); |
493 | } | |
494 | ||
495 | return netnsid; | |
496 | } | |
497 | #else | |
d6cdbf8d | 498 | static int get_netnsid(struct path_cxt *pc __attribute__((__unused__)), |
4195756e MY |
499 | ino_t netino __attribute__((__unused__))) |
500 | { | |
501 | return LSNS_NETNS_UNUSABLE; | |
502 | } | |
503 | #endif /* HAVE_LINUX_NET_NAMESPACE_H */ | |
504 | ||
7d5036fd MY |
505 | static struct lsns_namespace *add_namespace_for_nsfd(struct lsns *ls, int fd, ino_t ino); |
506 | ||
507 | static void read_open_ns_inos(struct lsns *ls, struct path_cxt *pc) | |
508 | { | |
509 | DIR *sub = NULL; | |
510 | struct dirent *d = NULL; | |
511 | char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))]; | |
512 | ||
513 | while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) { | |
514 | uint64_t num; | |
515 | struct stat st; | |
516 | ||
517 | if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */ | |
518 | continue; | |
519 | ||
520 | snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num); | |
166e15d1 KZ |
521 | |
522 | if (ul_path_stat(pc, &st, 0, path) == 0 | |
523 | && st.st_dev == ls->nsfs_dev) { | |
7d5036fd MY |
524 | int fd = ul_path_open(pc, O_RDONLY, path); |
525 | if (fd >= 0) { | |
526 | add_namespace_for_nsfd(ls, fd, st.st_ino); | |
527 | close(fd); | |
528 | } | |
529 | } | |
530 | } | |
531 | } | |
532 | ||
d6cdbf8d | 533 | static int read_process(struct lsns *ls, struct path_cxt *pc) |
8a204562 KZ |
534 | { |
535 | struct lsns_process *p = NULL; | |
d6cdbf8d | 536 | int rc = 0; |
8a204562 | 537 | char buf[BUFSIZ]; |
8a204562 | 538 | size_t i; |
8a204562 | 539 | |
9d3d66df | 540 | p = xcalloc(1, sizeof(*p)); |
4195756e | 541 | p->netnsid = LSNS_NETNS_UNUSABLE; |
8a204562 | 542 | |
d6cdbf8d MY |
543 | if (procfs_process_get_uid(pc, &p->uid) == 0) |
544 | add_uid(uid_cache, p->uid); | |
a52852d3 | 545 | |
d6cdbf8d | 546 | if ((rc = procfs_process_get_stat(pc, buf, sizeof(buf))) < 0) |
8a204562 | 547 | goto done; |
d6cdbf8d | 548 | if ((rc = parse_proc_stat(buf, &p->pid, &p->state, &p->ppid)) < 0) |
8a204562 | 549 | goto done; |
8a204562 KZ |
550 | rc = 0; |
551 | ||
552 | for (i = 0; i < ARRAY_SIZE(p->ns_ids); i++) { | |
553 | INIT_LIST_HEAD(&p->ns_siblings[i]); | |
554 | ||
304fbe8b KZ |
555 | if (!ls->fltr_types[i]) |
556 | continue; | |
557 | ||
d6cdbf8d | 558 | rc = get_ns_ino(pc, ns_names[i], &p->ns_ids[i], |
d652d4c6 | 559 | &p->ns_pids[i], &p->ns_oids[i]); |
3082f851 | 560 | if (rc && rc != -EACCES && rc != -ENOENT) |
8a204562 | 561 | goto done; |
4195756e | 562 | if (i == LSNS_ID_NET) |
d6cdbf8d | 563 | p->netnsid = get_netnsid(pc, p->ns_ids[i]); |
8a204562 KZ |
564 | rc = 0; |
565 | } | |
566 | ||
567 | INIT_LIST_HEAD(&p->processes); | |
8a204562 | 568 | |
9dfd6019 | 569 | DBG(PROC, ul_debugobj(p, "new pid=%d", p->pid)); |
8a204562 | 570 | list_add_tail(&p->processes, &ls->processes); |
7d5036fd MY |
571 | |
572 | read_open_ns_inos(ls, pc); | |
8a204562 | 573 | done: |
8a204562 KZ |
574 | if (rc) |
575 | free(p); | |
576 | return rc; | |
577 | } | |
578 | ||
579 | static int read_processes(struct lsns *ls) | |
580 | { | |
f7f6cc63 KZ |
581 | DIR *dir; |
582 | struct dirent *d; | |
8a204562 | 583 | int rc = 0; |
d6cdbf8d | 584 | struct path_cxt *pc; |
8a204562 | 585 | |
8a204562 KZ |
586 | DBG(PROC, ul_debug("opening /proc")); |
587 | ||
f7f6cc63 KZ |
588 | dir = opendir(_PATH_PROC); |
589 | if (!dir) | |
590 | return -errno; | |
8a204562 | 591 | |
d6cdbf8d MY |
592 | pc = ul_new_path(NULL); |
593 | if (!pc) | |
594 | err(EXIT_FAILURE, _("failed to alloc procfs handler")); | |
595 | ||
f7f6cc63 KZ |
596 | while ((d = xreaddir(dir))) { |
597 | pid_t pid = 0; | |
598 | ||
599 | if (procfs_dirent_get_pid(d, &pid) != 0) | |
600 | continue; | |
601 | ||
d6cdbf8d MY |
602 | DBG(PROC, ul_debug("reading %d", (int) pid)); |
603 | rc = procfs_process_init_path(pc, pid); | |
604 | if (rc < 0) { | |
605 | DBG(PROC, ul_debug("failed in reading /proc/%d", (int) pid)); | |
606 | continue; | |
607 | } | |
608 | ||
609 | rc = read_process(ls, pc); | |
5dc625b6 | 610 | if (rc && rc != -EACCES && rc != -ENOENT) |
8a204562 KZ |
611 | break; |
612 | rc = 0; | |
613 | } | |
f7f6cc63 | 614 | |
d6cdbf8d MY |
615 | ul_unref_path(pc); |
616 | ||
8a204562 | 617 | DBG(PROC, ul_debug("closing /proc")); |
f7f6cc63 | 618 | closedir(dir); |
8a204562 KZ |
619 | return rc; |
620 | } | |
621 | ||
969d3d15 | 622 | static struct lsns_namespace *get_namespace(struct lsns *ls, ino_t ino) |
8a204562 KZ |
623 | { |
624 | struct list_head *p; | |
625 | ||
626 | list_for_each(p, &ls->namespaces) { | |
627 | struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces); | |
628 | ||
969d3d15 | 629 | if (ns->id == ino) |
8a204562 KZ |
630 | return ns; |
631 | } | |
632 | return NULL; | |
633 | } | |
634 | ||
9dfd6019 KZ |
635 | static int namespace_has_process(struct lsns_namespace *ns, pid_t pid) |
636 | { | |
637 | struct list_head *p; | |
638 | ||
639 | list_for_each(p, &ns->processes) { | |
640 | struct lsns_process *proc = list_entry(p, struct lsns_process, ns_siblings[ns->type]); | |
641 | ||
642 | if (proc->pid == pid) | |
643 | return 1; | |
644 | } | |
645 | return 0; | |
646 | } | |
647 | ||
d652d4c6 MY |
648 | static struct lsns_namespace *add_namespace(struct lsns *ls, int type, ino_t ino, |
649 | ino_t parent_ino, ino_t owner_ino) | |
8a204562 | 650 | { |
9d3d66df | 651 | struct lsns_namespace *ns = xcalloc(1, sizeof(*ns)); |
8a204562 KZ |
652 | |
653 | if (!ns) | |
654 | return NULL; | |
655 | ||
e1a15f8f | 656 | DBG(NS, ul_debugobj(ns, "new %s[%ju]", ns_names[type], (uintmax_t)ino)); |
8a204562 KZ |
657 | |
658 | INIT_LIST_HEAD(&ns->processes); | |
659 | INIT_LIST_HEAD(&ns->namespaces); | |
660 | ||
661 | ns->type = type; | |
662 | ns->id = ino; | |
c67b83c1 MY |
663 | ns->related_id[RELA_PARENT] = parent_ino; |
664 | ns->related_id[RELA_OWNER] = owner_ino; | |
8a204562 KZ |
665 | |
666 | list_add_tail(&ns->namespaces, &ls->namespaces); | |
667 | return ns; | |
668 | } | |
669 | ||
969d3d15 | 670 | static int add_process_to_namespace(struct lsns *ls, struct lsns_namespace *ns, struct lsns_process *proc) |
8a204562 | 671 | { |
969d3d15 KZ |
672 | struct list_head *p; |
673 | ||
e1a15f8f RM |
674 | DBG(NS, ul_debugobj(ns, "add process [%p] pid=%d to %s[%ju]", |
675 | proc, proc->pid, ns_names[ns->type], (uintmax_t)ns->id)); | |
8a204562 | 676 | |
969d3d15 KZ |
677 | list_for_each(p, &ls->processes) { |
678 | struct lsns_process *xproc = list_entry(p, struct lsns_process, processes); | |
679 | ||
680 | if (xproc->pid == proc->ppid) /* my parent */ | |
681 | proc->parent = xproc; | |
682 | else if (xproc->ppid == proc->pid) /* my child */ | |
683 | xproc->parent = proc; | |
684 | } | |
685 | ||
8a204562 KZ |
686 | list_add_tail(&proc->ns_siblings[ns->type], &ns->processes); |
687 | ns->nprocs++; | |
688 | ||
a52852d3 KZ |
689 | if (!ns->proc || ns->proc->pid > proc->pid) |
690 | ns->proc = proc; | |
969d3d15 | 691 | |
8a204562 KZ |
692 | return 0; |
693 | } | |
694 | ||
1880a578 KZ |
695 | static int cmp_namespaces(struct list_head *a, struct list_head *b, |
696 | __attribute__((__unused__)) void *data) | |
697 | { | |
698 | struct lsns_namespace *xa = list_entry(a, struct lsns_namespace, namespaces), | |
699 | *xb = list_entry(b, struct lsns_namespace, namespaces); | |
700 | ||
701 | return cmp_numbers(xa->id, xb->id); | |
702 | } | |
703 | ||
4195756e MY |
704 | static int netnsid_xasputs(char **str, int netnsid) |
705 | { | |
706 | if (netnsid >= 0) | |
707 | return xasprintf(str, "%d", netnsid); | |
708 | #ifdef NETNSA_NSID_NOT_ASSIGNED | |
042f62df | 709 | if (netnsid == NETNSA_NSID_NOT_ASSIGNED) |
4195756e MY |
710 | return xasprintf(str, "%s", "unassigned"); |
711 | #endif | |
042f62df | 712 | return 0; |
4195756e MY |
713 | } |
714 | ||
e9de8d3b | 715 | #ifdef USE_NS_GET_API |
de72df79 MY |
716 | static int clone_type_to_lsns_type(int clone_type) |
717 | { | |
718 | switch (clone_type) { | |
719 | case CLONE_NEWNS: | |
720 | return LSNS_ID_MNT; | |
721 | case CLONE_NEWCGROUP: | |
722 | return LSNS_ID_CGROUP; | |
723 | case CLONE_NEWUTS: | |
724 | return LSNS_ID_UTS; | |
725 | case CLONE_NEWIPC: | |
726 | return LSNS_ID_IPC; | |
727 | case CLONE_NEWUSER: | |
728 | return LSNS_ID_USER; | |
729 | case CLONE_NEWPID: | |
730 | return LSNS_ID_PID; | |
731 | case CLONE_NEWNET: | |
732 | return LSNS_ID_NET; | |
86e64cbf MY |
733 | #ifdef CLONE_NEWTIME |
734 | case CLONE_NEWTIME: | |
735 | return LSNS_ID_TIME; | |
736 | #endif | |
de72df79 MY |
737 | default: |
738 | return -1; | |
739 | } | |
740 | } | |
741 | ||
742 | static struct lsns_namespace *add_namespace_for_nsfd(struct lsns *ls, int fd, ino_t ino) | |
743 | { | |
744 | int fd_owner = -1, fd_parent = -1; | |
745 | struct stat st_owner, st_parent; | |
746 | ino_t ino_owner = 0, ino_parent = 0; | |
747 | struct lsns_namespace *ns; | |
748 | int clone_type, lsns_type; | |
749 | ||
249ba69c | 750 | clone_type = lsns_ioctl(fd, NS_GET_NSTYPE); |
de72df79 MY |
751 | if (clone_type < 0) |
752 | return NULL; | |
753 | lsns_type = clone_type_to_lsns_type(clone_type); | |
eabbd8b7 | 754 | if (lsns_type < 0 || ls->fltr_types[lsns_type] == 0) |
de72df79 MY |
755 | return NULL; |
756 | ||
249ba69c | 757 | fd_owner = lsns_ioctl(fd, NS_GET_USERNS); |
de72df79 MY |
758 | if (fd_owner < 0) |
759 | goto parent; | |
760 | if (fstat(fd_owner, &st_owner) < 0) | |
761 | goto parent; | |
762 | ino_owner = st_owner.st_ino; | |
763 | ||
764 | parent: | |
249ba69c | 765 | fd_parent = lsns_ioctl(fd, NS_GET_PARENT); |
de72df79 MY |
766 | if (fd_parent < 0) |
767 | goto add_ns; | |
768 | if (fstat(fd_parent, &st_parent) < 0) | |
769 | goto add_ns; | |
770 | ino_parent = st_parent.st_ino; | |
771 | ||
772 | add_ns: | |
773 | ns = add_namespace(ls, lsns_type, ino, ino_parent, ino_owner); | |
249ba69c | 774 | lsns_ioctl(fd, NS_GET_OWNER_UID, &ns->uid_fallback); |
f91ffe44 | 775 | add_uid(uid_cache, ns->uid_fallback); |
de72df79 MY |
776 | |
777 | if ((lsns_type == LSNS_ID_USER || lsns_type == LSNS_ID_PID) | |
778 | && ino_parent != ino && ino_parent != 0) { | |
779 | ns->related_ns[RELA_PARENT] = get_namespace(ls, ino_parent); | |
780 | if (!ns->related_ns[RELA_PARENT]) { | |
781 | ns->related_ns[RELA_PARENT] = add_namespace_for_nsfd(ls, fd_parent, ino_parent); | |
782 | if (ino_parent == ino_owner) | |
783 | ns->related_ns[RELA_OWNER] = ns->related_ns[RELA_PARENT]; | |
784 | } | |
785 | } | |
786 | ||
787 | if (ns->related_ns[RELA_OWNER] == NULL && ino_owner != 0) { | |
788 | ns->related_ns[RELA_OWNER] = get_namespace(ls, ino_owner); | |
789 | if (!ns->related_ns[RELA_OWNER]) | |
790 | ns->related_ns[RELA_OWNER] = add_namespace_for_nsfd(ls, fd_owner, ino_owner); | |
791 | } | |
792 | ||
793 | if (fd_owner >= 0) | |
794 | close(fd_owner); | |
795 | if (fd_parent >= 0) | |
796 | close(fd_parent); | |
797 | ||
798 | return ns; | |
799 | } | |
800 | ||
09710a22 | 801 | static void interpolate_missing_namespaces(struct lsns *ls, struct lsns_namespace *orphan, int rela) |
de72df79 MY |
802 | { |
803 | const int cmd[MAX_RELA] = { | |
804 | [RELA_PARENT] = NS_GET_PARENT, | |
805 | [RELA_OWNER] = NS_GET_USERNS | |
806 | }; | |
807 | char buf[BUFSIZ]; | |
808 | int fd_orphan, fd_missing; | |
809 | struct stat st; | |
810 | ||
811 | orphan->related_ns[rela] = get_namespace(ls, orphan->related_id[rela]); | |
812 | if (orphan->related_ns[rela]) | |
813 | return; | |
814 | ||
09710a22 | 815 | snprintf(buf, sizeof(buf), "/proc/%d/ns/%s", orphan->proc->pid, ns_names[orphan->type]); |
de72df79 MY |
816 | fd_orphan = open(buf, O_RDONLY); |
817 | if (fd_orphan < 0) | |
818 | return; | |
819 | ||
249ba69c | 820 | fd_missing = lsns_ioctl(fd_orphan, cmd[rela]); |
09710a22 | 821 | close(fd_orphan); |
de72df79 MY |
822 | if (fd_missing < 0) |
823 | return; | |
824 | ||
825 | if (fstat(fd_missing, &st) < 0 | |
826 | || st.st_ino != orphan->related_id[rela]) { | |
09710a22 | 827 | close(fd_missing); |
de72df79 MY |
828 | return; |
829 | } | |
830 | ||
831 | orphan->related_ns[rela] = add_namespace_for_nsfd(ls, fd_missing, orphan->related_id[rela]); | |
09710a22 | 832 | close(fd_missing); |
de72df79 MY |
833 | } |
834 | ||
e9de8d3b | 835 | static void read_related_namespaces(struct lsns *ls) |
8a204562 KZ |
836 | { |
837 | struct list_head *p; | |
de72df79 MY |
838 | struct lsns_namespace *orphan[2] = {NULL, NULL}; |
839 | int rela; | |
8a204562 | 840 | |
e9de8d3b KZ |
841 | list_for_each(p, &ls->namespaces) { |
842 | struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces); | |
843 | struct list_head *pp; | |
844 | list_for_each(pp, &ls->namespaces) { | |
845 | struct lsns_namespace *pns = list_entry(pp, struct lsns_namespace, namespaces); | |
846 | if (ns->type == LSNS_ID_USER | |
847 | || ns->type == LSNS_ID_PID) { | |
848 | if (ns->related_id[RELA_PARENT] == pns->id) | |
849 | ns->related_ns[RELA_PARENT] = pns; | |
850 | if (ns->related_id[RELA_OWNER] == pns->id) | |
851 | ns->related_ns[RELA_OWNER] = pns; | |
852 | if (ns->related_ns[RELA_PARENT] && ns->related_ns[RELA_OWNER]) | |
853 | break; | |
854 | } else { | |
855 | if (ns->related_id[RELA_OWNER] == pns->id) { | |
856 | ns->related_ns[RELA_OWNER] = pns; | |
857 | break; | |
858 | } | |
859 | } | |
860 | } | |
861 | ||
862 | /* lsns scans /proc/[0-9]+ for finding namespaces. | |
863 | * So if a namespace has no process, lsns cannot | |
864 | * find it. Here we call it a missing namespace. | |
865 | * | |
866 | * If the id for a related namesspce is known but | |
867 | * namespace for the id is not found, there must | |
868 | * be orphan namespaces. A missing namespace is an | |
869 | * owner or a parent of the orphan namespace. | |
870 | */ | |
871 | for (rela = 0; rela < MAX_RELA; rela++) { | |
872 | if (ns->related_id[rela] != 0 | |
873 | && ns->related_ns[rela] == NULL) { | |
874 | ns->related_ns[rela] = orphan[rela]; | |
875 | orphan[rela] = ns; | |
876 | } | |
877 | } | |
878 | } | |
879 | ||
880 | for (rela = 0; rela < MAX_RELA; rela++) { | |
881 | while (orphan[rela]) { | |
882 | struct lsns_namespace *current = orphan[rela]; | |
883 | orphan[rela] = orphan[rela]->related_ns[rela]; | |
884 | current->related_ns[rela] = NULL; | |
885 | interpolate_missing_namespaces(ls, current, rela); | |
886 | } | |
887 | } | |
888 | } | |
889 | ||
eabbd8b7 KZ |
890 | static int read_persistent_namespaces(struct lsns *ls) |
891 | { | |
892 | struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD); | |
893 | struct libmnt_fs *fs = NULL; | |
894 | ||
895 | while (mnt_table_next_fs(ls->tab, itr, &fs) == 0) { | |
896 | const char *root; | |
897 | char *p, *end = NULL; | |
898 | ino_t ino; | |
899 | int fd; | |
900 | ||
901 | if (!mnt_fs_match_fstype(fs, "nsfs")) | |
902 | continue; | |
903 | root = mnt_fs_get_root(fs); | |
904 | if (!root || !(p = strchr(root, '['))) | |
905 | continue; | |
906 | ||
907 | errno = 0; | |
908 | ino = strtoumax(++p, &end, 10); | |
909 | if (!end || *end != ']' || errno != 0) | |
910 | continue; | |
911 | if (get_namespace(ls, ino)) | |
912 | continue; | |
913 | ||
914 | fd = open(mnt_fs_get_target(fs), O_RDONLY); | |
915 | if (fd < 0) | |
916 | continue; | |
917 | ||
918 | add_namespace_for_nsfd(ls, fd, ino); | |
919 | close(fd); | |
920 | } | |
921 | ||
922 | mnt_free_iter(itr); | |
923 | return 0; | |
924 | } | |
925 | ||
e9de8d3b KZ |
926 | #endif /* USE_NS_GET_API */ |
927 | ||
928 | static int read_namespaces(struct lsns *ls) | |
929 | { | |
930 | struct list_head *p; | |
931 | ||
8a204562 KZ |
932 | DBG(NS, ul_debug("reading namespace")); |
933 | ||
934 | list_for_each(p, &ls->processes) { | |
935 | size_t i; | |
936 | struct lsns_namespace *ns; | |
937 | struct lsns_process *proc = list_entry(p, struct lsns_process, processes); | |
938 | ||
939 | for (i = 0; i < ARRAY_SIZE(proc->ns_ids); i++) { | |
940 | if (proc->ns_ids[i] == 0) | |
941 | continue; | |
969d3d15 | 942 | if (!(ns = get_namespace(ls, proc->ns_ids[i]))) { |
d652d4c6 MY |
943 | ns = add_namespace(ls, i, proc->ns_ids[i], |
944 | proc->ns_pids[i], proc->ns_oids[i]); | |
8a204562 KZ |
945 | if (!ns) |
946 | return -ENOMEM; | |
947 | } | |
969d3d15 | 948 | add_process_to_namespace(ls, ns, proc); |
8a204562 KZ |
949 | } |
950 | } | |
951 | ||
e9de8d3b | 952 | #ifdef USE_NS_GET_API |
eabbd8b7 KZ |
953 | read_persistent_namespaces(ls); |
954 | ||
e9de8d3b KZ |
955 | if (ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT) |
956 | read_related_namespaces(ls); | |
957 | #endif | |
1880a578 KZ |
958 | list_sort(&ls->namespaces, cmp_namespaces, NULL); |
959 | ||
8a204562 KZ |
960 | return 0; |
961 | } | |
962 | ||
7eda2400 | 963 | static int is_nsfs_root(struct libmnt_fs *fs, void *data) |
74d2056a | 964 | { |
7eda2400 KZ |
965 | if (!mnt_fs_match_fstype(fs, "nsfs") || !mnt_fs_get_root(fs)) |
966 | return 0; | |
967 | ||
968 | return (strcmp(mnt_fs_get_root(fs), (char *)data) == 0); | |
74d2056a MY |
969 | } |
970 | ||
7eda2400 | 971 | static int is_path_included(const char *path_set, const char *elt, |
74d2056a MY |
972 | const char sep) |
973 | { | |
974 | size_t elt_len; | |
975 | size_t path_set_len; | |
976 | char *tmp; | |
977 | ||
978 | ||
979 | tmp = strstr(path_set, elt); | |
980 | if (!tmp) | |
7eda2400 | 981 | return 0; |
74d2056a MY |
982 | |
983 | elt_len = strlen(elt); | |
984 | path_set_len = strlen(path_set); | |
985 | ||
986 | /* path_set includes only elt or | |
987 | * path_set includes elt as the first element. | |
988 | */ | |
989 | if (tmp == path_set | |
990 | && ((path_set_len == elt_len) | |
991 | || (path_set[elt_len] == sep))) | |
7eda2400 KZ |
992 | return 1; |
993 | ||
74d2056a MY |
994 | /* path_set includes elt at the middle |
995 | * or as the last element. | |
996 | */ | |
997 | if ((*(tmp - 1) == sep) | |
998 | && ((*(tmp + elt_len) == sep) | |
999 | || (*(tmp + elt_len) == '\0'))) | |
7eda2400 | 1000 | return 1; |
74d2056a | 1001 | |
7eda2400 | 1002 | return 0; |
74d2056a MY |
1003 | } |
1004 | ||
1005 | static int nsfs_xasputs(char **str, | |
1006 | struct lsns_namespace *ns, | |
1007 | struct libmnt_table *tab, | |
1008 | char sep) | |
1009 | { | |
1010 | struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD); | |
1011 | char *expected_root; | |
7eda2400 | 1012 | struct libmnt_fs *fs = NULL; |
74d2056a | 1013 | |
845dac5f | 1014 | xasprintf(&expected_root, "%s:[%ju]", ns_names[ns->type], (uintmax_t)ns->id); |
7eda2400 | 1015 | *str = NULL; |
74d2056a | 1016 | |
7eda2400 KZ |
1017 | while (mnt_table_find_next_fs(tab, itr, is_nsfs_root, |
1018 | expected_root, &fs) == 0) { | |
74d2056a | 1019 | |
7eda2400 KZ |
1020 | const char *tgt = mnt_fs_get_target(fs); |
1021 | ||
1022 | if (!*str) | |
1023 | xasprintf(str, "%s", tgt); | |
1024 | ||
1025 | else if (!is_path_included(*str, tgt, sep)) { | |
1026 | char *tmp = NULL; | |
1027 | ||
1028 | xasprintf(&tmp, "%s%c%s", *str, sep, tgt); | |
1029 | free(*str); | |
1030 | *str = tmp; | |
74d2056a MY |
1031 | } |
1032 | } | |
1033 | free(expected_root); | |
1034 | mnt_free_iter(itr); | |
1035 | ||
1036 | return 1; | |
1037 | } | |
592c1d95 MY |
1038 | |
1039 | static void fill_column(struct lsns *ls, | |
1040 | struct lsns_namespace *ns, | |
1041 | struct lsns_process *proc, | |
1042 | struct libscols_line *line, | |
1043 | size_t column_index) | |
1044 | { | |
1045 | char *str = NULL; | |
1046 | ||
1047 | switch (get_column_id(column_index)) { | |
1048 | case COL_NS: | |
1049 | xasprintf(&str, "%ju", (uintmax_t)ns->id); | |
1050 | break; | |
1051 | case COL_PID: | |
1052 | if (proc) | |
1053 | xasprintf(&str, "%d", (int) proc->pid); | |
1054 | break; | |
1055 | case COL_PPID: | |
1056 | if (proc) | |
1057 | xasprintf(&str, "%d", (int) proc->ppid); | |
1058 | break; | |
1059 | case COL_TYPE: | |
1060 | xasprintf(&str, "%s", ns_names[ns->type]); | |
1061 | break; | |
1062 | case COL_NPROCS: | |
1063 | xasprintf(&str, "%d", ns->nprocs); | |
1064 | break; | |
1065 | case COL_COMMAND: | |
1066 | if (!proc) | |
1067 | break; | |
1068 | str = pid_get_cmdline(proc->pid); | |
1069 | if (!str) | |
1070 | str = pid_get_cmdname(proc->pid); | |
1071 | break; | |
1072 | case COL_PATH: | |
1073 | if (!proc) | |
1074 | break; | |
1075 | xasprintf(&str, "/proc/%d/ns/%s", (int) proc->pid, ns_names[ns->type]); | |
1076 | break; | |
1077 | case COL_UID: | |
1078 | xasprintf(&str, "%d", proc? (int) proc->uid: (int) ns->uid_fallback); | |
1079 | break; | |
1080 | case COL_USER: | |
1081 | xasprintf(&str, "%s", get_id(uid_cache, proc? proc->uid: ns->uid_fallback)->name); | |
1082 | break; | |
1083 | case COL_NETNSID: | |
1084 | if (!proc) | |
1085 | break; | |
1086 | if (ns->type == LSNS_ID_NET) | |
1087 | netnsid_xasputs(&str, proc->netnsid); | |
1088 | break; | |
1089 | case COL_NSFS: | |
1090 | nsfs_xasputs(&str, ns, ls->tab, ls->no_wrap ? ',' : '\n'); | |
1091 | break; | |
1092 | case COL_PNS: | |
1093 | xasprintf(&str, "%ju", (uintmax_t)ns->related_id[RELA_PARENT]); | |
1094 | break; | |
1095 | case COL_ONS: | |
1096 | xasprintf(&str, "%ju", (uintmax_t)ns->related_id[RELA_OWNER]); | |
1097 | break; | |
1098 | default: | |
1099 | break; | |
1100 | } | |
1101 | ||
1102 | if (str && scols_line_refer_data(line, column_index, str) != 0) | |
1103 | err_oom(); | |
1104 | } | |
1105 | ||
1106 | ||
1107 | static int filter_filler_cb( | |
1108 | struct libscols_filter *filter __attribute__((__unused__)), | |
1109 | struct libscols_line *line, | |
1110 | size_t column_index, | |
1111 | void *userdata) | |
1112 | { | |
1113 | struct filler_data *fid = (struct filler_data *) userdata; | |
1114 | ||
1115 | fill_column(fid->ls, fid->ns, fid->proc, line, column_index); | |
1116 | return 0; | |
1117 | } | |
1118 | ||
969d3d15 KZ |
1119 | static void add_scols_line(struct lsns *ls, struct libscols_table *table, |
1120 | struct lsns_namespace *ns, struct lsns_process *proc) | |
8a204562 KZ |
1121 | { |
1122 | size_t i; | |
1123 | struct libscols_line *line; | |
1124 | ||
1125 | assert(ns); | |
1126 | assert(table); | |
1127 | ||
969d3d15 | 1128 | line = scols_table_new_line(table, |
e749d51a | 1129 | (ls->tree == LSNS_TREE_PROCESS && proc) && proc->parent ? proc->parent->outline: |
c67b83c1 MY |
1130 | (ls->tree == LSNS_TREE_PARENT) && ns->related_ns[RELA_PARENT] ? ns->related_ns[RELA_PARENT]->ns_outline: |
1131 | (ls->tree == LSNS_TREE_OWNER) && ns->related_ns[RELA_OWNER] ? ns->related_ns[RELA_OWNER]->ns_outline: | |
e8c56ae2 | 1132 | NULL); |
8a204562 KZ |
1133 | if (!line) { |
1134 | warn(_("failed to add line to output")); | |
1135 | return; | |
1136 | } | |
1137 | ||
592c1d95 MY |
1138 | if (ls->filter) { |
1139 | int status = 0; | |
1140 | struct filler_data fid = { | |
1141 | .ls = ls, | |
1142 | .ns = ns, | |
1143 | .proc = proc, | |
1144 | }; | |
8a204562 | 1145 | |
592c1d95 MY |
1146 | scols_filter_set_filler_cb(ls->filter, |
1147 | filter_filler_cb, (void *) &fid); | |
1148 | ||
1149 | if (scols_line_apply_filter(line, ls->filter, &status)) | |
1150 | err(EXIT_FAILURE, _("failed to apply filter")); | |
1151 | if (status == 0) { | |
1152 | struct libscols_line *x = scols_line_get_parent(line); | |
1153 | ||
1154 | if (x) | |
1155 | scols_line_remove_child(x, line); | |
1156 | ||
1157 | scols_table_remove_line(table, line); | |
1158 | return; | |
8a204562 | 1159 | } |
592c1d95 | 1160 | } |
8a204562 | 1161 | |
592c1d95 MY |
1162 | for (i = 0; i < ncolumns; i++) { |
1163 | if (scols_line_is_filled(line, i)) | |
1164 | continue; | |
1165 | fill_column(ls, ns, proc, line, i); | |
8a204562 | 1166 | } |
969d3d15 | 1167 | |
8d27b605 | 1168 | if (ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT) |
e8c56ae2 | 1169 | ns->ns_outline = line; |
e749d51a | 1170 | else if (proc) |
e8c56ae2 | 1171 | proc->outline = line; |
8a204562 KZ |
1172 | } |
1173 | ||
969d3d15 | 1174 | static struct libscols_table *init_scols_table(struct lsns *ls) |
8a204562 KZ |
1175 | { |
1176 | struct libscols_table *tab; | |
8a204562 | 1177 | size_t i; |
8a204562 KZ |
1178 | |
1179 | tab = scols_new_table(); | |
1180 | if (!tab) { | |
1181 | warn(_("failed to initialize output table")); | |
969d3d15 | 1182 | return NULL; |
8a204562 KZ |
1183 | } |
1184 | ||
1185 | scols_table_enable_raw(tab, ls->raw); | |
1186 | scols_table_enable_json(tab, ls->json); | |
1187 | scols_table_enable_noheadings(tab, ls->no_headings); | |
1188 | ||
1189 | if (ls->json) | |
1190 | scols_table_set_name(tab, "namespaces"); | |
1191 | ||
1192 | for (i = 0; i < ncolumns; i++) { | |
969d3d15 KZ |
1193 | const struct colinfo *col = get_column_info(i); |
1194 | int flags = col->flags; | |
74d2056a | 1195 | struct libscols_column *cl; |
969d3d15 | 1196 | |
deb3f518 | 1197 | if (ls->no_trunc) |
969d3d15 | 1198 | flags &= ~SCOLS_FL_TRUNC; |
8d27b605 | 1199 | if (ls->tree == LSNS_TREE_PROCESS && get_column_id(i) == COL_COMMAND) |
969d3d15 | 1200 | flags |= SCOLS_FL_TREE; |
0a32d39a MY |
1201 | if (ls->no_wrap) |
1202 | flags &= ~SCOLS_FL_WRAP; | |
8d27b605 MY |
1203 | if ((ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT) |
1204 | && get_column_id(i) == COL_NS) { | |
e8c56ae2 MY |
1205 | flags |= SCOLS_FL_TREE; |
1206 | flags &= ~SCOLS_FL_RIGHT; | |
1207 | } | |
8a204562 | 1208 | |
74d2056a MY |
1209 | cl = scols_table_new_column(tab, col->name, col->whint, flags); |
1210 | if (cl == NULL) { | |
8a204562 | 1211 | warnx(_("failed to initialize output column")); |
969d3d15 | 1212 | goto err; |
8a204562 | 1213 | } |
592c1d95 | 1214 | if (ls->json || ls->filter) |
31b25347 KZ |
1215 | scols_column_set_json_type(cl, col->json_type); |
1216 | ||
7eda2400 | 1217 | if (!ls->no_wrap && get_column_id(i) == COL_NSFS) { |
74d2056a MY |
1218 | scols_column_set_wrapfunc(cl, |
1219 | scols_wrapnl_chunksize, | |
1220 | scols_wrapnl_nextchunk, | |
1221 | NULL); | |
1222 | scols_column_set_safechars(cl, "\n"); | |
1223 | } | |
8a204562 KZ |
1224 | } |
1225 | ||
969d3d15 KZ |
1226 | return tab; |
1227 | err: | |
1228 | scols_unref_table(tab); | |
1229 | return NULL; | |
1230 | } | |
1231 | ||
e8c56ae2 MY |
1232 | static void show_namespace(struct lsns *ls, struct libscols_table *tab, |
1233 | struct lsns_namespace *ns, struct lsns_process *proc) | |
1234 | { | |
1235 | /* | |
1236 | * create a tree from owner->owned and/or parent->child relation | |
1237 | */ | |
8d27b605 | 1238 | if (ls->tree == LSNS_TREE_OWNER |
c67b83c1 MY |
1239 | && ns->related_ns[RELA_OWNER] |
1240 | && !ns->related_ns[RELA_OWNER]->ns_outline) | |
1241 | show_namespace(ls, tab, ns->related_ns[RELA_OWNER], ns->related_ns[RELA_OWNER]->proc); | |
8d27b605 | 1242 | else if (ls->tree == LSNS_TREE_PARENT) { |
c67b83c1 MY |
1243 | if (ns->related_ns[RELA_PARENT]) { |
1244 | if (!ns->related_ns[RELA_PARENT]->ns_outline) | |
1245 | show_namespace(ls, tab, ns->related_ns[RELA_PARENT], ns->related_ns[RELA_PARENT]->proc); | |
e8c56ae2 | 1246 | } |
c67b83c1 MY |
1247 | else if (ns->related_ns[RELA_OWNER] && !ns->related_ns[RELA_OWNER]->ns_outline) |
1248 | show_namespace(ls, tab, ns->related_ns[RELA_OWNER], ns->related_ns[RELA_OWNER]->proc); | |
e8c56ae2 MY |
1249 | } |
1250 | ||
1251 | add_scols_line(ls, tab, ns, proc); | |
1252 | } | |
1253 | ||
592c1d95 MY |
1254 | static inline void add_column(int id) |
1255 | { | |
1256 | if (ncolumns >= ARRAY_SIZE(columns)) | |
1257 | errx(EXIT_FAILURE, _("too many columns specified, " | |
1258 | "the limit is %zu columns"), | |
1259 | ARRAY_SIZE(columns) - 1); | |
1260 | columns[ ncolumns++ ] = id; | |
1261 | } | |
1262 | ||
1263 | static void init_scols_filter(struct libscols_table *tb, struct libscols_filter *f) | |
1264 | { | |
1265 | struct libscols_iter *itr; | |
1266 | const char *name = NULL; | |
1267 | int nerrs = 0; | |
1268 | ||
1269 | itr = scols_new_iter(SCOLS_ITER_FORWARD); | |
1270 | if (!itr) | |
1271 | err(EXIT_FAILURE, _("failed to allocate iterator")); | |
1272 | ||
1273 | while (scols_filter_next_holder(f, itr, &name, 0) == 0) { | |
1274 | struct libscols_column *col = scols_table_get_column_by_name(tb, name); | |
1275 | int id = column_name_to_id(name, strlen(name)); | |
1276 | const struct colinfo *ci = id >= 0 ? &infos[id] : NULL; | |
1277 | ||
1278 | if (!ci) { | |
1279 | nerrs++; | |
1280 | continue; /* report all unknown columns */ | |
1281 | } | |
1282 | if (!col) { | |
1283 | add_column(id); | |
1284 | col = scols_table_new_column(tb, ci->name, | |
1285 | ci->whint, SCOLS_FL_HIDDEN); | |
1286 | if (!col) | |
1287 | err(EXIT_FAILURE,_("failed to allocate output column")); | |
1288 | ||
1289 | scols_column_set_json_type(col, ci->json_type); | |
1290 | } | |
1291 | ||
1292 | scols_filter_assign_column(f, itr, name, col); | |
1293 | } | |
1294 | ||
1295 | scols_free_iter(itr); | |
1296 | ||
1297 | if (!nerrs) | |
1298 | return; | |
1299 | ||
1300 | errx(EXIT_FAILURE, _("failed to initialize filter")); | |
1301 | } | |
1302 | ||
969d3d15 KZ |
1303 | static int show_namespaces(struct lsns *ls) |
1304 | { | |
1305 | struct libscols_table *tab; | |
1306 | struct list_head *p; | |
1307 | int rc = 0; | |
1308 | ||
1309 | tab = init_scols_table(ls); | |
1310 | if (!tab) | |
1311 | return -ENOMEM; | |
1312 | ||
592c1d95 MY |
1313 | init_scols_filter(tab, ls->filter); |
1314 | ||
8a204562 KZ |
1315 | list_for_each(p, &ls->namespaces) { |
1316 | struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces); | |
9dfd6019 | 1317 | |
304fbe8b | 1318 | if (ls->fltr_pid != 0 && !namespace_has_process(ns, ls->fltr_pid)) |
9dfd6019 | 1319 | continue; |
eabbd8b7 KZ |
1320 | if (ls->persist && ns->nprocs != 0) |
1321 | continue; | |
9dfd6019 | 1322 | |
e8c56ae2 MY |
1323 | if (!ns->ns_outline) |
1324 | show_namespace(ls, tab, ns, ns->proc); | |
8a204562 KZ |
1325 | } |
1326 | ||
1327 | scols_print_table(tab); | |
8a204562 KZ |
1328 | scols_unref_table(tab); |
1329 | return rc; | |
1330 | } | |
1331 | ||
969d3d15 KZ |
1332 | static void show_process(struct lsns *ls, struct libscols_table *tab, |
1333 | struct lsns_process *proc, struct lsns_namespace *ns) | |
1334 | { | |
1335 | /* | |
1336 | * create a tree from parent->child relation, but only if the parent is | |
1337 | * within the same namespace | |
1338 | */ | |
8d27b605 | 1339 | if (ls->tree == LSNS_TREE_PROCESS |
969d3d15 KZ |
1340 | && proc->parent |
1341 | && !proc->parent->outline | |
1342 | && proc->parent->ns_ids[ns->type] == proc->ns_ids[ns->type]) | |
1343 | show_process(ls, tab, proc->parent, ns); | |
1344 | ||
1345 | add_scols_line(ls, tab, ns, proc); | |
1346 | } | |
1347 | ||
1348 | ||
1349 | static int show_namespace_processes(struct lsns *ls, struct lsns_namespace *ns) | |
1350 | { | |
1351 | struct libscols_table *tab; | |
1352 | struct list_head *p; | |
1353 | ||
1354 | tab = init_scols_table(ls); | |
1355 | if (!tab) | |
1356 | return -ENOMEM; | |
1357 | ||
1358 | list_for_each(p, &ns->processes) { | |
1359 | struct lsns_process *proc = list_entry(p, struct lsns_process, ns_siblings[ns->type]); | |
1880a578 KZ |
1360 | |
1361 | if (!proc->outline) | |
1362 | show_process(ls, tab, proc, ns); | |
969d3d15 KZ |
1363 | } |
1364 | ||
1365 | ||
1366 | scols_print_table(tab); | |
1367 | scols_unref_table(tab); | |
1368 | return 0; | |
1369 | } | |
1370 | ||
395f3bae | 1371 | static void free_lsns_process(struct lsns_process *lsns_p) |
1372 | { | |
1373 | free(lsns_p); | |
1374 | } | |
1375 | ||
1376 | static void free_netnsid_caches(struct netnsid_cache *cache) | |
1377 | { | |
1378 | free(cache); | |
1379 | } | |
1380 | ||
1381 | static void free_lsns_namespace(struct lsns_namespace *lsns_n) | |
1382 | { | |
1383 | free(lsns_n); | |
1384 | } | |
1385 | ||
1386 | static void free_all(struct lsns *ls) | |
1387 | { | |
1388 | list_free(&ls->processes, struct lsns_process, processes, free_lsns_process); | |
1389 | list_free(&netnsids_cache, struct netnsid_cache, netnsids, free_netnsid_caches); | |
1390 | list_free(&ls->namespaces, struct lsns_namespace, namespaces, free_lsns_namespace); | |
1391 | } | |
1392 | ||
592c1d95 MY |
1393 | static struct libscols_filter *new_filter(const char *query) |
1394 | { | |
1395 | struct libscols_filter *f; | |
1396 | ||
1397 | f = scols_new_filter(NULL); | |
1398 | if (!f) | |
1399 | err(EXIT_FAILURE, _("failed to allocate filter")); | |
1400 | if (query && scols_filter_parse_string(f, query) != 0) | |
1401 | errx(EXIT_FAILURE, _("failed to parse \"%s\": %s"), query, | |
1402 | scols_filter_get_errmsg(f)); | |
1403 | return f; | |
1404 | } | |
1405 | ||
86be6a32 | 1406 | static void __attribute__((__noreturn__)) usage(void) |
8a204562 | 1407 | { |
86be6a32 | 1408 | FILE *out = stdout; |
8a204562 KZ |
1409 | |
1410 | fputs(USAGE_HEADER, out); | |
1411 | ||
1412 | fprintf(out, | |
969d3d15 | 1413 | _(" %s [options] [<namespace>]\n"), program_invocation_short_name); |
8a204562 KZ |
1414 | |
1415 | fputs(USAGE_SEPARATOR, out); | |
969d3d15 | 1416 | fputs(_("List system namespaces.\n"), out); |
8a204562 KZ |
1417 | |
1418 | fputs(USAGE_OPTIONS, out); | |
1419 | fputs(_(" -J, --json use JSON output format\n"), out); | |
969d3d15 | 1420 | fputs(_(" -l, --list use list format output\n"), out); |
8a204562 KZ |
1421 | fputs(_(" -n, --noheadings don't print headings\n"), out); |
1422 | fputs(_(" -o, --output <list> define which output columns to use\n"), out); | |
1f7b62e0 | 1423 | fputs(_(" --output-all output all columns\n"), out); |
eabbd8b7 | 1424 | fputs(_(" -P, --persistent namespaces without processes\n"), out); |
969d3d15 | 1425 | fputs(_(" -p, --task <pid> print process namespaces\n"), out); |
8a204562 KZ |
1426 | fputs(_(" -r, --raw use the raw output format\n"), out); |
1427 | fputs(_(" -u, --notruncate don't truncate text in columns\n"), out); | |
0a32d39a | 1428 | fputs(_(" -W, --nowrap don't use multi-line representation\n"), out); |
96dc4f80 | 1429 | fputs(_(" -t, --type <name> namespace type (mnt, net, ipc, user, pid, uts, cgroup, time)\n"), out); |
37b4d8d6 | 1430 | fputs(_(" -T, --tree[=<rel>] use tree format (parent, owner, or process)\n"), out); |
8a204562 KZ |
1431 | |
1432 | fputs(USAGE_SEPARATOR, out); | |
1467a668 | 1433 | fputs(_(" -H, --list-columns list the available columns\n"), out); |
bad4c729 | 1434 | fprintf(out, USAGE_HELP_OPTIONS(24)); |
bad4c729 | 1435 | fprintf(out, USAGE_MAN_TAIL("lsns(8)")); |
8a204562 | 1436 | |
86be6a32 | 1437 | exit(EXIT_SUCCESS); |
8a204562 KZ |
1438 | } |
1439 | ||
1467a668 MY |
1440 | static void __attribute__((__noreturn__)) list_colunms(bool raw, bool json) |
1441 | { | |
1442 | struct libscols_table *col_tb = xcolumn_list_table_new("lsns-columns", stdout, raw, json); | |
1443 | ||
1444 | for (size_t i = 0; i < ARRAY_SIZE(infos); i++) | |
1445 | xcolumn_list_table_append_line(col_tb, infos[i].name, | |
1446 | infos[i].json_type, NULL, | |
1447 | _(infos[i].help)); | |
1448 | ||
1449 | scols_print_table(col_tb); | |
1450 | scols_unref_table(col_tb); | |
1451 | ||
1452 | exit(EXIT_SUCCESS); | |
1453 | } | |
304fbe8b | 1454 | |
7d5036fd MY |
1455 | static dev_t read_nsfs_dev(void) |
1456 | { | |
1457 | struct stat st; | |
1458 | ||
1459 | if (stat("/proc/self/ns/user", &st) < 0) | |
1460 | err(EXIT_FAILURE, _("failed to do stat /proc/self/ns/user")); | |
1461 | ||
1462 | return st.st_dev; | |
1463 | } | |
1464 | ||
8a204562 KZ |
1465 | int main(int argc, char *argv[]) |
1466 | { | |
1467 | struct lsns ls; | |
3387ad72 | 1468 | int c, force_list = 0; |
8a204562 KZ |
1469 | int r = 0; |
1470 | char *outarg = NULL; | |
1f7b62e0 SK |
1471 | enum { |
1472 | OPT_OUTPUT_ALL = CHAR_MAX + 1 | |
1473 | }; | |
8a204562 KZ |
1474 | static const struct option long_opts[] = { |
1475 | { "json", no_argument, NULL, 'J' }, | |
1476 | { "task", required_argument, NULL, 'p' }, | |
1477 | { "help", no_argument, NULL, 'h' }, | |
1478 | { "output", required_argument, NULL, 'o' }, | |
1f7b62e0 | 1479 | { "output-all", no_argument, NULL, OPT_OUTPUT_ALL }, |
eabbd8b7 | 1480 | { "persistent", no_argument, NULL, 'P' }, |
592c1d95 | 1481 | { "filter", required_argument, NULL, 'Q' }, |
8a204562 KZ |
1482 | { "notruncate", no_argument, NULL, 'u' }, |
1483 | { "version", no_argument, NULL, 'V' }, | |
1484 | { "noheadings", no_argument, NULL, 'n' }, | |
0a32d39a | 1485 | { "nowrap", no_argument, NULL, 'W' }, |
969d3d15 | 1486 | { "list", no_argument, NULL, 'l' }, |
8a204562 | 1487 | { "raw", no_argument, NULL, 'r' }, |
304fbe8b | 1488 | { "type", required_argument, NULL, 't' }, |
8d27b605 | 1489 | { "tree", optional_argument, NULL, 'T' }, |
1467a668 | 1490 | { "list-columns", no_argument, NULL, 'H' }, |
8a204562 KZ |
1491 | { NULL, 0, NULL, 0 } |
1492 | }; | |
1493 | ||
1494 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ | |
1495 | { 'J','r' }, | |
eabbd8b7 | 1496 | { 'P','p' }, |
3387ad72 | 1497 | { 'l','T' }, |
8a204562 KZ |
1498 | { 0 } |
1499 | }; | |
1500 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
f593e279 | 1501 | int is_net = 0; |
8a204562 KZ |
1502 | |
1503 | setlocale(LC_ALL, ""); | |
1504 | bindtextdomain(PACKAGE, LOCALEDIR); | |
1505 | textdomain(PACKAGE); | |
2c308875 | 1506 | close_stdout_atexit(); |
8a204562 KZ |
1507 | |
1508 | lsns_init_debug(); | |
8a204562 | 1509 | memset(&ls, 0, sizeof(ls)); |
304fbe8b | 1510 | |
8a204562 KZ |
1511 | INIT_LIST_HEAD(&ls.processes); |
1512 | INIT_LIST_HEAD(&ls.namespaces); | |
4195756e | 1513 | INIT_LIST_HEAD(&netnsids_cache); |
8a204562 KZ |
1514 | |
1515 | while ((c = getopt_long(argc, argv, | |
1467a668 | 1516 | "JlPp:o:nruhVt:T::WQ:H", long_opts, NULL)) != -1) { |
8a204562 KZ |
1517 | |
1518 | err_exclusive_options(c, long_opts, excl, excl_st); | |
1519 | ||
1520 | switch(c) { | |
1521 | case 'J': | |
1522 | ls.json = 1; | |
1523 | break; | |
969d3d15 | 1524 | case 'l': |
3387ad72 | 1525 | force_list = 1; |
969d3d15 | 1526 | break; |
8a204562 KZ |
1527 | case 'o': |
1528 | outarg = optarg; | |
1529 | break; | |
1f7b62e0 SK |
1530 | case OPT_OUTPUT_ALL: |
1531 | for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++) | |
1532 | columns[ncolumns] = ncolumns; | |
1533 | break; | |
eabbd8b7 KZ |
1534 | case 'P': |
1535 | ls.persist = 1; | |
1536 | break; | |
8a204562 | 1537 | case 'p': |
304fbe8b | 1538 | ls.fltr_pid = strtos32_or_err(optarg, _("invalid PID argument")); |
8a204562 | 1539 | break; |
8a204562 KZ |
1540 | case 'n': |
1541 | ls.no_headings = 1; | |
1542 | break; | |
1543 | case 'r': | |
7eda2400 | 1544 | ls.no_wrap = ls.raw = 1; |
8a204562 KZ |
1545 | break; |
1546 | case 'u': | |
deb3f518 | 1547 | ls.no_trunc = 1; |
8a204562 | 1548 | break; |
304fbe8b KZ |
1549 | case 't': |
1550 | { | |
1551 | int type = ns_name2type(optarg); | |
1552 | if (type < 0) | |
1553 | errx(EXIT_FAILURE, _("unknown namespace type: %s"), optarg); | |
1554 | ls.fltr_types[type] = 1; | |
1555 | ls.fltr_ntypes++; | |
7a360346 | 1556 | if (type == LSNS_ID_NET) |
f593e279 | 1557 | is_net = 1; |
304fbe8b KZ |
1558 | break; |
1559 | } | |
0a32d39a MY |
1560 | case 'W': |
1561 | ls.no_wrap = 1; | |
1562 | break; | |
e8c56ae2 | 1563 | case 'T': |
8d27b605 | 1564 | ls.tree = LSNS_TREE_OWNER; |
e8c56ae2 | 1565 | if (optarg) { |
8d27b605 MY |
1566 | if (*optarg == '=') |
1567 | optarg++; | |
e8c56ae2 | 1568 | if (strcmp (optarg, "parent") == 0) |
8d27b605 MY |
1569 | ls.tree = LSNS_TREE_PARENT; |
1570 | else if (strcmp (optarg, "process") == 0) | |
1571 | ls.tree = LSNS_TREE_PROCESS; | |
e8c56ae2 | 1572 | else if (strcmp (optarg, "owner") != 0) |
0f344e24 | 1573 | errx(EXIT_FAILURE, _("unknown tree type: %s"), optarg); |
e8c56ae2 MY |
1574 | } |
1575 | break; | |
592c1d95 MY |
1576 | case 'Q': |
1577 | ls.filter = new_filter(optarg); | |
1578 | break; | |
1467a668 MY |
1579 | case 'H': |
1580 | list_colunms(ls.raw, ls.json); | |
2c308875 KZ |
1581 | |
1582 | case 'h': | |
1583 | usage(); | |
1584 | case 'V': | |
1585 | print_version(EXIT_SUCCESS); | |
8a204562 | 1586 | default: |
677ec86c | 1587 | errtryhelp(EXIT_FAILURE); |
8a204562 KZ |
1588 | } |
1589 | } | |
1590 | ||
304fbe8b KZ |
1591 | if (!ls.fltr_ntypes) { |
1592 | size_t i; | |
7a360346 | 1593 | |
304fbe8b KZ |
1594 | for (i = 0; i < ARRAY_SIZE(ns_names); i++) |
1595 | ls.fltr_types[i] = 1; | |
82a437c7 | 1596 | } |
304fbe8b | 1597 | |
969d3d15 | 1598 | if (optind < argc) { |
304fbe8b | 1599 | if (ls.fltr_pid) |
969d3d15 | 1600 | errx(EXIT_FAILURE, _("--task is mutually exclusive with <namespace>")); |
304fbe8b | 1601 | ls.fltr_ns = strtou64_or_err(argv[optind], _("invalid namespace argument")); |
3387ad72 KZ |
1602 | if (!ls.tree && !force_list) |
1603 | ls.tree = LSNS_TREE_PROCESS; | |
969d3d15 KZ |
1604 | |
1605 | if (!ncolumns) { | |
1606 | columns[ncolumns++] = COL_PID; | |
1607 | columns[ncolumns++] = COL_PPID; | |
1608 | columns[ncolumns++] = COL_USER; | |
1609 | columns[ncolumns++] = COL_COMMAND; | |
1610 | } | |
1611 | } | |
1612 | ||
8a204562 | 1613 | if (!ncolumns) { |
8a204562 KZ |
1614 | columns[ncolumns++] = COL_NS; |
1615 | columns[ncolumns++] = COL_TYPE; | |
1616 | columns[ncolumns++] = COL_NPROCS; | |
1617 | columns[ncolumns++] = COL_PID; | |
a52852d3 | 1618 | columns[ncolumns++] = COL_USER; |
f593e279 | 1619 | if (is_net) { |
7a360346 | 1620 | columns[ncolumns++] = COL_NETNSID; |
74d2056a MY |
1621 | columns[ncolumns++] = COL_NSFS; |
1622 | } | |
8a204562 | 1623 | columns[ncolumns++] = COL_COMMAND; |
3387ad72 KZ |
1624 | |
1625 | if (!ls.tree && !force_list) | |
1626 | ls.tree = LSNS_TREE_PROCESS; | |
8a204562 KZ |
1627 | } |
1628 | ||
e9de8d3b KZ |
1629 | #ifndef USE_NS_GET_API |
1630 | if (ls.tree && ls.tree != LSNS_TREE_PROCESS) | |
1631 | errx(EXIT_FAILURE, _("--tree={parent|owner} is unsupported for your system")); | |
1632 | #endif | |
f593e279 KZ |
1633 | if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), |
1634 | &ncolumns, column_name_to_id) < 0) | |
1635 | return EXIT_FAILURE; | |
7eda2400 | 1636 | |
8a204562 KZ |
1637 | scols_init_debug(0); |
1638 | ||
a52852d3 KZ |
1639 | uid_cache = new_idcache(); |
1640 | if (!uid_cache) | |
1641 | err(EXIT_FAILURE, _("failed to allocate UID cache")); | |
1642 | ||
4195756e | 1643 | #ifdef HAVE_LINUX_NET_NAMESPACE_H |
f593e279 | 1644 | if (has_column(COL_NETNSID)) |
7a360346 | 1645 | netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); |
4195756e | 1646 | #endif |
eabbd8b7 KZ |
1647 | ls.tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); |
1648 | if (!ls.tab) | |
1649 | err(MNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO); | |
74d2056a | 1650 | |
7d5036fd MY |
1651 | ls.nsfs_dev = read_nsfs_dev(); |
1652 | ||
8a204562 KZ |
1653 | r = read_processes(&ls); |
1654 | if (!r) | |
1655 | r = read_namespaces(&ls); | |
969d3d15 | 1656 | if (!r) { |
304fbe8b KZ |
1657 | if (ls.fltr_ns) { |
1658 | struct lsns_namespace *ns = get_namespace(&ls, ls.fltr_ns); | |
969d3d15 KZ |
1659 | |
1660 | if (!ns) | |
82a437c7 | 1661 | errx(EXIT_FAILURE, _("not found namespace: %ju"), (uintmax_t) ls.fltr_ns); |
969d3d15 KZ |
1662 | r = show_namespace_processes(&ls, ns); |
1663 | } else | |
1664 | r = show_namespaces(&ls); | |
1665 | } | |
8a204562 | 1666 | |
592c1d95 | 1667 | scols_unref_filter(ls.filter); |
74d2056a | 1668 | mnt_free_table(ls.tab); |
4195756e MY |
1669 | if (netlink_fd >= 0) |
1670 | close(netlink_fd); | |
a52852d3 | 1671 | free_idcache(uid_cache); |
395f3bae | 1672 | |
1673 | free_all(&ls); | |
1674 | ||
249ba69c TW |
1675 | switch (r) { |
1676 | case 0: return EXIT_SUCCESS; | |
1677 | case -ENOTTY: return EXIT_UNSUPPORTED_IOCTL; | |
1678 | default: return EXIT_FAILURE; | |
1679 | } | |
8a204562 | 1680 | } |