aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarel Zak <kzak@redhat.com>2024-04-02 12:58:02 +0200
committerKarel Zak <kzak@redhat.com>2024-04-02 12:58:02 +0200
commitc984aa7426a64ed2dbad789ff568e600db40aaa3 (patch)
tree0aec5efafc82886154921d6754ee4447f419d1e3
parent3400ca5a9f3174308fea9c106ee3678bd58deabd (diff)
parent0b517b611e42c813f4450442e0421a3d2bd021f6 (diff)
downloadutil-linux-c984aa7426a64ed2dbad789ff568e600db40aaa3.tar.gz
Merge branch 'lsns--Q' of https://github.com/masatake/util-linux
* 'lsns--Q' of https://github.com/masatake/util-linux: tests: (lsns) add a case testing -Q, --filter option tests: (test_mkfds::userns) add a new factory tests: (test_mkfds::multiplexing) fix the factory description lsns: add -H, --list-columns option lsns: implement -Q, --filter option lsfd: (man) fix the decoration of an optional parameter lsns: add a missing '=' character in the help message lsns: (man) make the namespace parameter optional lsblk: (refactor) refer to a parameter instead of a file static var
-rw-r--r--misc-utils/lsblk.c2
-rw-r--r--misc-utils/lsfd.1.adoc2
-rw-r--r--sys-utils/lsns.8.adoc18
-rw-r--r--sys-utils/lsns.c274
-rw-r--r--tests/expected/lsns/filter2
-rw-r--r--tests/helpers/test_mkfds.c106
-rwxr-xr-xtests/ts/lsns/filter65
7 files changed, 397 insertions, 72 deletions
diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c
index 30bd2edfdf..fbc0356451 100644
--- a/misc-utils/lsblk.c
+++ b/misc-utils/lsblk.c
@@ -2264,7 +2264,7 @@ static void init_scols_filter(struct libscols_table *tb, struct libscols_filter
}
if (!col) {
add_column(id);
- col = scols_table_new_column(lsblk->table, ci->name,
+ col = scols_table_new_column(tb, ci->name,
ci->whint, SCOLS_FL_HIDDEN);
if (!col)
err(EXIT_FAILURE,_("failed to allocate output column"));
diff --git a/misc-utils/lsfd.1.adoc b/misc-utils/lsfd.1.adoc
index f7d398da06..05cc9e9aee 100644
--- a/misc-utils/lsfd.1.adoc
+++ b/misc-utils/lsfd.1.adoc
@@ -90,7 +90,7 @@ counters by specifying this option multiple times.
+
See also *COUNTER EXAMPLES*.
-*--summary*[=_when_]::
+*--summary*[**=**__when__]::
This option controls summary lines output. The optional argument _when_
can be *only*, *append* or *never*. If the _when_ argument is omitted,
it defaults to *only*.
diff --git a/sys-utils/lsns.8.adoc b/sys-utils/lsns.8.adoc
index ea6c87e9d1..ec79cf0ed3 100644
--- a/sys-utils/lsns.8.adoc
+++ b/sys-utils/lsns.8.adoc
@@ -17,7 +17,7 @@ lsns - list namespaces
== SYNOPSIS
-*lsns* [options] _namespace_
+*lsns* [options] [_namespace_]
== DESCRIPTION
@@ -31,6 +31,9 @@ Note that *lsns* reads information directly from the _/proc_ filesystem and for
== OPTIONS
+*-H*, *--list-columns*::
+List the available columns, use with *--json* or *--raw* to get output in machine-readable format.
+
*-J*, *--json*::
Use JSON output format.
@@ -55,6 +58,16 @@ Display only the namespaces without processes (aka persistent namespaces), creat
*-p*, *--task* _PID_::
Display only the namespaces held by the process with this _PID_.
+*-Q*, *--filter* _expr_::
+Print only the namespaces that meet the conditions specified by the expr.
++
+This feature is EXPERIMENTAL. See also *scols-filter*(5). For example
+exclude root as username, but print every namespaces more than one process
+belongs to:
+____
+ lsns --filter 'USER != "root" and NPROCS > 1'
+____
+
*-r*, *--raw*::
Use the raw output format.
@@ -67,7 +80,7 @@ Do not truncate text in columns.
*-W*, *--nowrap*::
Do not use multi-line text in columns.
-*-T*, *--tree* _rel_::
+*-T*, *--tree*[**=**__rel__]::
Use tree-like output format. If *process* is given as _rel_, print process tree(s) in each name space. This is default when *--tree* is not specified. If *parent* is given, print tree(s) constructed by the parent/child relationship. If *owner* is given, print tree(s) constructed by the owner/owned relationship. *owner* is used as default when _rel_ is omitted.
include::man-common/help-version.adoc[]
@@ -95,6 +108,7 @@ mailto:kzak@redhat.com[Karel Zak]
*namespaces*(7),
*ioctl_ns*(2),
*ip-netns*(8)
+*scols-filter*(5)
include::man-common/bugreports.adoc[]
diff --git a/sys-utils/lsns.c b/sys-utils/lsns.c
index e68bdbed46..f444f5e1a5 100644
--- a/sys-utils/lsns.c
+++ b/sys-utils/lsns.c
@@ -22,9 +22,9 @@
#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>
@@ -50,6 +50,7 @@
#include "namespace.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
@@ -218,6 +220,7 @@ struct lsns {
struct libmnt_table *tab;
+ struct libscols_filter *filter;
};
struct netnsid_cache {
@@ -226,6 +229,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;
@@ -1012,6 +1022,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)
{
@@ -1031,66 +1122,34 @@ static void add_scols_line(struct lsns *ls, struct libscols_table *table,
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)
@@ -1139,7 +1198,7 @@ static struct libscols_table *init_scols_table(struct lsns *ls)
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) {
@@ -1179,6 +1238,55 @@ static void show_namespace(struct lsns *ls, struct libscols_table *tab,
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;
@@ -1189,6 +1297,8 @@ 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);
@@ -1267,10 +1377,22 @@ static void free_all(struct lsns *ls)
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);
@@ -1292,20 +1414,30 @@ static void __attribute__((__noreturn__)) usage(void)
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);
+ fputs(_(" -H, --list-columns list the available columns\n"), out);
fprintf(out, USAGE_HELP_OPTIONS(24));
-
- fputs(USAGE_COLUMNS, out);
- for (i = 0; i < ARRAY_SIZE(infos); i++)
- fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
-
fprintf(out, USAGE_MAN_TAIL("lsns(8)"));
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);
+
+ 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);
+}
int main(int argc, char *argv[])
{
@@ -1323,6 +1455,7 @@ 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' },
@@ -1331,6 +1464,7 @@ int main(int argc, char *argv[])
{ "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 }
};
@@ -1356,7 +1490,7 @@ int main(int argc, char *argv[])
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);
@@ -1416,6 +1550,11 @@ int main(int argc, char *argv[])
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();
@@ -1500,6 +1639,7 @@ 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);
diff --git a/tests/expected/lsns/filter b/tests/expected/lsns/filter
new file mode 100644
index 0000000000..ac79b6b367
--- /dev/null
+++ b/tests/expected/lsns/filter
@@ -0,0 +1,2 @@
+-Q: pid == PID
+--filter: pid == PID
diff --git a/tests/helpers/test_mkfds.c b/tests/helpers/test_mkfds.c
index b0d34c6050..dd0a128a88 100644
--- a/tests/helpers/test_mkfds.c
+++ b/tests/helpers/test_mkfds.c
@@ -3207,6 +3207,99 @@ static void *make_mmap(const struct factory *factory, struct fdesc fdescs[] _U_,
return data;
}
+/* Do as unshare --map-root-user. */
+static void map_root_user(uid_t uid, uid_t gid)
+{
+ char buf[BUFSIZ];
+ int n;
+ int mapfd;
+ int r;
+
+ n = snprintf(buf, sizeof(buf), "0 %d 1", uid);
+ mapfd = open("/proc/self/uid_map", O_WRONLY);
+ if (mapfd < 0)
+ err(EXIT_FAILURE,
+ "failed to open /proc/self/uid_map");
+ r = write (mapfd, buf, n);
+ if (r < 0)
+ err(EXIT_FAILURE,
+ "failed to write to /proc/self/uid_map");
+ if (r != n)
+ errx(EXIT_FAILURE,
+ "failed to write to /proc/self/uid_map");
+ close(mapfd);
+
+ mapfd = open("/proc/self/setgroups", O_WRONLY);
+ if (mapfd < 0)
+ err(EXIT_FAILURE,
+ "failed to open /proc/self/setgroups");
+ r = write (mapfd, "deny", 4);
+ if (r < 0)
+ err(EXIT_FAILURE,
+ "failed to write to /proc/self/setgroups");
+ if (r != 4)
+ errx(EXIT_FAILURE,
+ "failed to write to /proc/self/setgroups");
+ close(mapfd);
+
+ n = snprintf(buf, sizeof(buf), "0 %d 1", gid);
+ mapfd = open("/proc/self/gid_map", O_WRONLY);
+ if (mapfd < 0)
+ err(EXIT_FAILURE,
+ "failed to open /proc/self/gid_map");
+ r = write (mapfd, buf, n);
+ if (r < 0)
+ err(EXIT_FAILURE,
+ "failed to write to /proc/self/gid_map");
+ if (r != n)
+ errx(EXIT_FAILURE,
+ "failed to write to /proc/self/gid_map");
+ close(mapfd);
+}
+
+static void *make_userns(const struct factory *factory _U_, struct fdesc fdescs[],
+ int argc _U_, char ** argv _U_)
+{
+ uid_t uid = geteuid();
+ uid_t gid = getegid();
+
+ if (unshare(CLONE_NEWUSER) < 0)
+ err((errno == EPERM? EXIT_EPERM: EXIT_FAILURE),
+ "failed in the 1st unshare(2)");
+
+ map_root_user(uid, gid);
+
+ int userns = open("/proc/self/ns/user", O_RDONLY);
+ if (userns < 0)
+ err(EXIT_FAILURE, "failed to open /proc/self/ns/user for the new user ns");
+
+ if (unshare(CLONE_NEWUSER) < 0) {
+ int e = errno;
+ close(userns);
+ errno = e;
+ err((errno == EPERM? EXIT_EPERM: EXIT_FAILURE),
+ "failed in the 2nd unshare(2)");
+ }
+
+ if (userns != fdescs[0].fd) {
+ if (dup2(userns, fdescs[0].fd) < 0) {
+ int e = errno;
+ close(userns);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", userns, fdescs[0].fd);
+ }
+ close(userns);
+ }
+
+ fdescs[0] = (struct fdesc){
+ .fd = fdescs[0].fd,
+ .close = close_fdesc,
+ .data = NULL
+ };
+
+ return NULL;
+}
+
static void free_mmap(const struct factory * factory _U_, void *data)
{
munmap(((struct mmap_data *)data)->addr,
@@ -3999,7 +4092,7 @@ static const struct factory factories[] = {
},
{
.name = "multiplexing",
- .desc = "making pipes monitored by multiplexers",
+ .desc = "make pipes monitored by multiplexers",
.priv = false,
.N = 12,
.EX_N = 0,
@@ -4063,6 +4156,17 @@ static const struct factory factories[] = {
PARAM_END
},
},
+ {
+ .name = "userns",
+ .desc = "open a user namespae",
+ .priv = false,
+ .N = 1,
+ .EX_N = 0,
+ .make = make_userns,
+ .params = (struct parameter []) {
+ PARAM_END
+ }
+ },
};
static int count_parameters(const struct factory *factory)
diff --git a/tests/ts/lsns/filter b/tests/ts/lsns/filter
new file mode 100755
index 0000000000..fa8b090aed
--- /dev/null
+++ b/tests/ts/lsns/filter
@@ -0,0 +1,65 @@
+#!/bin/bash
+#
+# Copyright (C) 2024 Masatake YAMATO <yamato@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# 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 file is distributed in the hope that it will 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.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="-Q, --filter option"
+
+. "$TS_TOPDIR"/functions.sh
+# for $EPERM
+. "$TS_TOPDIR"/ts/lsfd/lsfd-functions.bash
+
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_LSNS"
+ts_check_test_command "$TS_CMD_LSFD"
+
+ts_check_test_command "$TS_HELPER_MKFDS"
+
+# unshare(2) used in userns factory of test_mkfds reports "Invalid argument".
+ts_skip_qemu_user
+
+ts_cd "$TS_OUTDIR"
+PID=
+FD=4
+EXPR=
+
+{
+ coproc MKFDS { "$TS_HELPER_MKFDS" --comm ABC userns $FD; }
+ if read -u ${MKFDS[0]} PID; then
+ expr="PID == \"${PID}\" and ASSOC == \"user\""
+ inode=$(${TS_CMD_LSFD} -n --raw -o INODE -Q "${expr}")
+ for opt in -Q --filter; do
+ pid=$(${TS_CMD_LSNS} -n --raw -o PID "$opt" "NS == $inode && NPROCS == 1")
+ if [[ "$pid" = "$PID" ]]; then
+ echo "$opt: pid == PID"
+ else
+ echo "$opt: pid != PID"
+ echo expr: "${expr}"
+ ${TS_CMD_LSFD} -n --raw -o INODE -Q "${expr}"
+ echo inode: "${inode}"
+ ${TS_CMD_LSNS} -n --raw -o PID -Q "NS == $inode && NPROCS == 1"
+ echo pid: "${pid}"
+ echo PID: "${PID}"
+ fi
+ done
+ echo DONE >&"${MKFDS[1]}"
+ fi
+ wait "${MKFDS_PID}"
+} > $TS_OUTPUT 2>&1
+if [ "$?" == "$EPERM" ]; then
+ ts_skip "unshare(2) is not permitted on this platform"
+fi
+ts_finalize