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