/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
* lsns(8) - list system namespaces
*
* Copyright (C) 2015 Karel Zak <kzak@redhat.com>
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
- *
- * This program is distributed in the hope that it would be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <libsmartcols.h>
#include <libmount.h>
+# include <stdbool.h>
#ifdef HAVE_LINUX_NET_NAMESPACE_H
-# include <stdbool.h>
# include <sys/socket.h>
# include <linux/netlink.h>
# include <linux/rtnetlink.h>
#include "namespace.h"
#include "idcache.h"
#include "fileutils.h"
+#include "column-list-table.h"
#include "debug.h"
#define LSNS_DEBUG_INIT (1 << 1)
#define LSNS_DEBUG_PROC (1 << 2)
#define LSNS_DEBUG_NS (1 << 3)
+#define LSNS_DEBUG_FILTER (1 << 4)
#define LSNS_DEBUG_ALL 0xFFFF
#define LSNS_NETNS_UNUSABLE -2
#define DBG(m, x) __UL_DBG(lsns, LSNS_DEBUG_, m, x)
#define ON_DBG(m, x) __UL_DBG_CALL(lsns, LSNS_DEBUG_, m, x)
+#define lsns_ioctl(fildes, request, ...) __extension__ ({ \
+ int ret = ioctl(fildes, request, ##__VA_ARGS__); \
+ if (ret == -1 && errno == ENOTTY) \
+ warnx("Unsupported ioctl %s", #request); \
+ ret; })
+
#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(lsns)
#include "debugobj.h"
+#define EXIT_UNSUPPORTED_IOCTL 2
+
static struct idcache *uid_cache = NULL;
/* column IDs */
no_headings: 1,
no_wrap : 1;
+ dev_t nsfs_dev;
struct libmnt_table *tab;
+ struct libscols_filter *filter;
};
struct netnsid_cache {
struct list_head netnsids;
};
+/* "userdata" used by callback for libsmartcols filter */
+struct filler_data {
+ struct lsns *ls;
+ struct lsns_namespace *ns;
+ struct lsns_process *proc;
+};
+
static struct list_head netnsids_cache;
static int netlink_fd = -1;
return &infos[ get_column_id(num) ];
}
-static int get_ns_ino(int dir, const char *nsname, ino_t *ino, ino_t *pino, ino_t *oino)
+static int get_ns_ino(struct path_cxt *pc, const char *nsname, ino_t *ino, ino_t *pino, ino_t *oino)
{
struct stat st;
char path[16];
snprintf(path, sizeof(path), "ns/%s", nsname);
- if (fstatat(dir, path, &st, 0) != 0)
+ *ino = 0;
+ if (ul_path_stat(pc, &st, 0, path) != 0)
return -errno;
*ino = st.st_ino;
#ifdef USE_NS_GET_API
int fd, pfd, ofd;
- fd = openat(dir, path, 0);
+ fd = ul_path_open(pc, 0, path);
if (fd < 0)
return -errno;
if (strcmp(nsname, "pid") == 0 || strcmp(nsname, "user") == 0) {
- if ((pfd = ioctl(fd, NS_GET_PARENT)) < 0) {
- if (errno == EPERM)
+ if ((pfd = lsns_ioctl(fd, NS_GET_PARENT)) < 0) {
+ if (errno == EPERM
+ /* On the test platforms, "build (qemu-user, s390x)" and
+ * "build (qemu-user, riscv64)", the ioctl reported ENOSYS.
+ */
+ || errno == ENOSYS)
goto user;
close(fd);
return -errno;
close(pfd);
}
user:
- if ((ofd = ioctl(fd, NS_GET_USERNS)) < 0) {
- if (errno == EPERM)
+ if ((ofd = lsns_ioctl(fd, NS_GET_USERNS)) < 0) {
+ if (errno == EPERM
+ /* On the test platforms, "build (qemu-user, s390x)" and
+ * "build (qemu-user, riscv64)", the ioctl reported ENOSYS.
+ */
+ || errno == ENOSYS)
goto out;
close(fd);
return -errno;
return 0;
}
-static int parse_proc_stat(FILE *fp, pid_t *pid, char *state, pid_t *ppid)
+static int parse_proc_stat(char *line, pid_t *pid, char *state, pid_t *ppid)
{
- char *line = NULL, *p;
- size_t len = 0;
+ char *p;
int rc;
- if (getline(&line, &len, fp) < 0) {
- rc = -errno;
- goto error;
- }
-
p = strrchr(line, ')');
if (p == NULL ||
sscanf(line, "%d (", pid) != 1 ||
rc = 0;
error:
- free(line);
return rc;
}
return 0;
}
-static int get_netnsid_via_netlink(int dir, const char *path)
+static int get_netnsid_via_netlink(struct path_cxt *pc, const char *path)
{
int netnsid;
int target_fd;
if (netlink_fd < 0)
return LSNS_NETNS_UNUSABLE;
- target_fd = openat(dir, path, O_RDONLY);
+ target_fd = ul_path_open(pc, O_RDONLY, path);
if (target_fd < 0)
return LSNS_NETNS_UNUSABLE;
return netnsid;
}
-static int get_netnsid(int dir, ino_t netino)
+static int get_netnsid(struct path_cxt *pc, ino_t netino)
{
int netnsid;
if (!netnsid_cache_find(netino, &netnsid)) {
- netnsid = get_netnsid_via_netlink(dir, "ns/net");
+ netnsid = get_netnsid_via_netlink(pc, "ns/net");
netnsid_cache_add(netino, netnsid);
}
return netnsid;
}
#else
-static int get_netnsid(int dir __attribute__((__unused__)),
+static int get_netnsid(struct path_cxt *pc __attribute__((__unused__)),
ino_t netino __attribute__((__unused__)))
{
return LSNS_NETNS_UNUSABLE;
}
#endif /* HAVE_LINUX_NET_NAMESPACE_H */
-static int read_process(struct lsns *ls, pid_t pid)
+static struct lsns_namespace *add_namespace_for_nsfd(struct lsns *ls, int fd, ino_t ino);
+
+static void read_open_ns_inos(struct lsns *ls, struct path_cxt *pc)
+{
+ DIR *sub = NULL;
+ struct dirent *d = NULL;
+ char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))];
+
+ while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) {
+ uint64_t num;
+ struct stat st;
+
+ if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */
+ continue;
+
+ snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num);
+
+ if (ul_path_stat(pc, &st, 0, path) == 0
+ && st.st_dev == ls->nsfs_dev) {
+ int fd = ul_path_open(pc, O_RDONLY, path);
+ if (fd >= 0) {
+ add_namespace_for_nsfd(ls, fd, st.st_ino);
+ close(fd);
+ }
+ }
+ }
+}
+
+static int read_process(struct lsns *ls, struct path_cxt *pc)
{
struct lsns_process *p = NULL;
+ int rc = 0;
char buf[BUFSIZ];
- DIR *dir;
- int rc = 0, fd;
- FILE *f = NULL;
size_t i;
- struct stat st;
-
- DBG(PROC, ul_debug("reading %d", (int) pid));
-
- snprintf(buf, sizeof(buf), "/proc/%d", pid);
- dir = opendir(buf);
- if (!dir)
- return -errno;
p = xcalloc(1, sizeof(*p));
p->netnsid = LSNS_NETNS_UNUSABLE;
- if (fstat(dirfd(dir), &st) == 0) {
- p->uid = st.st_uid;
- add_uid(uid_cache, st.st_uid);
- }
+ if (procfs_process_get_uid(pc, &p->uid) == 0)
+ add_uid(uid_cache, p->uid);
- fd = openat(dirfd(dir), "stat", O_RDONLY);
- if (fd < 0) {
- rc = -errno;
+ if ((rc = procfs_process_get_stat(pc, buf, sizeof(buf))) < 0) {
+ DBG(PROC, ul_debug("failed in procfs_process_get_stat() (rc: %d)", rc));
goto done;
}
- if (!(f = fdopen(fd, "r"))) {
- rc = -errno;
+ if ((rc = parse_proc_stat(buf, &p->pid, &p->state, &p->ppid)) < 0) {
+ DBG(PROC, ul_debug("failed in parse_proc_stat() (rc: %d)", rc));
goto done;
}
- rc = parse_proc_stat(f, &p->pid, &p->state, &p->ppid);
- if (rc < 0)
- goto done;
rc = 0;
for (i = 0; i < ARRAY_SIZE(p->ns_ids); i++) {
if (!ls->fltr_types[i])
continue;
- rc = get_ns_ino(dirfd(dir), ns_names[i], &p->ns_ids[i],
+ rc = get_ns_ino(pc, ns_names[i], &p->ns_ids[i],
&p->ns_pids[i], &p->ns_oids[i]);
- if (rc && rc != -EACCES && rc != -ENOENT)
+ if (rc && rc != -EACCES && rc != -ENOENT) {
+ DBG(PROC, ul_debug("failed in get_ns_ino (rc: %d)", rc));
goto done;
- if (i == LSNS_ID_NET)
- p->netnsid = get_netnsid(dirfd(dir), p->ns_ids[i]);
+ }
+ if (p->ns_ids[i] && i == LSNS_ID_NET)
+ p->netnsid = get_netnsid(pc, p->ns_ids[i]);
rc = 0;
}
DBG(PROC, ul_debugobj(p, "new pid=%d", p->pid));
list_add_tail(&p->processes, &ls->processes);
+
+ read_open_ns_inos(ls, pc);
done:
- if (f)
- fclose(f);
- closedir(dir);
if (rc)
free(p);
return rc;
DIR *dir;
struct dirent *d;
int rc = 0;
+ struct path_cxt *pc;
DBG(PROC, ul_debug("opening /proc"));
if (!dir)
return -errno;
+ pc = ul_new_path(NULL);
+ if (!pc)
+ err(EXIT_FAILURE, _("failed to alloc procfs handler"));
+
while ((d = xreaddir(dir))) {
pid_t pid = 0;
if (procfs_dirent_get_pid(d, &pid) != 0)
continue;
- /* TODO: use ul_new_procfs_path(pid, NULL) to read files from /proc/pid/
- */
- rc = read_process(ls, pid);
- if (rc && rc != -EACCES && rc != -ENOENT)
+ DBG(PROC, ul_debug("reading %d", (int) pid));
+ rc = procfs_process_init_path(pc, pid);
+ if (rc < 0) {
+ DBG(PROC, ul_debug("failed in initializing path_cxt for /proc/%d (rc: %d)", (int) pid, rc));
+ /* This failure is acceptable. If a process ($pid) owning
+ * a namespace is gone while running this lsns process,
+ * procfs_process_init_path(pc, $pid) may fail.
+ *
+ * We must reset this `rc' here. If this `d' is the last
+ * dentry in `dir', this read_processes() invocation
+ * returns this `rc'. In the caller context, the
+ * non-zero value returned from read_processes() makes
+ * lsns prints nothing. We should avoid the behavior. */
+ rc = 0;
+ continue;
+ }
+
+ rc = read_process(ls, pc);
+ if (rc && rc != -EACCES && rc != -ENOENT) {
+ DBG(PROC, ul_debug("failed in read_process() (pid: %d, rc: %d)", (int) pid, rc));
break;
+ }
rc = 0;
}
+ ul_unref_path(pc);
+
DBG(PROC, ul_debug("closing /proc"));
closedir(dir);
return rc;
struct lsns_namespace *ns;
int clone_type, lsns_type;
- clone_type = ioctl(fd, NS_GET_NSTYPE);
+ clone_type = lsns_ioctl(fd, NS_GET_NSTYPE);
if (clone_type < 0)
return NULL;
lsns_type = clone_type_to_lsns_type(clone_type);
if (lsns_type < 0 || ls->fltr_types[lsns_type] == 0)
return NULL;
- fd_owner = ioctl(fd, NS_GET_USERNS);
+ fd_owner = lsns_ioctl(fd, NS_GET_USERNS);
if (fd_owner < 0)
goto parent;
if (fstat(fd_owner, &st_owner) < 0)
ino_owner = st_owner.st_ino;
parent:
- fd_parent = ioctl(fd, NS_GET_PARENT);
+ fd_parent = lsns_ioctl(fd, NS_GET_PARENT);
if (fd_parent < 0)
goto add_ns;
if (fstat(fd_parent, &st_parent) < 0)
add_ns:
ns = add_namespace(ls, lsns_type, ino, ino_parent, ino_owner);
- ioctl(fd, NS_GET_OWNER_UID, &ns->uid_fallback);
+ lsns_ioctl(fd, NS_GET_OWNER_UID, &ns->uid_fallback);
add_uid(uid_cache, ns->uid_fallback);
if ((lsns_type == LSNS_ID_USER || lsns_type == LSNS_ID_PID)
if (fd_orphan < 0)
return;
- fd_missing = ioctl(fd_orphan, cmd[rela]);
+ fd_missing = lsns_ioctl(fd_orphan, cmd[rela]);
close(fd_orphan);
if (fd_missing < 0)
return;
return 1;
}
+
+static void fill_column(struct lsns *ls,
+ struct lsns_namespace *ns,
+ struct lsns_process *proc,
+ struct libscols_line *line,
+ size_t column_index)
+{
+ char *str = NULL;
+
+ switch (get_column_id(column_index)) {
+ case COL_NS:
+ xasprintf(&str, "%ju", (uintmax_t)ns->id);
+ break;
+ case COL_PID:
+ if (proc)
+ xasprintf(&str, "%d", (int) proc->pid);
+ break;
+ case COL_PPID:
+ if (proc)
+ xasprintf(&str, "%d", (int) proc->ppid);
+ break;
+ case COL_TYPE:
+ xasprintf(&str, "%s", ns_names[ns->type]);
+ break;
+ case COL_NPROCS:
+ xasprintf(&str, "%d", ns->nprocs);
+ break;
+ case COL_COMMAND:
+ if (!proc)
+ break;
+ str = pid_get_cmdline(proc->pid);
+ if (!str)
+ str = pid_get_cmdname(proc->pid);
+ break;
+ case COL_PATH:
+ if (!proc)
+ break;
+ xasprintf(&str, "/proc/%d/ns/%s", (int) proc->pid, ns_names[ns->type]);
+ break;
+ case COL_UID:
+ xasprintf(&str, "%d", proc? (int) proc->uid: (int) ns->uid_fallback);
+ break;
+ case COL_USER:
+ xasprintf(&str, "%s", get_id(uid_cache, proc? proc->uid: ns->uid_fallback)->name);
+ break;
+ case COL_NETNSID:
+ if (!proc)
+ break;
+ if (ns->type == LSNS_ID_NET)
+ netnsid_xasputs(&str, proc->netnsid);
+ break;
+ case COL_NSFS:
+ nsfs_xasputs(&str, ns, ls->tab, ls->no_wrap ? ',' : '\n');
+ break;
+ case COL_PNS:
+ xasprintf(&str, "%ju", (uintmax_t)ns->related_id[RELA_PARENT]);
+ break;
+ case COL_ONS:
+ xasprintf(&str, "%ju", (uintmax_t)ns->related_id[RELA_OWNER]);
+ break;
+ default:
+ break;
+ }
+
+ if (str && scols_line_refer_data(line, column_index, str) != 0)
+ err_oom();
+}
+
+
+static int filter_filler_cb(
+ struct libscols_filter *filter __attribute__((__unused__)),
+ struct libscols_line *line,
+ size_t column_index,
+ void *userdata)
+{
+ struct filler_data *fid = (struct filler_data *) userdata;
+
+ fill_column(fid->ls, fid->ns, fid->proc, line, column_index);
+ return 0;
+}
+
static void add_scols_line(struct lsns *ls, struct libscols_table *table,
struct lsns_namespace *ns, struct lsns_process *proc)
{
return;
}
- for (i = 0; i < ncolumns; i++) {
- char *str = NULL;
+ if (ls->filter) {
+ int status = 0;
+ struct filler_data fid = {
+ .ls = ls,
+ .ns = ns,
+ .proc = proc,
+ };
- switch (get_column_id(i)) {
- case COL_NS:
- xasprintf(&str, "%ju", (uintmax_t)ns->id);
- break;
- case COL_PID:
- if (proc)
- xasprintf(&str, "%d", (int) proc->pid);
- break;
- case COL_PPID:
- if (proc)
- xasprintf(&str, "%d", (int) proc->ppid);
- break;
- case COL_TYPE:
- xasprintf(&str, "%s", ns_names[ns->type]);
- break;
- case COL_NPROCS:
- xasprintf(&str, "%d", ns->nprocs);
- break;
- case COL_COMMAND:
- if (!proc)
- break;
- str = pid_get_cmdline(proc->pid);
- if (!str)
- str = pid_get_cmdname(proc->pid);
- break;
- case COL_PATH:
- if (!proc)
- break;
- xasprintf(&str, "/proc/%d/ns/%s", (int) proc->pid, ns_names[ns->type]);
- break;
- case COL_UID:
- xasprintf(&str, "%d", proc? (int) proc->uid: (int) ns->uid_fallback);
- break;
- case COL_USER:
- xasprintf(&str, "%s", get_id(uid_cache, proc? proc->uid: ns->uid_fallback)->name);
- break;
- case COL_NETNSID:
- if (!proc)
- break;
- if (ns->type == LSNS_ID_NET)
- netnsid_xasputs(&str, proc->netnsid);
- break;
- case COL_NSFS:
- nsfs_xasputs(&str, ns, ls->tab, ls->no_wrap ? ',' : '\n');
- break;
- case COL_PNS:
- xasprintf(&str, "%ju", (uintmax_t)ns->related_id[RELA_PARENT]);
- break;
- case COL_ONS:
- xasprintf(&str, "%ju", (uintmax_t)ns->related_id[RELA_OWNER]);
- break;
- default:
- break;
+ scols_filter_set_filler_cb(ls->filter,
+ filter_filler_cb, (void *) &fid);
+
+ if (scols_line_apply_filter(line, ls->filter, &status))
+ err(EXIT_FAILURE, _("failed to apply filter"));
+ if (status == 0) {
+ struct libscols_line *x = scols_line_get_parent(line);
+
+ if (x)
+ scols_line_remove_child(x, line);
+
+ scols_table_remove_line(table, line);
+ return;
}
+ }
- if (str && scols_line_refer_data(line, i, str) != 0)
- err_oom();
+ for (i = 0; i < ncolumns; i++) {
+ if (scols_line_is_filled(line, i))
+ continue;
+ fill_column(ls, ns, proc, line, i);
}
if (ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT)
warnx(_("failed to initialize output column"));
goto err;
}
- if (ls->json)
+ if (ls->json || ls->filter)
scols_column_set_json_type(cl, col->json_type);
if (!ls->no_wrap && get_column_id(i) == COL_NSFS) {
add_scols_line(ls, tab, ns, proc);
}
+static inline void add_column(int id)
+{
+ if (ncolumns >= ARRAY_SIZE(columns))
+ errx(EXIT_FAILURE, _("too many columns specified, "
+ "the limit is %zu columns"),
+ ARRAY_SIZE(columns) - 1);
+ columns[ ncolumns++ ] = id;
+}
+
+static void init_scols_filter(struct libscols_table *tb, struct libscols_filter *f)
+{
+ struct libscols_iter *itr;
+ const char *name = NULL;
+ int nerrs = 0;
+
+ itr = scols_new_iter(SCOLS_ITER_FORWARD);
+ if (!itr)
+ err(EXIT_FAILURE, _("failed to allocate iterator"));
+
+ while (scols_filter_next_holder(f, itr, &name, 0) == 0) {
+ struct libscols_column *col = scols_table_get_column_by_name(tb, name);
+ int id = column_name_to_id(name, strlen(name));
+ const struct colinfo *ci = id >= 0 ? &infos[id] : NULL;
+
+ if (!ci) {
+ nerrs++;
+ continue; /* report all unknown columns */
+ }
+ if (!col) {
+ add_column(id);
+ col = scols_table_new_column(tb, ci->name,
+ ci->whint, SCOLS_FL_HIDDEN);
+ if (!col)
+ err(EXIT_FAILURE,_("failed to allocate output column"));
+
+ scols_column_set_json_type(col, ci->json_type);
+ }
+
+ scols_filter_assign_column(f, itr, name, col);
+ }
+
+ scols_free_iter(itr);
+
+ if (!nerrs)
+ return;
+
+ errx(EXIT_FAILURE, _("failed to initialize filter"));
+}
+
static int show_namespaces(struct lsns *ls)
{
struct libscols_table *tab;
if (!tab)
return -ENOMEM;
+ init_scols_filter(tab, ls->filter);
+
list_for_each(p, &ls->namespaces) {
struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces);
list_free(&ls->namespaces, struct lsns_namespace, namespaces, free_lsns_namespace);
}
+static struct libscols_filter *new_filter(const char *query)
+{
+ struct libscols_filter *f;
+
+ f = scols_new_filter(NULL);
+ if (!f)
+ err(EXIT_FAILURE, _("failed to allocate filter"));
+ if (query && scols_filter_parse_string(f, query) != 0)
+ errx(EXIT_FAILURE, _("failed to parse \"%s\": %s"), query,
+ scols_filter_get_errmsg(f));
+ return f;
+}
+
static void __attribute__((__noreturn__)) usage(void)
{
FILE *out = stdout;
- size_t i;
fputs(USAGE_HEADER, out);
fputs(_(" -u, --notruncate don't truncate text in columns\n"), out);
fputs(_(" -W, --nowrap don't use multi-line representation\n"), out);
fputs(_(" -t, --type <name> namespace type (mnt, net, ipc, user, pid, uts, cgroup, time)\n"), out);
- fputs(_(" -T, --tree <rel> use tree format (parent, owner, or process)\n"), out);
+ fputs(_(" -T, --tree[=<rel>] use tree format (parent, owner, or process)\n"), out);
fputs(USAGE_SEPARATOR, out);
- printf(USAGE_HELP_OPTIONS(24));
+ fputs(_(" -H, --list-columns list the available columns\n"), out);
+ fprintf(out, USAGE_HELP_OPTIONS(24));
+ fprintf(out, USAGE_MAN_TAIL("lsns(8)"));
- fputs(USAGE_COLUMNS, out);
- for (i = 0; i < ARRAY_SIZE(infos); i++)
- fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
+ exit(EXIT_SUCCESS);
+}
- printf(USAGE_MAN_TAIL("lsns(8)"));
+static void __attribute__((__noreturn__)) list_colunms(bool raw, bool json)
+{
+ struct libscols_table *col_tb = xcolumn_list_table_new("lsns-columns", stdout, raw, json);
- exit(EXIT_SUCCESS);
+ for (size_t i = 0; i < ARRAY_SIZE(infos); i++)
+ xcolumn_list_table_append_line(col_tb, infos[i].name,
+ infos[i].json_type, NULL,
+ _(infos[i].help));
+
+ scols_print_table(col_tb);
+ scols_unref_table(col_tb);
+
+ exit(EXIT_SUCCESS);
}
+static dev_t read_nsfs_dev(void)
+{
+ struct stat st;
+
+ if (stat("/proc/self/ns/user", &st) < 0)
+ err(EXIT_FAILURE, _("failed to do stat /proc/self/ns/user"));
+
+ return st.st_dev;
+}
int main(int argc, char *argv[])
{
{ "output", required_argument, NULL, 'o' },
{ "output-all", no_argument, NULL, OPT_OUTPUT_ALL },
{ "persistent", no_argument, NULL, 'P' },
+ { "filter", required_argument, NULL, 'Q' },
{ "notruncate", no_argument, NULL, 'u' },
{ "version", no_argument, NULL, 'V' },
{ "noheadings", no_argument, NULL, 'n' },
{ "raw", no_argument, NULL, 'r' },
{ "type", required_argument, NULL, 't' },
{ "tree", optional_argument, NULL, 'T' },
+ { "list-columns", no_argument, NULL, 'H' },
{ NULL, 0, NULL, 0 }
};
INIT_LIST_HEAD(&netnsids_cache);
while ((c = getopt_long(argc, argv,
- "JlPp:o:nruhVt:T::W", long_opts, NULL)) != -1) {
+ "JlPp:o:nruhVt:T::WQ:H", long_opts, NULL)) != -1) {
err_exclusive_options(c, long_opts, excl, excl_st);
errx(EXIT_FAILURE, _("unknown tree type: %s"), optarg);
}
break;
+ case 'Q':
+ ls.filter = new_filter(optarg);
+ break;
+ case 'H':
+ list_colunms(ls.raw, ls.json);
case 'h':
usage();
if (!ls.tab)
err(MNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO);
+ ls.nsfs_dev = read_nsfs_dev();
+
r = read_processes(&ls);
if (!r)
r = read_namespaces(&ls);
r = show_namespaces(&ls);
}
+ scols_unref_filter(ls.filter);
mnt_free_table(ls.tab);
if (netlink_fd >= 0)
close(netlink_fd);
free_all(&ls);
- return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+ switch (r) {
+ case 0: return EXIT_SUCCESS;
+ case -ENOTTY: return EXIT_UNSUPPORTED_IOCTL;
+ default: return EXIT_FAILURE;
+ }
}