]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - sys-utils/lsns.c
lsns: fill the netsid member of lsns_process with reliable value
[thirdparty/util-linux.git] / sys-utils / lsns.c
index 3050b505dd08f4071a7ffbc05aced9b7554955f9..0ab5e26a3079f743c5f2cc4474e95eb48ad2b85b 100644 (file)
@@ -1,4 +1,6 @@
 /*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
  * lsns(8) - list system namespaces
  *
  * Copyright (C) 2015 Karel Zak <kzak@redhat.com>
@@ -7,15 +9,6 @@
  * 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 <linux/net_namespace.h>
+# include <sys/socket.h>
+# include <linux/netlink.h>
+# include <linux/rtnetlink.h>
+# include <linux/net_namespace.h>
+#endif
+
+#ifdef HAVE_LINUX_NSFS_H
+# include <linux/nsfs.h>
+# if defined(NS_GET_NSTYPE) && defined(NS_GET_OWNER_UID)
+#  define USE_NS_GET_API       1
+# endif
 #endif
 
 #include "pathnames.h"
 #include "list.h"
 #include "closestream.h"
 #include "optutils.h"
-#include "procutils.h"
+#include "procfs.h"
 #include "strutils.h"
 #include "namespace.h"
-#include "path.h"
 #include "idcache.h"
+#include "fileutils.h"
+#include "column-list-table.h"
 
 #include "debug.h"
 
@@ -59,6 +60,7 @@ UL_DEBUG_DEFINE_MASKNAMES(lsns) = UL_DEBUG_EMPTY_MASKNAMES;
 #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
@@ -66,9 +68,17 @@ UL_DEBUG_DEFINE_MASKNAMES(lsns) = UL_DEBUG_EMPTY_MASKNAMES;
 #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 */
@@ -84,6 +94,8 @@ enum {
        COL_USER,
        COL_NETNSID,
        COL_NSFS,
+       COL_PNS,                /* parent namespace */
+       COL_ONS,                /* owner namespace */
 };
 
 /* column names */
@@ -107,7 +119,9 @@ static const struct colinfo infos[] = {
        [COL_UID]     = { "UID",     0, SCOLS_FL_RIGHT, N_("UID of the PID"), SCOLS_JSON_NUMBER},
        [COL_USER]    = { "USER",    0, 0, N_("username of the PID")},
        [COL_NETNSID] = { "NETNSID", 0, SCOLS_FL_RIGHT, N_("namespace ID as used by network subsystem")},
-       [COL_NSFS]    = { "NSFS",    0, SCOLS_FL_WRAP, N_("nsfs mountpoint (usually used network subsystem)")}
+       [COL_NSFS]    = { "NSFS",    0, SCOLS_FL_WRAP, N_("nsfs mountpoint (usually used network subsystem)")},
+       [COL_PNS]     = { "PNS",   10, SCOLS_FL_RIGHT, N_("parent namespace identifier (inode number)"), SCOLS_JSON_NUMBER },
+       [COL_ONS]     = { "ONS",   10, SCOLS_FL_RIGHT, N_("owner namespace identifier (inode number)"), SCOLS_JSON_NUMBER },
 };
 
 static int columns[ARRAY_SIZE(infos) * 2];
@@ -120,7 +134,8 @@ enum {
        LSNS_ID_UTS,
        LSNS_ID_IPC,
        LSNS_ID_USER,
-       LSNS_ID_CGROUP
+       LSNS_ID_CGROUP,
+       LSNS_ID_TIME
 };
 
 static char *ns_names[] = {
@@ -130,7 +145,14 @@ static char *ns_names[] = {
        [LSNS_ID_UTS] = "uts",
        [LSNS_ID_IPC] = "ipc",
        [LSNS_ID_USER] = "user",
-       [LSNS_ID_CGROUP] = "cgroup"
+       [LSNS_ID_CGROUP] = "cgroup",
+       [LSNS_ID_TIME] = "time"
+};
+
+enum {
+      RELA_PARENT,
+      RELA_OWNER,
+      MAX_RELA
 };
 
 struct lsns_namespace {
@@ -138,9 +160,14 @@ struct lsns_namespace {
        int type;                       /* LSNS_* */
        int nprocs;
        int netnsid;
+       ino_t related_id[MAX_RELA];
 
        struct lsns_process *proc;
 
+       struct lsns_namespace *related_ns[MAX_RELA];
+       struct libscols_line *ns_outline;
+       uid_t uid_fallback;     /* refer this member if `proc' is NULL. */
+
        struct list_head namespaces;    /* lsns->processes member */
        struct list_head processes;     /* head of lsns_process *siblings */
 };
@@ -153,6 +180,9 @@ struct lsns_process {
        uid_t uid;
 
        ino_t            ns_ids[ARRAY_SIZE(ns_names)];
+       ino_t            ns_pids[ARRAY_SIZE(ns_names)];
+       ino_t            ns_oids[ARRAY_SIZE(ns_names)];
+
        struct list_head ns_siblings[ARRAY_SIZE(ns_names)];
 
        struct list_head processes;     /* list of processes */
@@ -163,6 +193,14 @@ struct lsns_process {
        int netnsid;
 };
 
+
+enum {
+      LSNS_TREE_NONE,
+      LSNS_TREE_PROCESS,
+      LSNS_TREE_OWNER,
+      LSNS_TREE_PARENT,
+};
+
 struct lsns {
        struct list_head processes;
        struct list_head namespaces;
@@ -174,13 +212,16 @@ struct lsns {
 
        unsigned int raw        : 1,
                     json       : 1,
-                    tree       : 1,
-                    list       : 1,
+                    tree       : 2,
+                    persist    : 1,
                     no_trunc   : 1,
                     no_headings: 1,
                     no_wrap    : 1;
 
+       dev_t nsfs_dev;
+
        struct libmnt_table *tab;
+       struct libscols_filter *filter;
 };
 
 struct netnsid_cache {
@@ -189,6 +230,13 @@ 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;
@@ -250,30 +298,74 @@ static inline const struct colinfo *get_column_info(unsigned num)
        return &infos[ get_column_id(num) ];
 }
 
-static int get_ns_ino(int dir, const char *nsname, ino_t *ino)
+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;
+
+       *pino = 0;
+       *oino = 0;
+
+#ifdef USE_NS_GET_API
+       int fd, pfd, ofd;
+       fd = ul_path_open(pc, 0, path);
+       if (fd < 0)
+               return -errno;
+       if (strcmp(nsname, "pid") == 0 || strcmp(nsname, "user") == 0) {
+               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;
+               }
+               if (fstat(pfd, &st) < 0) {
+                       close(pfd);
+                       close(fd);
+                       return -errno;
+               }
+               *pino = st.st_ino;
+               close(pfd);
+       }
+ user:
+       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;
+       }
+       if (fstat(ofd, &st) < 0) {
+               close(ofd);
+               close(fd);
+               return -errno;
+       }
+       *oino = st.st_ino;
+       close(ofd);
+ out:
+       close(fd);
+#endif
        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 ||
@@ -284,7 +376,6 @@ static int parse_proc_stat(FILE *fp, pid_t *pid, char *state, pid_t *ppid)
        rc = 0;
 
 error:
-       free(line);
        return rc;
 }
 
@@ -374,7 +465,7 @@ static int get_netnsid_via_netlink_recv_response(int *netnsid)
        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;
@@ -382,7 +473,7 @@ static int get_netnsid_via_netlink(int dir, const char *path)
        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;
 
@@ -401,62 +492,74 @@ static int get_netnsid_via_netlink(int dir, const char *path)
        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++) {
@@ -465,11 +568,14 @@ static int read_process(struct lsns *ls, pid_t pid)
                if (!ls->fltr_types[i])
                        continue;
 
-               rc = get_ns_ino(dirfd(dir), ns_names[i], &p->ns_ids[i]);
-               if (rc && rc != -EACCES && rc != -ENOENT)
+               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) {
+                       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;
        }
 
@@ -477,10 +583,9 @@ static int read_process(struct lsns *ls, pid_t pid)
 
        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;
@@ -488,26 +593,56 @@ done:
 
 static int read_processes(struct lsns *ls)
 {
-       struct proc_processes *proc = NULL;
-       pid_t pid;
+       DIR *dir;
+       struct dirent *d;
        int rc = 0;
+       struct path_cxt *pc;
 
        DBG(PROC, ul_debug("opening /proc"));
 
-       if (!(proc = proc_open_processes())) {
-               rc = -errno;
-               goto done;
-       }
+       dir = opendir(_PATH_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;
 
-       while (proc_next_pid(proc, &pid) == 0) {
-               rc = read_process(ls, pid);
-               if (rc && rc != -EACCES && rc != -ENOENT)
+               if (procfs_dirent_get_pid(d, &pid) != 0)
+                       continue;
+
+               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;
        }
-done:
+
+       ul_unref_path(pc);
+
        DBG(PROC, ul_debug("closing /proc"));
-       proc_close_processes(proc);
+       closedir(dir);
        return rc;
 }
 
@@ -537,7 +672,8 @@ static int namespace_has_process(struct lsns_namespace *ns, pid_t pid)
        return 0;
 }
 
-static struct lsns_namespace *add_namespace(struct lsns *ls, int type, ino_t ino)
+static struct lsns_namespace *add_namespace(struct lsns *ls, int type, ino_t ino,
+                                           ino_t parent_ino, ino_t owner_ino)
 {
        struct lsns_namespace *ns = xcalloc(1, sizeof(*ns));
 
@@ -551,6 +687,8 @@ static struct lsns_namespace *add_namespace(struct lsns *ls, int type, ino_t ino
 
        ns->type = type;
        ns->id = ino;
+       ns->related_id[RELA_PARENT] = parent_ino;
+       ns->related_id[RELA_OWNER] = owner_ino;
 
        list_add_tail(&ns->namespaces, &ls->namespaces);
        return ns;
@@ -595,13 +733,225 @@ static int netnsid_xasputs(char **str, int netnsid)
        if (netnsid >= 0)
                return xasprintf(str, "%d", netnsid);
 #ifdef NETNSA_NSID_NOT_ASSIGNED
-       else if (netnsid == NETNSA_NSID_NOT_ASSIGNED)
+       if (netnsid == NETNSA_NSID_NOT_ASSIGNED)
                return xasprintf(str, "%s", "unassigned");
 #endif
-       else
-               return 0;
+       return 0;
+}
+
+#ifdef USE_NS_GET_API
+static int clone_type_to_lsns_type(int clone_type)
+{
+       switch (clone_type) {
+       case CLONE_NEWNS:
+               return LSNS_ID_MNT;
+       case CLONE_NEWCGROUP:
+               return LSNS_ID_CGROUP;
+       case CLONE_NEWUTS:
+               return LSNS_ID_UTS;
+       case CLONE_NEWIPC:
+               return LSNS_ID_IPC;
+       case CLONE_NEWUSER:
+               return LSNS_ID_USER;
+       case CLONE_NEWPID:
+               return LSNS_ID_PID;
+       case CLONE_NEWNET:
+               return LSNS_ID_NET;
+#ifdef CLONE_NEWTIME
+       case CLONE_NEWTIME:
+               return LSNS_ID_TIME;
+#endif
+       default:
+               return -1;
+       }
+}
+
+static struct lsns_namespace *add_namespace_for_nsfd(struct lsns *ls, int fd, ino_t ino)
+{
+       int fd_owner = -1, fd_parent = -1;
+       struct stat st_owner, st_parent;
+       ino_t ino_owner = 0, ino_parent = 0;
+       struct lsns_namespace *ns;
+       int clone_type, lsns_type;
+
+       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 = lsns_ioctl(fd, NS_GET_USERNS);
+       if (fd_owner < 0)
+               goto parent;
+       if (fstat(fd_owner, &st_owner) < 0)
+               goto parent;
+       ino_owner = st_owner.st_ino;
+
+ parent:
+       fd_parent = lsns_ioctl(fd, NS_GET_PARENT);
+       if (fd_parent < 0)
+               goto add_ns;
+       if (fstat(fd_parent, &st_parent) < 0)
+               goto add_ns;
+       ino_parent = st_parent.st_ino;
+
+ add_ns:
+       ns = add_namespace(ls, lsns_type, ino, ino_parent, ino_owner);
+       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)
+           && ino_parent != ino && ino_parent != 0) {
+               ns->related_ns[RELA_PARENT] = get_namespace(ls, ino_parent);
+               if (!ns->related_ns[RELA_PARENT]) {
+                       ns->related_ns[RELA_PARENT] = add_namespace_for_nsfd(ls, fd_parent, ino_parent);
+                       if (ino_parent == ino_owner)
+                               ns->related_ns[RELA_OWNER] = ns->related_ns[RELA_PARENT];
+               }
+       }
+
+       if (ns->related_ns[RELA_OWNER] == NULL && ino_owner != 0) {
+               ns->related_ns[RELA_OWNER] = get_namespace(ls, ino_owner);
+               if (!ns->related_ns[RELA_OWNER])
+                       ns->related_ns[RELA_OWNER] = add_namespace_for_nsfd(ls, fd_owner, ino_owner);
+       }
+
+       if (fd_owner >= 0)
+               close(fd_owner);
+       if (fd_parent >= 0)
+               close(fd_parent);
+
+       return ns;
+}
+
+static void interpolate_missing_namespaces(struct lsns *ls, struct lsns_namespace *orphan, int rela)
+{
+       const int cmd[MAX_RELA] = {
+               [RELA_PARENT] = NS_GET_PARENT,
+               [RELA_OWNER] = NS_GET_USERNS
+       };
+       char buf[BUFSIZ];
+       int fd_orphan, fd_missing;
+       struct stat st;
+
+       orphan->related_ns[rela] = get_namespace(ls, orphan->related_id[rela]);
+       if (orphan->related_ns[rela])
+               return;
+
+       snprintf(buf, sizeof(buf), "/proc/%d/ns/%s", orphan->proc->pid, ns_names[orphan->type]);
+       fd_orphan = open(buf, O_RDONLY);
+       if (fd_orphan < 0)
+               return;
+
+       fd_missing = lsns_ioctl(fd_orphan, cmd[rela]);
+       close(fd_orphan);
+       if (fd_missing < 0)
+               return;
+
+       if (fstat(fd_missing, &st) < 0
+           || st.st_ino != orphan->related_id[rela]) {
+               close(fd_missing);
+               return;
+       }
+
+       orphan->related_ns[rela] = add_namespace_for_nsfd(ls, fd_missing, orphan->related_id[rela]);
+       close(fd_missing);
+}
+
+static void read_related_namespaces(struct lsns *ls)
+{
+       struct list_head *p;
+       struct lsns_namespace *orphan[2] = {NULL, NULL};
+       int rela;
+
+       list_for_each(p, &ls->namespaces) {
+               struct lsns_namespace *ns = list_entry(p, struct lsns_namespace, namespaces);
+               struct list_head *pp;
+               list_for_each(pp, &ls->namespaces) {
+                       struct lsns_namespace *pns = list_entry(pp, struct lsns_namespace, namespaces);
+                       if (ns->type == LSNS_ID_USER
+                           || ns->type == LSNS_ID_PID) {
+                               if (ns->related_id[RELA_PARENT] == pns->id)
+                                       ns->related_ns[RELA_PARENT] = pns;
+                               if (ns->related_id[RELA_OWNER] == pns->id)
+                                       ns->related_ns[RELA_OWNER] = pns;
+                               if (ns->related_ns[RELA_PARENT] && ns->related_ns[RELA_OWNER])
+                                       break;
+                       } else {
+                               if (ns->related_id[RELA_OWNER] == pns->id) {
+                                       ns->related_ns[RELA_OWNER] = pns;
+                                       break;
+                               }
+                       }
+               }
+
+               /* lsns scans /proc/[0-9]+ for finding namespaces.
+                * So if a namespace has no process, lsns cannot
+                * find it. Here we call it a missing namespace.
+                *
+                * If the id for a related namesspce is known but
+                * namespace for the id is not found, there must
+                * be orphan namespaces. A missing namespace is an
+                * owner or a parent of the orphan namespace.
+                */
+               for (rela = 0; rela < MAX_RELA; rela++) {
+                       if (ns->related_id[rela] != 0
+                           && ns->related_ns[rela] == NULL) {
+                               ns->related_ns[rela] = orphan[rela];
+                               orphan[rela] = ns;
+                       }
+               }
+       }
+
+       for (rela = 0; rela < MAX_RELA; rela++) {
+               while (orphan[rela]) {
+                       struct lsns_namespace *current = orphan[rela];
+                       orphan[rela] = orphan[rela]->related_ns[rela];
+                       current->related_ns[rela] = NULL;
+                       interpolate_missing_namespaces(ls, current, rela);
+               }
+       }
+}
+
+static int read_persistent_namespaces(struct lsns *ls)
+{
+       struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
+       struct libmnt_fs *fs = NULL;
+
+       while (mnt_table_next_fs(ls->tab, itr, &fs) == 0) {
+               const char *root;
+               char *p, *end = NULL;
+               ino_t ino;
+               int fd;
+
+               if (!mnt_fs_match_fstype(fs, "nsfs"))
+                       continue;
+               root = mnt_fs_get_root(fs);
+               if (!root || !(p = strchr(root, '[')))
+                       continue;
+
+               errno = 0;
+               ino = strtoumax(++p, &end, 10);
+               if (!end || *end != ']' || errno != 0)
+                       continue;
+               if (get_namespace(ls, ino))
+                       continue;
+
+               fd = open(mnt_fs_get_target(fs), O_RDONLY);
+               if (fd < 0)
+                       continue;
+
+               add_namespace_for_nsfd(ls, fd, ino);
+               close(fd);
+       }
+
+       mnt_free_iter(itr);
+       return 0;
 }
 
+#endif /* USE_NS_GET_API */
+
 static int read_namespaces(struct lsns *ls)
 {
        struct list_head *p;
@@ -617,7 +967,8 @@ static int read_namespaces(struct lsns *ls)
                        if (proc->ns_ids[i] == 0)
                                continue;
                        if (!(ns = get_namespace(ls, proc->ns_ids[i]))) {
-                               ns = add_namespace(ls, i, proc->ns_ids[i]);
+                               ns = add_namespace(ls, i, proc->ns_ids[i],
+                                                  proc->ns_pids[i], proc->ns_oids[i]);
                                if (!ns)
                                        return -ENOMEM;
                        }
@@ -625,6 +976,12 @@ static int read_namespaces(struct lsns *ls)
                }
        }
 
+#ifdef USE_NS_GET_API
+       read_persistent_namespaces(ls);
+
+       if (ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT)
+               read_related_namespaces(ls);
+#endif
        list_sort(&ls->namespaces, cmp_namespaces, NULL);
 
        return 0;
@@ -705,6 +1062,87 @@ static int nsfs_xasputs(char **str,
 
        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)
 {
@@ -715,61 +1153,49 @@ static void add_scols_line(struct lsns *ls, struct libscols_table *table,
        assert(table);
 
        line = scols_table_new_line(table,
-                       ls->tree && proc->parent ? proc->parent->outline : NULL);
+                       (ls->tree == LSNS_TREE_PROCESS && proc) && proc->parent ? proc->parent->outline:
+                       (ls->tree == LSNS_TREE_PARENT)  && ns->related_ns[RELA_PARENT] ? ns->related_ns[RELA_PARENT]->ns_outline:
+                       (ls->tree == LSNS_TREE_OWNER)   && ns->related_ns[RELA_OWNER]  ? ns->related_ns[RELA_OWNER]->ns_outline:
+                       NULL);
        if (!line) {
                warn(_("failed to add line to output"));
                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:
-                       xasprintf(&str, "%d", (int) proc->pid);
-                       break;
-               case COL_PPID:
-                       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:
-                       str = proc_get_command(proc->pid);
-                       if (!str)
-                               str = proc_get_command_name(proc->pid);
-                       break;
-               case COL_PATH:
-                       xasprintf(&str, "/proc/%d/ns/%s", (int) proc->pid, ns_names[ns->type]);
-                       break;
-               case COL_UID:
-                       xasprintf(&str, "%d", (int) proc->uid);
-                       break;
-               case COL_USER:
-                       xasprintf(&str, "%s", get_id(uid_cache, proc->uid)->name);
-                       break;
-               case COL_NETNSID:
-                       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;
-               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);
        }
 
-       proc->outline = line;
+       if (ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT)
+               ns->ns_outline = line;
+       else if (proc)
+               proc->outline = line;
 }
 
 static struct libscols_table *init_scols_table(struct lsns *ls)
@@ -797,17 +1223,22 @@ static struct libscols_table *init_scols_table(struct lsns *ls)
 
                if (ls->no_trunc)
                       flags &= ~SCOLS_FL_TRUNC;
-               if (ls->tree && get_column_id(i) == COL_COMMAND)
+               if (ls->tree == LSNS_TREE_PROCESS && get_column_id(i) == COL_COMMAND)
                        flags |= SCOLS_FL_TREE;
                if (ls->no_wrap)
                        flags &= ~SCOLS_FL_WRAP;
+               if ((ls->tree == LSNS_TREE_OWNER || ls->tree == LSNS_TREE_PARENT)
+                   && get_column_id(i) == COL_NS) {
+                       flags |= SCOLS_FL_TREE;
+                       flags &= ~SCOLS_FL_RIGHT;
+               }
 
                cl = scols_table_new_column(tab, col->name, col->whint, flags);
                if (cl == NULL) {
                        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) {
@@ -825,6 +1256,77 @@ err:
        return NULL;
 }
 
+static void show_namespace(struct lsns *ls, struct libscols_table *tab,
+                          struct lsns_namespace *ns, struct lsns_process *proc)
+{
+       /*
+        * create a tree from owner->owned and/or parent->child relation
+        */
+       if (ls->tree == LSNS_TREE_OWNER
+           && ns->related_ns[RELA_OWNER]
+           && !ns->related_ns[RELA_OWNER]->ns_outline)
+               show_namespace(ls, tab, ns->related_ns[RELA_OWNER], ns->related_ns[RELA_OWNER]->proc);
+       else if (ls->tree == LSNS_TREE_PARENT) {
+               if (ns->related_ns[RELA_PARENT]) {
+                       if (!ns->related_ns[RELA_PARENT]->ns_outline)
+                               show_namespace(ls, tab, ns->related_ns[RELA_PARENT], ns->related_ns[RELA_PARENT]->proc);
+               }
+               else if (ns->related_ns[RELA_OWNER] && !ns->related_ns[RELA_OWNER]->ns_outline)
+                       show_namespace(ls, tab, ns->related_ns[RELA_OWNER], ns->related_ns[RELA_OWNER]->proc);
+       }
+
+       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;
@@ -835,13 +1337,18 @@ static int show_namespaces(struct lsns *ls)
        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);
 
                if (ls->fltr_pid != 0 && !namespace_has_process(ns, ls->fltr_pid))
                        continue;
+               if (ls->persist && ns->nprocs != 0)
+                       continue;
 
-               add_scols_line(ls, tab, ns, ns->proc);
+               if (!ns->ns_outline)
+                       show_namespace(ls, tab, ns, ns->proc);
        }
 
        scols_print_table(tab);
@@ -856,7 +1363,7 @@ static void show_process(struct lsns *ls, struct libscols_table *tab,
         * create a tree from parent->child relation, but only if the parent is
         * within the same namespace
         */
-       if (ls->tree
+       if (ls->tree == LSNS_TREE_PROCESS
            && proc->parent
            && !proc->parent->outline
            && proc->parent->ns_ids[ns->type] == proc->ns_ids[ns->type])
@@ -888,10 +1395,44 @@ static int show_namespace_processes(struct lsns *ls, struct lsns_namespace *ns)
        return 0;
 }
 
+static void free_lsns_process(struct lsns_process *lsns_p)
+{
+       free(lsns_p);
+}
+
+static void free_netnsid_caches(struct netnsid_cache *cache)
+{
+       free(cache);
+}
+
+static void free_lsns_namespace(struct lsns_namespace *lsns_n)
+{
+       free(lsns_n);
+}
+
+static void free_all(struct lsns *ls)
+{
+       list_free(&ls->processes, struct lsns_process, processes, free_lsns_process);
+       list_free(&netnsids_cache, struct netnsid_cache, netnsids, free_netnsid_caches);
+       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);
 
@@ -907,29 +1448,51 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" -n, --noheadings       don't print headings\n"), out);
        fputs(_(" -o, --output <list>    define which output columns to use\n"), out);
        fputs(_("     --output-all       output all columns\n"), out);
+       fputs(_(" -P, --persistent       namespaces without processes\n"), out);
        fputs(_(" -p, --task <pid>       print process namespaces\n"), out);
        fputs(_(" -r, --raw              use the raw output format\n"), 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)\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(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);
+}
+
+static void __attribute__((__noreturn__)) list_colunms(bool raw, bool json)
+{
+   struct libscols_table *col_tb = xcolumn_list_table_new("lsns-columns", stdout, raw, json);
 
-       printf(USAGE_MAN_TAIL("lsns(8)"));
+   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));
 
-       exit(EXIT_SUCCESS);
+   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[])
 {
        struct lsns ls;
-       int c;
+       int c, force_list = 0;
        int r = 0;
        char *outarg = NULL;
        enum {
@@ -941,6 +1504,8 @@ int main(int argc, char *argv[])
                { "help",       no_argument,       NULL, 'h' },
                { "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' },
@@ -948,11 +1513,15 @@ int main(int argc, char *argv[])
                { "list",       no_argument,       NULL, 'l' },
                { "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 }
        };
 
        static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
                { 'J','r' },
+               { 'P','p' },
+               { 'l','T' },
                { 0 }
        };
        int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
@@ -961,7 +1530,7 @@ int main(int argc, char *argv[])
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
        lsns_init_debug();
        memset(&ls, 0, sizeof(ls));
@@ -971,7 +1540,7 @@ int main(int argc, char *argv[])
        INIT_LIST_HEAD(&netnsids_cache);
 
        while ((c = getopt_long(argc, argv,
-                               "Jlp:o:nruhVt:W", long_opts, NULL)) != -1) {
+                               "JlPp:o:nruhVt:T::WQ:H", long_opts, NULL)) != -1) {
 
                err_exclusive_options(c, long_opts, excl, excl_st);
 
@@ -980,7 +1549,7 @@ int main(int argc, char *argv[])
                        ls.json = 1;
                        break;
                case 'l':
-                       ls.list = 1;
+                       force_list = 1;
                        break;
                case 'o':
                        outarg = optarg;
@@ -989,14 +1558,12 @@ int main(int argc, char *argv[])
                        for (ncolumns = 0; ncolumns < ARRAY_SIZE(infos); ncolumns++)
                                columns[ncolumns] = ncolumns;
                        break;
-               case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
+               case 'P':
+                       ls.persist = 1;
+                       break;
                case 'p':
                        ls.fltr_pid = strtos32_or_err(optarg, _("invalid PID argument"));
                        break;
-               case 'h':
-                       usage();
                case 'n':
                        ls.no_headings = 1;
                        break;
@@ -1020,6 +1587,29 @@ int main(int argc, char *argv[])
                case 'W':
                        ls.no_wrap = 1;
                        break;
+               case 'T':
+                       ls.tree = LSNS_TREE_OWNER;
+                       if (optarg) {
+                               if (*optarg == '=')
+                                       optarg++;
+                               if (strcmp (optarg, "parent") == 0)
+                                       ls.tree = LSNS_TREE_PARENT;
+                               else if (strcmp (optarg, "process") == 0)
+                                       ls.tree = LSNS_TREE_PROCESS;
+                               else if (strcmp (optarg, "owner") != 0)
+                                       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();
+               case 'V':
+                       print_version(EXIT_SUCCESS);
                default:
                        errtryhelp(EXIT_FAILURE);
                }
@@ -1036,7 +1626,8 @@ int main(int argc, char *argv[])
                if (ls.fltr_pid)
                        errx(EXIT_FAILURE, _("--task is mutually exclusive with <namespace>"));
                ls.fltr_ns = strtou64_or_err(argv[optind], _("invalid namespace argument"));
-               ls.tree = ls.list ? 0 : 1;
+               if (!ls.tree && !force_list)
+                       ls.tree = LSNS_TREE_PROCESS;
 
                if (!ncolumns) {
                        columns[ncolumns++] = COL_PID;
@@ -1057,8 +1648,15 @@ int main(int argc, char *argv[])
                        columns[ncolumns++] = COL_NSFS;
                }
                columns[ncolumns++] = COL_COMMAND;
+
+               if (!ls.tree && !force_list)
+                       ls.tree = LSNS_TREE_PROCESS;
        }
 
+#ifndef USE_NS_GET_API
+       if (ls.tree && ls.tree != LSNS_TREE_PROCESS)
+               errx(EXIT_FAILURE, _("--tree={parent|owner} is unsupported for your system"));
+#endif
        if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
                                  &ncolumns, column_name_to_id) < 0)
                return EXIT_FAILURE;
@@ -1073,11 +1671,11 @@ int main(int argc, char *argv[])
        if (has_column(COL_NETNSID))
                netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
 #endif
-       if (has_column(COL_NSFS)) {
-               ls.tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
-               if (!ls.tab)
-                       err(MNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO);
-       }
+       ls.tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
+       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)
@@ -1093,9 +1691,17 @@ int main(int argc, char *argv[])
                        r = show_namespaces(&ls);
        }
 
+       scols_unref_filter(ls.filter);
        mnt_free_table(ls.tab);
        if (netlink_fd >= 0)
                close(netlink_fd);
        free_idcache(uid_cache);
-       return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+       free_all(&ls);
+
+       switch (r) {
+               case 0: return EXIT_SUCCESS;
+               case -ENOTTY: return EXIT_UNSUPPORTED_IOCTL;
+               default: return EXIT_FAILURE;
+       }
 }