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