aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarel Zak <kzak@redhat.com>2022-09-30 10:44:55 +0200
committerKarel Zak <kzak@redhat.com>2022-09-30 10:44:55 +0200
commite70e5258c05b856842ce5bd2ab2d6cb4d440162b (patch)
tree65521f077e5ce93c500febb376fa174e3c324cf7
parente0b15af484e6e0c05d3af1bf6cd226ca9252849a (diff)
parent55529a2620d5fd3cc549c38bf50455748da9020f (diff)
downloadutil-linux-e70e5258c05b856842ce5bd2ab2d6cb4d440162b.tar.gz
Merge branch 'lsfd-sock-unix-xinfo' of https://github.com/masatake/util-linux
* 'lsfd-sock-unix-xinfo' of https://github.com/masatake/util-linux: tests: (lsfd) add a case for testing SOCKNETNS column tests: (lsfd) extend unix-stream test case to test SEQPACKET socket tests: (lsfd) add a case testing UNIX+DGRAM socket tests: (lsfd) add a case testing UNIX-STREAM sockets tests: (mkfds) add a factory making unix sockets lsfd: (man) write about UNIX-STREAM and UNIX sockets lsfd: use extra information loaded from /proc/net/unix lsfd: add new columns: SOCKNETNS, SOCKSTATE, and SOCKTYPE as stubs lsfd: facilitate the way to attach extra info loaded from /proc/net/* to sockets tests: (mkfds) quit when a byte is given via standard input tests: (mkfds) call close method of factory only when it is specified tests: (mkfds) cosmetic change, deleting empty lines tests: (mkfds) add boolean, a new parameter type tests: (mkfds) add a method for printing factory specific data to struct factory tests: (mkfds) allow a factory to make a factory specific temporarily data tests: (mkfds) delete unused "child" parameter for factories tests: (mkfds) delete per-factory "fork" field
-rw-r--r--misc-utils/Makemodule.am2
-rw-r--r--misc-utils/lsfd-sock-xinfo.c380
-rw-r--r--misc-utils/lsfd-sock.c61
-rw-r--r--misc-utils/lsfd-sock.h72
-rw-r--r--misc-utils/lsfd.1.adoc28
-rw-r--r--misc-utils/lsfd.c31
-rw-r--r--misc-utils/lsfd.h12
-rw-r--r--misc-utils/meson.build2
-rw-r--r--tests/expected/lsfd/column-name2
-rw-r--r--tests/expected/lsfd/mkfds-unix-dgram6
-rw-r--r--tests/expected/lsfd/mkfds-unix-in-netns18
-rw-r--r--tests/expected/lsfd/mkfds-unix-stream24
-rw-r--r--tests/helpers/test_mkfds.c705
-rwxr-xr-xtests/ts/lsfd/mkfds-unix-dgram60
-rwxr-xr-xtests/ts/lsfd/mkfds-unix-in-netns91
-rwxr-xr-xtests/ts/lsfd/mkfds-unix-stream82
16 files changed, 1524 insertions, 52 deletions
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am
index dfeff6f4e6..2c9f7ead5c 100644
--- a/misc-utils/Makemodule.am
+++ b/misc-utils/Makemodule.am
@@ -263,6 +263,8 @@ lsfd_SOURCES = \
misc-utils/lsfd-cdev.c \
misc-utils/lsfd-bdev.c \
misc-utils/lsfd-sock.c \
+ misc-utils/lsfd-sock.h \
+ misc-utils/lsfd-sock-xinfo.c \
misc-utils/lsfd-unkn.c \
misc-utils/lsfd-fifo.c
lsfd_LDADD = $(LDADD) libsmartcols.la libcommon.la
diff --git a/misc-utils/lsfd-sock-xinfo.c b/misc-utils/lsfd-sock-xinfo.c
new file mode 100644
index 0000000000..ef994f191c
--- /dev/null
+++ b/misc-utils/lsfd-sock-xinfo.c
@@ -0,0 +1,380 @@
+/*
+ * lsfd-sock-xinfo.c - read various information from files under /proc/net/
+ *
+ * Copyright (C) 2022 Red Hat, Inc. All rights reserved.
+ * Written by Masatake YAMATO <yamato@redhat.com>
+ *
+ * This program 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 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 <fcntl.h> /* open(2) */
+#include <linux/net.h> /* SS_* */
+#include <linux/un.h> /* UNIX_PATH_MAX */
+#include <sched.h> /* for setns(2) */
+#include <search.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/socket.h> /* SOCK_* */
+
+#include "xalloc.h"
+#include "nls.h"
+#include "libsmartcols.h"
+
+#include "lsfd.h"
+#include "lsfd-sock.h"
+
+static void load_xinfo_from_proc_unix(ino_t netns_inode);
+
+static int self_netns_fd = -1;
+struct stat self_netns_sb;
+
+static void *xinfo_tree; /* for tsearch/tfind */
+static void *netns_tree;
+
+static int netns_compare(const void *a, const void *b)
+{
+ if (*(ino_t *)a < *(ino_t *)b)
+ return -1;
+ else if (*(ino_t *)a > *(ino_t *)b)
+ return 1;
+ else
+ return 0;
+}
+
+static bool is_sock_xinfo_loaded(ino_t netns)
+{
+ return tfind(&netns, &netns_tree, netns_compare)? true: false;
+}
+
+static void mark_sock_xinfo_loaded(ino_t ino)
+{
+ ino_t *netns = xmalloc(sizeof(ino));
+ ino_t **tmp;
+
+ *netns = ino;
+ tmp = tsearch(netns, &netns_tree, netns_compare);
+ if (tmp == NULL)
+ errx(EXIT_FAILURE, _("failed to allocate memory"));
+}
+
+static void load_sock_xinfo_no_nsswitch(ino_t netns)
+{
+ load_xinfo_from_proc_unix(netns);
+}
+
+static void load_sock_xinfo_with_fd(int fd, ino_t netns)
+{
+ if (setns (fd, CLONE_NEWNET) == 0) {
+ load_sock_xinfo_no_nsswitch(netns);
+ setns (self_netns_fd, CLONE_NEWNET);
+ }
+}
+
+void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns)
+{
+ if (self_netns_fd == -1)
+ return;
+
+ if (!is_sock_xinfo_loaded(netns)) {
+ int fd;
+
+ mark_sock_xinfo_loaded(netns);
+ fd = ul_path_open(pc, O_RDONLY, name);
+ if (fd < 0)
+ return;
+
+ load_sock_xinfo_with_fd(fd, netns);
+ close(fd);
+ }
+}
+
+void initialize_sock_xinfos(void)
+{
+ struct path_cxt *pc;
+ DIR *dir;
+ struct dirent *d;
+
+ self_netns_fd = open("/proc/self/ns/net", O_RDONLY);
+
+ if (self_netns_fd < 0)
+ load_sock_xinfo_no_nsswitch(0);
+ else {
+ if (fstat(self_netns_fd, &self_netns_sb) == 0) {
+ mark_sock_xinfo_loaded(self_netns_sb.st_ino);
+ load_sock_xinfo_no_nsswitch(self_netns_sb.st_ino);
+ }
+ }
+
+ /* Load /proc/net/{unix,...} of the network namespace
+ * specified with netns files under /var/run/netns/.
+ *
+ * `ip netns' command pins a network namespace on
+ * /var/run/netns.
+ */
+ pc = ul_new_path("/var/run/netns");
+ if (!pc)
+ err(EXIT_FAILURE, _("failed to alloc path context for /var/run/netns"));
+ dir = ul_path_opendir(pc, NULL);
+ if (dir == NULL) {
+ ul_unref_path(pc);
+ return;
+ }
+ while ((d = readdir(dir))) {
+ struct stat sb;
+ int fd;
+ if (ul_path_stat(pc, &sb, 0, d->d_name) < 0)
+ continue;
+ if (is_sock_xinfo_loaded(sb.st_ino))
+ continue;
+ mark_sock_xinfo_loaded(sb.st_ino);
+ fd = ul_path_open(pc, O_RDONLY, d->d_name);
+ if (fd < 0)
+ continue;
+ load_sock_xinfo_with_fd(fd, sb.st_ino);
+ close(fd);
+ }
+ closedir(dir);
+ ul_unref_path(pc);
+}
+
+static void free_sock_xinfo (void *node)
+{
+ struct sock_xinfo *xinfo = node;
+ if (xinfo->class->free)
+ xinfo->class->free (xinfo);
+ free(node);
+}
+
+void finalize_sock_xinfos(void)
+{
+ if (self_netns_fd != -1)
+ close(self_netns_fd);
+ tdestroy(netns_tree, free);
+ tdestroy(xinfo_tree, free_sock_xinfo);
+}
+
+static int xinfo_compare(const void *a, const void *b)
+{
+ if (((struct sock_xinfo *)a)->inode < ((struct sock_xinfo *)b)->inode)
+ return -1;
+ if (((struct sock_xinfo *)a)->inode > ((struct sock_xinfo *)b)->inode)
+ return 1;
+ return 0;
+}
+
+static void add_sock_info(struct sock_xinfo *xinfo)
+{
+ struct sock_xinfo **tmp = tsearch(xinfo, &xinfo_tree, xinfo_compare);
+
+ if (tmp == NULL)
+ errx(EXIT_FAILURE, _("failed to allocate memory"));
+}
+
+struct sock_xinfo *get_sock_xinfo(ino_t netns_inode)
+{
+ struct sock_xinfo **xinfo = tfind(&netns_inode, &xinfo_tree, xinfo_compare);
+
+ if (xinfo)
+ return *xinfo;
+ return NULL;
+}
+
+bool is_nsfs_dev(dev_t dev)
+{
+ return (dev == self_netns_sb.st_dev);
+}
+
+static const char *sock_decode_type(uint16_t type)
+{
+ switch (type) {
+ case SOCK_STREAM:
+ return "stream";
+ case SOCK_DGRAM:
+ return "dgram";
+ case SOCK_RAW:
+ return "raw";
+ case SOCK_RDM:
+ return "rdm";
+ case SOCK_SEQPACKET:
+ return "seqpacket";
+ case SOCK_DCCP:
+ return "dccp";
+ case SOCK_PACKET:
+ return "packet";
+ default:
+ return "unknown";
+ }
+}
+
+/*
+ * Protocol specific code
+ */
+
+/*
+ * UNIX
+ */
+struct unix_xinfo {
+ struct sock_xinfo sock;
+ int acceptcon; /* flags */
+ uint16_t type;
+ uint8_t st;
+ char path[
+ UNIX_PATH_MAX
+ + 1 /* for @ */
+ + 1 /* \0? */
+ ];
+};
+
+static const char *unix_decode_state(uint8_t st)
+{
+ switch (st) {
+ case SS_FREE:
+ return "free";
+ case SS_UNCONNECTED:
+ return "unconnected";
+ case SS_CONNECTING:
+ return "connecting";
+ case SS_CONNECTED:
+ return "connected";
+ case SS_DISCONNECTING:
+ return "disconnecting";
+ default:
+ return "unknown";
+ }
+}
+
+static char *unix_get_name(struct sock_xinfo *sock_xinfo,
+ struct sock *sock)
+{
+ struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo;
+ const char *state = unix_decode_state(ux->st);
+ char *str = NULL;
+
+ if (sock->protoname && (strcmp(sock->protoname, "UNIX-STREAM") == 0))
+ xasprintf(&str, "state=%s%s%s",
+ (ux->acceptcon)? "listen": state,
+ *(ux->path)? " path=": "",
+ *(ux->path)? ux->path: "");
+ else
+ xasprintf(&str, "state=%s%s%s type=%s",
+ (ux->acceptcon)? "listen": state,
+ *(ux->path)? " path=": "",
+ *(ux->path)? ux->path: "",
+ sock_decode_type(ux->type));
+ return str;
+}
+
+static char *unix_get_type(struct sock_xinfo *sock_xinfo,
+ struct sock *sock __attribute__((__unused__)))
+{
+ const char *str;
+ struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo;
+
+ str = sock_decode_type(ux->type);
+ return strdup(str);
+}
+
+static char *unix_get_state(struct sock_xinfo *sock_xinfo,
+ struct sock *sock __attribute__((__unused__)))
+{
+ const char *str;
+ struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo;
+
+ if (ux->acceptcon)
+ return strdup("listen");
+
+ str = unix_decode_state(ux->st);
+ return strdup(str);
+}
+
+static bool unix_fill_column(struct proc *proc __attribute__((__unused__)),
+ struct sock_xinfo *sock_xinfo,
+ struct sock *sock __attribute__((__unused__)),
+ struct libscols_line *ln __attribute__((__unused__)),
+ int column_id,
+ size_t column_index __attribute__((__unused__)),
+ char **str)
+{
+ struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo;
+
+ switch(column_id) {
+ case COL_UNIX_PATH:
+ if (*ux->path) {
+ *str = strdup(ux->path);
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static struct sock_xinfo_class unix_xinfo_class = {
+ .class = "unix",
+ .get_name = unix_get_name,
+ .get_type = unix_get_type,
+ .get_state = unix_get_state,
+ .fill_column = unix_fill_column,
+ .free = NULL,
+};
+
+/* #define UNIX_LINE_LEN 54 + 21 + UNIX_LINE_LEN + 1 */
+#define UNIX_LINE_LEN 256
+static void load_xinfo_from_proc_unix(ino_t netns_inode)
+{
+ char line[UNIX_LINE_LEN];
+ FILE *unix_fp;
+
+ unix_fp = fopen("/proc/net/unix", "r");
+ if (!unix_fp)
+ return;
+
+ if (fgets(line, sizeof(line), unix_fp) == NULL)
+ goto out;
+ if (! (line[0] == 'N' && line[1] == 'u' && line[2] == 'm'))
+ /* Unexpected line */
+ goto out;
+
+ while (fgets(line, sizeof(line), unix_fp)) {
+ uint64_t flags;
+ uint32_t type;
+ unsigned int st;
+ unsigned long inode;
+ char path[1 + UNIX_PATH_MAX +1];
+ struct unix_xinfo *ux;
+
+ memset(path, 0, sizeof(path));
+ if (sscanf(line, "%*x: %*x %*x %lx %x %x %lu %s",
+ &flags, &type, &st, &inode, path) < 4)
+ continue;
+
+ if (inode == 0)
+ continue;
+
+ ux = xmalloc(sizeof(struct unix_xinfo));
+ ux->sock.class = &unix_xinfo_class;
+ ux->sock.inode = (ino_t)inode;
+ ux->sock.netns_inode = netns_inode;
+
+ ux->acceptcon = !!flags;
+ ux->type = type;
+ ux->st = st;
+ strcpy(ux->path, path);
+
+ add_sock_info((struct sock_xinfo *)ux);
+ }
+
+ out:
+ fclose(unix_fp);
+}
diff --git a/misc-utils/lsfd-sock.c b/misc-utils/lsfd-sock.c
index c7adbf3582..06201646dd 100644
--- a/misc-utils/lsfd-sock.c
+++ b/misc-utils/lsfd-sock.c
@@ -27,11 +27,13 @@
#include "libsmartcols.h"
#include "lsfd.h"
+#include "lsfd-sock.h"
-struct sock {
- struct file file;
- char *protoname;
-};
+static void attach_sock_xinfo(struct file *file)
+{
+ struct sock *sock = (struct sock *)file;
+ sock->xinfo = get_sock_xinfo(file->stat.st_ino);
+}
static bool sock_fill_column(struct proc *proc __attribute__((__unused__)),
struct file *file,
@@ -51,6 +53,14 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)),
if (scols_line_set_data(ln, column_index, sock->protoname))
err(EXIT_FAILURE, _("failed to add output data"));
return true;
+ case COL_NAME:
+ if (sock->xinfo
+ && sock->xinfo->class && sock->xinfo->class->get_name) {
+ str = sock->xinfo->class->get_name (sock->xinfo, sock);
+ if (str)
+ break;
+ }
+ return false;
case COL_SOURCE:
if (major(file->stat.st_dev) == 0
&& strncmp(file->name, "socket:", 7) == 0) {
@@ -58,7 +68,37 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)),
break;
}
return false;
+ case COL_SOCKNETNS:
+ if (sock->xinfo) {
+ xasprintf(&str, "%llu",
+ (unsigned long long)sock->xinfo->netns_inode);
+ break;
+ }
+ return false;
+ case COL_SOCKTYPE:
+ if (sock->xinfo
+ && sock->xinfo->class && sock->xinfo->class->get_type) {
+ str = sock->xinfo->class->get_type(sock->xinfo, sock);
+ if (str)
+ break;
+ }
+ return false;
+ case COL_SOCKSTATE:
+ if (sock->xinfo
+ && sock->xinfo->class && sock->xinfo->class->get_state) {
+ str = sock->xinfo->class->get_state(sock->xinfo, sock);
+ if (str)
+ break;
+ }
+ return false;
default:
+ if (sock->xinfo && sock->xinfo->class
+ && sock->xinfo->class->fill_column) {
+ if (sock->xinfo->class->fill_column(proc, sock->xinfo, sock, ln,
+ column_id, column_index,
+ &str))
+ break;
+ }
return false;
}
@@ -110,10 +150,23 @@ static void free_sock_content(struct file *file)
}
}
+static void initialize_sock_class(void)
+{
+ initialize_sock_xinfos();
+}
+
+static void finalize_sock_class(void)
+{
+ finalize_sock_xinfos();
+}
+
const struct file_class sock_class = {
.super = &file_class,
.size = sizeof(struct sock),
.fill_column = sock_fill_column,
+ .attach_xinfo = attach_sock_xinfo,
.initialize_content = init_sock_content,
.free_content = free_sock_content,
+ .initialize_class = initialize_sock_class,
+ .finalize_class = finalize_sock_class,
};
diff --git a/misc-utils/lsfd-sock.h b/misc-utils/lsfd-sock.h
new file mode 100644
index 0000000000..155bf819ef
--- /dev/null
+++ b/misc-utils/lsfd-sock.h
@@ -0,0 +1,72 @@
+/*
+ * lsfd(1) - list file descriptors
+ *
+ * Copyright (C) 2022 Red Hat, Inc. All rights reserved.
+ * Written by Masatake YAMATO <yamato@redhat.com>
+ *
+ * Very generally based on lsof(8) by Victor A. Abell <abe@purdue.edu>
+ * It supports multiple OSes. lsfd specializes to Linux.
+ *
+ * This program 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 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
+ */
+#ifndef UTIL_LINUX_LSFD_SOCK_H
+#define UTIL_LINUX_LSFD_SOCK_H
+
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include "libsmartcols.h"
+
+/*
+ * xinfo: eXtra inforation about sockets
+ */
+struct sock_xinfo {
+ ino_t inode; /* inode in sockfs */
+ ino_t netns_inode; /* inode of netns where
+ the socket belongs to */
+ const struct sock_xinfo_class *class;
+};
+
+struct sock {
+ struct file file;
+ char *protoname;
+ struct sock_xinfo *xinfo;
+};
+
+struct sock_xinfo_class {
+ const char *class;
+ /* Methods for filling socket related columns */
+ char * (*get_name)(struct sock_xinfo *, struct sock *);
+ char * (*get_type)(struct sock_xinfo *, struct sock *);
+ char * (*get_state)(struct sock_xinfo *, struct sock *);
+ /* Method for class specific columns.
+ * Return true when the method fills the column. */
+ bool (*fill_column)(struct proc *,
+ struct sock_xinfo *,
+ struct sock *,
+ struct libscols_line *,
+ int,
+ size_t,
+ char **str);
+
+ void (*free)(struct sock_xinfo *);
+};
+
+void initialize_sock_xinfos(void);
+void finalize_sock_xinfos(void);
+
+struct sock_xinfo *get_sock_xinfo(ino_t netns_inode);
+
+#endif /* UTIL_LINUX_LSFD_SOCK_H */
diff --git a/misc-utils/lsfd.1.adoc b/misc-utils/lsfd.1.adoc
index 20d9e981e5..b6db69583a 100644
--- a/misc-utils/lsfd.1.adoc
+++ b/misc-utils/lsfd.1.adoc
@@ -204,6 +204,12 @@ pid=_TARGET-PID_ comm=_TARGET-COMMAND_ nspid=_TARGET-NSPIDS_
+
*lsfd* extracts _TARGET-PID_ and _TARGET-NSPIDS_ from
``/proc/``_pid_``/fdinfo/``_fd_.
++
+UNIX-STREAM:::
+state=_SOCKSTATE_[ path=_UNIX.PATH_]
++
+UNIX:::
+state=_SOCKSTATE_[ path=_UNIX.PATH_] type=_SOCKTYPE_
NLINK <``number``>::
Link count.
@@ -251,6 +257,24 @@ Device ID (if special file).
SIZE <``number``>::
File size.
+SOCKNETS <``number``>::
+Inode identifying network namespace where the socket belogs to.
+
+SOCKSTATE <``string``>::
+State of socket.
+
+SOCKTYPE <``string``>::
+Type of socket. Here type means the second parameter of
+socket system call:
++
+* stream
+* dgram
+* raw
+* rdm
+* seqpacket
+* dccp
+* packet
+
SOURCE <``string``>::
File system, partition, or device containing the file.
@@ -268,6 +292,9 @@ For UNKN, print the value for AINODECLASS if SOURCE is anon_inodefs.
UID <``number``>::
User ID number.
+UNIX.PATH <``string``>::
+Filesystem pathname for UNIX doamin socket.
+
USER <``string``>::
User of the process.
@@ -508,6 +535,7 @@ mailto:kzak@redhat.com[Karel Zak]
*lsof*(8)
*pidof*(1)
*proc*(5)
+*socket*(2)
*stat*(2)
include::man-common/bugreports.adoc[]
diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c
index 0d7e7e58a1..d403513961 100644
--- a/misc-utils/lsfd.c
+++ b/misc-utils/lsfd.c
@@ -185,6 +185,12 @@ static struct colinfo infos[] = {
N_("device ID (if special file)") },
[COL_SIZE] = { "SIZE", 4, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
N_("file size"), },
+ [COL_SOCKNETNS]={"SOCKNETNS", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+ N_("inode identifying network namespace where the socket belongs to") },
+ [COL_SOCKSTATE]={"SOCKSTATE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("State of socket") },
+ [COL_SOCKTYPE] ={"SOCKTYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+ N_("Type of socket") },
[COL_SOURCE] = { "SOURCE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
N_("file system, partition, or device containing file") },
[COL_STTYPE] = { "STTYPE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
@@ -195,6 +201,8 @@ static struct colinfo infos[] = {
N_("file type (cooked)") },
[COL_UID] = { "UID", 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
N_("user ID number of the process") },
+ [COL_UNIX_PATH]={ "UNIX.PATH",0.4,SCOLS_FL_TRUNC,SCOLS_JSON_STRING,
+ N_("filesystem pathname for UNIX doamin socketo") },
[COL_USER] = { "USER", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
N_("user of the process") },
};
@@ -605,6 +613,8 @@ static struct file *collect_file_symlink(struct path_cxt *pc,
if (is_association(f, NS_MNT))
proc->ns_mnt = f->stat.st_ino;
+ else if (is_association(f, NS_NET))
+ load_sock_xinfo(pc, name, f->stat.st_ino);
else if (assoc >= 0) {
/* file-descriptor based association */
@@ -613,6 +623,9 @@ static struct file *collect_file_symlink(struct path_cxt *pc,
if (ul_path_stat(pc, &sb, AT_SYMLINK_NOFOLLOW, name) == 0)
f->mode = sb.st_mode;
+ if (is_nsfs_dev(f->stat.st_dev))
+ load_sock_xinfo(pc, name, f->stat.st_ino);
+
fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%d", assoc);
if (fdinfo) {
read_fdinfo(f, fdinfo);
@@ -1620,6 +1633,22 @@ static void emit_summary(struct lsfd_control *ctl, struct lsfd_counter **counter
scols_unref_table(tb);
}
+static void attach_xinfos(struct list_head *procs)
+{
+ struct list_head *p;
+
+ list_for_each (p, procs) {
+ struct proc *proc = list_entry(p, struct proc, procs);
+ struct list_head *f;
+
+ list_for_each (f, &proc->files) {
+ struct file *file = list_entry(f, struct file, files);
+ if (file->class->attach_xinfo)
+ file->class->attach_xinfo(file);
+ }
+ }
+}
+
int main(int argc, char *argv[])
{
int c;
@@ -1806,6 +1835,8 @@ int main(int argc, char *argv[])
collect_processes(&ctl, pids, n_pids);
free(pids);
+ attach_xinfos(&ctl.procs);
+
convert(&ctl.procs, &ctl);
/* print */
diff --git a/misc-utils/lsfd.h b/misc-utils/lsfd.h
index 41f574ff4a..2ab17d27d3 100644
--- a/misc-utils/lsfd.h
+++ b/misc-utils/lsfd.h
@@ -30,6 +30,7 @@
#include <inttypes.h>
#include "list.h"
+#include "path.h"
#include "strutils.h"
/*
@@ -66,11 +67,15 @@ enum {
COL_PROTONAME,
COL_RDEV,
COL_SIZE,
+ COL_SOCKNETNS,
+ COL_SOCKSTATE,
+ COL_SOCKTYPE,
COL_SOURCE,
COL_STTYPE,
COL_TID,
COL_TYPE,
COL_UID, /* process */
+ COL_UNIX_PATH,
COL_USER, /* process */
COL_FUID, /* file */
COL_OWNER, /* file */
@@ -145,6 +150,7 @@ struct file_class {
int column_id,
size_t column_index);
int (*handle_fdinfo)(struct file *file, const char *key, const char* value);
+ void (*attach_xinfo)(struct file *file);
void (*initialize_content)(struct file *file);
void (*free_content)(struct file *file);
struct ipc_class *(*get_ipc_class)(struct file *file);
@@ -203,4 +209,10 @@ static inline void xstrputc(char **a, char c)
xstrappend(a, b);
}
+/*
+ * Net namespace
+ */
+void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns);
+bool is_nsfs_dev(dev_t dev);
+
#endif /* UTIL_LINUX_LSFD_H */
diff --git a/misc-utils/meson.build b/misc-utils/meson.build
index 818232a580..b0dcb801d5 100644
--- a/misc-utils/meson.build
+++ b/misc-utils/meson.build
@@ -51,6 +51,8 @@ lsfd_sources = files (
'lsfd-cdev.c',
'lsfd-bdev.c',
'lsfd-sock.c',
+ 'lsfd-sock.h',
+ 'lsfd-sock-xinfo.c',
'lsfd-unkn.c',
'lsfd-fifo.c',
)
diff --git a/tests/expected/lsfd/column-name b/tests/expected/lsfd/column-name
index 0e0ac1d9da..c8364fbf5a 100644
--- a/tests/expected/lsfd/column-name
+++ b/tests/expected/lsfd/column-name
@@ -2,5 +2,5 @@
ro-regular-file:ASSOC,KNAME,NAME: 0
3 anon_inode:[pidfd] pid=1 comm= nspid=1
pidfd:ASSOC,KNAME,NAME: 0
- 3 socket:[INODENUM] socket:[INODENUM]
+ 3 socket:[INODENUM] state=connected type=dgram
socketpair:ASSOC,KNAME,NAME: 0
diff --git a/tests/expected/lsfd/mkfds-unix-dgram b/tests/expected/lsfd/mkfds-unix-dgram
new file mode 100644
index 0000000000..915b34396e
--- /dev/null
+++ b/tests/expected/lsfd/mkfds-unix-dgram
@@ -0,0 +1,6 @@
+ 3 SOCK state=connected path=test_mkfds-unix-dgram type=dgram connected dgram test_mkfds-unix-dgram
+ 4 SOCK state=connected type=dgram connected dgram
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+ 3 SOCK state=connected path=@test_mkfds-unix-dgram type=dgram connected dgram @test_mkfds-unix-dgram
+ 4 SOCK state=connected type=dgram connected dgram
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
diff --git a/tests/expected/lsfd/mkfds-unix-in-netns b/tests/expected/lsfd/mkfds-unix-in-netns
new file mode 100644
index 0000000000..46b6f200d8
--- /dev/null
+++ b/tests/expected/lsfd/mkfds-unix-in-netns
@@ -0,0 +1,18 @@
+ 5 SOCK state=listen path=test_mkfds-unix-stream-ns listen stream test_mkfds-unix-stream-ns
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+the netns for the stream socket is extracted as expectedly
+ 5 SOCK state=listen path=@test_mkfds-unix-stream-ns listen stream @test_mkfds-unix-stream-ns
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+the netns for the abstract stream socket is extracted as expectedly
+ 5 SOCK state=unconnected path=test_mkfds-unix-dgram-ns type=dgram unconnected dgram test_mkfds-unix-dgram-ns
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+the netns for the dgram socket is extracted as expectedly
+ 5 SOCK state=unconnected path=@test_mkfds-unix-dgram-ns type=dgram unconnected dgram @test_mkfds-unix-dgram-ns
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+the netns for the abstract dgram socket is extracted as expectedly
+ 5 SOCK state=listen path=test_mkfds-unix-seqpacket-ns type=seqpacket listen seqpacket test_mkfds-unix-seqpacket-ns
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+the netns for the seqpacket socket is extracted as expectedly
+ 5 SOCK state=listen path=@test_mkfds-unix-seqpacket-ns type=seqpacket listen seqpacket @test_mkfds-unix-seqpacket-ns
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+the netns for the abstract seqpacket socket is extracted as expectedly
diff --git a/tests/expected/lsfd/mkfds-unix-stream b/tests/expected/lsfd/mkfds-unix-stream
new file mode 100644
index 0000000000..45eee020e0
--- /dev/null
+++ b/tests/expected/lsfd/mkfds-unix-stream
@@ -0,0 +1,24 @@
+ 3 SOCK state=listen path=test_mkfds-unix-stream listen stream test_mkfds-unix-stream
+ 4 SOCK state=connected connected stream
+ 5 SOCK state=connected path=test_mkfds-unix-stream connected stream test_mkfds-unix-stream
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+ 3 SOCK state=listen path=@test_mkfds-unix-stream-abs listen stream @test_mkfds-unix-stream-abs
+ 4 SOCK state=connected connected stream
+ 5 SOCK state=connected path=@test_mkfds-unix-stream-abs connected stream @test_mkfds-unix-stream-abs
+(abs) ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+ 3 SOCK state=listen path=test_mkfds-unix-stream-shutdown listen stream test_mkfds-unix-stream-shutdown
+ 4 SOCK state=connected connected stream
+ 5 SOCK state=connected path=test_mkfds-unix-stream-shutdown connected stream test_mkfds-unix-stream-shutdown
+(shutdown) ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+ 3 SOCK state=listen path=test_mkfds-unix-seqpacket type=seqpacket listen seqpacket test_mkfds-unix-seqpacket
+ 4 SOCK state=connected type=seqpacket connected seqpacket
+ 5 SOCK state=connected path=test_mkfds-unix-seqpacket type=seqpacket connected seqpacket test_mkfds-unix-seqpacket
+ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+ 3 SOCK state=listen path=@test_mkfds-unix-seqpacket-abs listen stream @test_mkfds-unix-seqpacket-abs
+ 4 SOCK state=connected connected stream
+ 5 SOCK state=connected path=@test_mkfds-unix-seqpacket-abs connected stream @test_mkfds-unix-seqpacket-abs
+(abs) ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
+ 3 SOCK state=listen path=test_mkfds-unix-seqpacket-shutdown type=seqpacket listen seqpacket test_mkfds-unix-seqpacket-shutdown
+ 4 SOCK state=connected type=seqpacket connected seqpacket
+ 5 SOCK state=connected path=test_mkfds-unix-seqpacket-shutdown type=seqpacket connected seqpacket test_mkfds-unix-seqpacket-shutdown
+(shutdown) ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH: 0
diff --git a/tests/helpers/test_mkfds.c b/tests/helpers/test_mkfds.c
index b0a9d8af3c..05b6db1e79 100644
--- a/tests/helpers/test_mkfds.c
+++ b/tests/helpers/test_mkfds.c
@@ -26,6 +26,7 @@
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
+#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
@@ -34,6 +35,7 @@
#include <sys/inotify.h>
#include <sys/mman.h>
#include <sys/prctl.h>
+#include <sys/select.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
@@ -74,11 +76,13 @@ static void __attribute__((__noreturn__)) usage(FILE *out, int status)
union value {
const char *string;
long integer;
+ bool boolean;
};
enum ptype {
PTYPE_STRING,
PTYPE_INTEGER,
+ PTYPE_BOOLEAN,
};
struct ptype_class {
@@ -99,6 +103,7 @@ struct ptype_class {
#define ARG_STRING(A) (A.v.string)
#define ARG_INTEGER(A) (A.v.integer)
+#define ARG_BOOLEAN(A) (A.v.boolean)
struct arg {
union value v;
void (*free)(union value value);
@@ -155,6 +160,32 @@ static void integer_free(union value value _U_)
/* Do nothing */
}
+static char *boolean_sprint(const union value *value)
+{
+ return xstrdup(value->boolean? "true": "false");
+}
+
+static union value boolean_read(const char *arg, const union value *defv)
+{
+ union value r;
+
+ if (!arg)
+ return *defv;
+
+ if (strcasecmp(arg, "true") == 0
+ || strcmp(arg, "1") == 0
+ || strcasecmp(arg, "yes") == 0
+ || strcasecmp(arg, "y") == 0)
+ r.boolean = true;
+ else
+ r.boolean = false;
+ return r;
+}
+
+static void boolean_free(union value value _U_)
+{
+ /* Do nothing */
+}
struct ptype_class ptype_classes [] = {
[PTYPE_STRING] = {
@@ -169,6 +200,12 @@ struct ptype_class ptype_classes [] = {
.read = integer_read,
.free = integer_free,
},
+ [PTYPE_BOOLEAN] = {
+ .name = "boolean",
+ .sprint = boolean_sprint,
+ .read = boolean_read,
+ .free = boolean_free,
+ },
};
static struct arg decode_arg(const char *pname,
@@ -227,8 +264,9 @@ struct factory {
#define MAX_N 5
int N; /* the number of fds this factory makes */
int EX_N; /* fds made optionally */
- bool fork; /* whether this factory make a child process or not */
- void (*make)(const struct factory *, struct fdesc[], pid_t *, int, char **);
+ void *(*make)(const struct factory *, struct fdesc[], int, char **);
+ void (*free)(const struct factory *, void *);
+ void (*report)(const struct factory *, void *, FILE *);
const struct parameter * params;
};
@@ -237,8 +275,8 @@ static void close_fdesc(int fd, void *data _U_)
close(fd);
}
-static void open_ro_regular_file(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *open_ro_regular_file(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
struct arg file = decode_arg("file", factory->params, argc, argv);
struct arg offset = decode_arg("offset", factory->params, argc, argv);
@@ -273,10 +311,12 @@ static void open_ro_regular_file(const struct factory *factory, struct fdesc fde
.close = close_fdesc,
.data = NULL
};
+
+ return NULL;
}
-static void make_pipe(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *make_pipe(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
int pd[2];
int nonblock_flags[2] = {0, 0};
@@ -366,6 +406,8 @@ static void make_pipe(const struct factory *factory, struct fdesc fdescs[], pid_
};
}
}
+
+ return NULL;
}
static void close_dir(int fd, void *data)
@@ -377,8 +419,8 @@ static void close_dir(int fd, void *data)
close_fdesc(fd, NULL);
}
-static void open_directory(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *open_directory(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
struct arg dir = decode_arg("dir", factory->params, argc, argv);
struct arg dentries = decode_arg("dentries", factory->params, argc, argv);
@@ -419,16 +461,17 @@ static void open_directory(const struct factory *factory, struct fdesc fdescs[],
}
free_arg(&dentries);
-
fdescs[0] = (struct fdesc){
.fd = fdescs[0].fd,
.close = close_dir,
.data = dp
};
+
+ return NULL;
}
-static void open_rw_chrdev(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *open_rw_chrdev(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
struct arg chrdev = decode_arg("chrdev", factory->params, argc, argv);
int fd = open(ARG_STRING(chrdev), O_RDWR);
@@ -451,10 +494,12 @@ static void open_rw_chrdev(const struct factory *factory, struct fdesc fdescs[],
.close = close_fdesc,
.data = NULL
};
+
+ return NULL;
}
-static void make_socketpair(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *make_socketpair(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
int sd[2];
struct arg socktype = decode_arg("socktype", factory->params, argc, argv);
@@ -492,10 +537,12 @@ static void make_socketpair(const struct factory *factory, struct fdesc fdescs[]
.data = NULL
};
}
+
+ return NULL;
}
-static void open_with_opath(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *open_with_opath(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
struct arg path = decode_arg("path", factory->params, argc, argv);
int fd = open(ARG_STRING(path), O_PATH|O_NOFOLLOW);
@@ -518,9 +565,11 @@ static void open_with_opath(const struct factory *factory, struct fdesc fdescs[]
.close = close_fdesc,
.data = NULL
};
+
+ return NULL;
}
-static void open_ro_blkdev(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
+static void *open_ro_blkdev(const struct factory *factory, struct fdesc fdescs[],
int argc, char ** argv)
{
struct arg blkdev = decode_arg("blkdev", factory->params, argc, argv);
@@ -544,6 +593,8 @@ static void open_ro_blkdev(const struct factory *factory, struct fdesc fdescs[],
.close = close_fdesc,
.data = NULL,
};
+
+ return NULL;
}
static int make_packet_socket(int socktype, const char *interface)
@@ -593,8 +644,8 @@ static void close_fdesc_after_munmap(int fd, void *data)
close(fd);
}
-static void make_mmapped_packet_socket(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *make_mmapped_packet_socket(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
int sd;
struct arg socktype = decode_arg("socktype", factory->params, argc, argv);
@@ -668,10 +719,12 @@ static void make_mmapped_packet_socket(const struct factory *factory, struct fde
.close = close_fdesc_after_munmap,
.data = munmap_data,
};
+
+ return NULL;
}
-static void make_pidfd(const struct factory *factory, struct fdesc fdescs[], pid_t * child _U_,
- int argc, char ** argv)
+static void *make_pidfd(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
{
struct arg target_pid = decode_arg("target-pid", factory->params, argc, argv);
pid_t pid = ARG_INTEGER(target_pid);
@@ -696,10 +749,12 @@ static void make_pidfd(const struct factory *factory, struct fdesc fdescs[], pid
.close = close_fdesc,
.data = NULL
};
+
+ return NULL;
}
-static void make_inotify_fd(const struct factory *factory _U_, struct fdesc fdescs[], pid_t * child _U_,
- int argc _U_, char ** argv _U_)
+static void *make_inotify_fd(const struct factory *factory _U_, struct fdesc fdescs[],
+ int argc _U_, char ** argv _U_)
{
int fd = inotify_init();
if (fd < 0)
@@ -720,6 +775,452 @@ static void make_inotify_fd(const struct factory *factory _U_, struct fdesc fdes
.close = close_fdesc,
.data = NULL
};
+
+ return NULL;
+}
+
+static void close_unix_socket(int fd, void *data)
+{
+ char *path = data;
+ close(fd);
+ if (path) {
+ unlink(path);
+ free(path);
+ }
+}
+
+static void *make_unix_stream_core(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv, int type, const char *typestr)
+{
+ struct arg path = decode_arg("path", factory->params, argc, argv);
+ const char *spath = ARG_STRING(path);
+
+ struct arg backlog = decode_arg("backlog", factory->params, argc, argv);
+ int ibacklog = ARG_INTEGER(path);
+
+ struct arg abstract = decode_arg("abstract", factory->params, argc, argv);
+ bool babstract = ARG_BOOLEAN(abstract);
+
+ struct arg server_shutdown = decode_arg("server-shutdown", factory->params, argc, argv);
+ int iserver_shutdown = ARG_INTEGER(server_shutdown);
+ struct arg client_shutdown = decode_arg("client-shutdown", factory->params, argc, argv);
+ int iclient_shutdown = ARG_INTEGER(client_shutdown);
+
+ int ssd, csd, asd; /* server, client, and accepted socket descriptors */
+ struct sockaddr_un un;
+ size_t un_len = sizeof(un);
+
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_UNIX;
+ if (babstract) {
+ strncpy(un.sun_path + 1, spath, sizeof(un.sun_path) - 1 - 1);
+ size_t pathlen = strlen(spath);
+ if (sizeof(un.sun_path) - 1 > pathlen)
+ un_len = sizeof(un) - sizeof(un.sun_path) + 1 + pathlen;
+ } else
+ strncpy(un.sun_path, spath, sizeof(un.sun_path) - 1 );
+
+ free_arg(&client_shutdown);
+ free_arg(&server_shutdown);
+ free_arg(&abstract);
+ free_arg(&backlog);
+ free_arg(&path);
+
+ if (iserver_shutdown < 0 || iserver_shutdown > 3)
+ errx(EXIT_FAILURE, "the server shudown specification in unexpected range");
+ if (iclient_shutdown < 0 || iclient_shutdown > 3)
+ errx(EXIT_FAILURE, "the client shudown specification in unexpected range");
+
+ ssd = socket(AF_UNIX, type, 0);
+ if (ssd < 0)
+ err(EXIT_FAILURE,
+ "failed to make a socket with AF_UNIX + SOCK_%s (server side)", typestr);
+ if (ssd != fdescs[0].fd) {
+ if (dup2(ssd, fdescs[0].fd) < 0) {
+ int e = errno;
+ close(ssd);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", ssd, fdescs[0].fd);
+ }
+ close(ssd);
+ ssd = fdescs[0].fd;
+ }
+
+ fdescs[0] = (struct fdesc){
+ .fd = fdescs[0].fd,
+ .close = close_unix_socket,
+ .data = NULL,
+ };
+
+ if (!babstract)
+ unlink(un.sun_path);
+ if (bind(ssd, (const struct sockaddr *)&un, un_len) < 0) {
+ int e = errno;
+ close(ssd);
+ errno = e;
+ err(EXIT_FAILURE, "failed to bind a socket for listening");
+ }
+
+ if (!babstract)
+ fdescs[0].data = xstrdup(un.sun_path);
+ if (listen(ssd, ibacklog) < 0) {
+ int e = errno;
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to listen a socket");
+ }
+
+ csd = socket(AF_UNIX, type, 0);
+ if (csd < 0)
+ err(EXIT_FAILURE,
+ "failed to make a socket with AF_UNIX + SOCK_%s (client side)", typestr);
+ if (csd != fdescs[1].fd) {
+ if (dup2(csd, fdescs[1].fd) < 0) {
+ int e = errno;
+ close(csd);
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", csd, fdescs[1].fd);
+ }
+ close(csd);
+ csd = fdescs[1].fd;
+ }
+
+ fdescs[1] = (struct fdesc){
+ .fd = fdescs[1].fd,
+ .close = close_fdesc,
+ .data = NULL,
+ };
+
+ if (connect(csd, (const struct sockaddr *)&un, un_len) < 0) {
+ int e = errno;
+ close_fdesc(csd, NULL);
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to connect a socket to the listening socket");
+ }
+
+ if (!babstract)
+ unlink(un.sun_path);
+
+ asd = accept(ssd, NULL, NULL);
+ if (asd < 0) {
+ int e = errno;
+ close_fdesc(csd, NULL);
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to accept a socket from the listening socket");
+ }
+ if (asd != fdescs[2].fd) {
+ if (dup2(asd, fdescs[2].fd) < 0) {
+ int e = errno;
+ close(asd);
+ close_fdesc(csd, NULL);
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", asd, fdescs[2].fd);
+ }
+ close(asd);
+ asd = fdescs[2].fd;
+ }
+
+ if (iserver_shutdown & (1 << 0))
+ shutdown(asd, SHUT_RD);
+ if (iserver_shutdown & (1 << 1))
+ shutdown(asd, SHUT_WR);
+ if (iclient_shutdown & (1 << 0))
+ shutdown(csd, SHUT_RD);
+ if (iclient_shutdown & (1 << 1))
+ shutdown(csd, SHUT_WR);
+
+ return NULL;
+}
+
+static void *make_unix_stream(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
+{
+ struct arg type = decode_arg("type", factory->params, argc, argv);
+ const char *stype = ARG_STRING(type);
+
+ int typesym;
+ const char *typestr;
+
+ if (strcmp(stype, "stream") == 0) {
+ typesym = SOCK_STREAM;
+ typestr = "STREAM";
+ } else if (strcmp(stype, "seqpacket") == 0) {
+ typesym = SOCK_SEQPACKET;
+ typestr = "SEQPACKET";
+ } else
+ errx(EXIT_FAILURE, _("unknown unix socket type: %s"), stype);
+
+ free_arg(&type);
+
+ return make_unix_stream_core(factory, fdescs, argc, argv, typesym, typestr);
+}
+
+static void *make_unix_dgram(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
+{
+ struct arg path = decode_arg("path", factory->params, argc, argv);
+ const char *spath = ARG_STRING(path);
+
+ struct arg abstract = decode_arg("abstract", factory->params, argc, argv);
+ bool babstract = ARG_BOOLEAN(abstract);
+
+ int ssd, csd; /* server and client socket descriptors */
+
+ struct sockaddr_un un;
+ size_t un_len = sizeof(un);
+
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_UNIX;
+ if (babstract) {
+ strncpy(un.sun_path + 1, spath, sizeof(un.sun_path) - 1 - 1);
+ size_t pathlen = strlen(spath);
+ if (sizeof(un.sun_path) - 1 > pathlen)
+ un_len = sizeof(un) - sizeof(un.sun_path) + 1 + pathlen;
+ } else
+ strncpy(un.sun_path, spath, sizeof(un.sun_path) - 1 );
+
+ free_arg(&abstract);
+ free_arg(&path);
+
+ ssd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (ssd < 0)
+ err(EXIT_FAILURE,
+ "failed to make a socket with AF_UNIX + SOCK_DGRAM (server side)");
+ if (ssd != fdescs[0].fd) {
+ if (dup2(ssd, fdescs[0].fd) < 0) {
+ int e = errno;
+ close(ssd);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", ssd, fdescs[0].fd);
+ }
+ close(ssd);
+ ssd = fdescs[0].fd;
+ }
+
+ fdescs[0] = (struct fdesc){
+ .fd = fdescs[0].fd,
+ .close = close_unix_socket,
+ .data = NULL,
+ };
+
+ if (!babstract)
+ unlink(un.sun_path);
+ if (bind(ssd, (const struct sockaddr *)&un, un_len) < 0) {
+ int e = errno;
+ close(ssd);
+ errno = e;
+ err(EXIT_FAILURE, "failed to bind a socket for server");
+ }
+
+ if (!babstract)
+ fdescs[0].data = xstrdup(un.sun_path);
+ csd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (csd < 0)
+ err(EXIT_FAILURE,
+ "failed to make a socket with AF_UNIX + SOCK_DGRAM (client side)");
+ if (csd != fdescs[1].fd) {
+ if (dup2(csd, fdescs[1].fd) < 0) {
+ int e = errno;
+ close(csd);
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", csd, fdescs[1].fd);
+ }
+ close(csd);
+ csd = fdescs[1].fd;
+ }
+
+ fdescs[1] = (struct fdesc){
+ .fd = fdescs[1].fd,
+ .close = close_fdesc,
+ .data = NULL,
+ };
+
+ if (connect(csd, (const struct sockaddr *)&un, un_len) < 0) {
+ int e = errno;
+ close_fdesc(csd, NULL);
+ close_unix_socket(ssd, fdescs[0].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to connect a socket to the server socket");
+ }
+
+ if (!babstract)
+ unlink(un.sun_path);
+
+ return NULL;
+}
+
+static void *make_unix_in_new_netns(const struct factory *factory, struct fdesc fdescs[],
+ int argc, char ** argv)
+{
+ struct arg type = decode_arg("type", factory->params, argc, argv);
+ const char *stype = ARG_STRING(type);
+
+ struct arg path = decode_arg("path", factory->params, argc, argv);
+ const char *spath = ARG_STRING(path);
+
+ struct arg abstract = decode_arg("abstract", factory->params, argc, argv);
+ bool babstract = ARG_BOOLEAN(abstract);
+
+ int typesym;
+ const char *typestr;
+
+ struct sockaddr_un un;
+ size_t un_len = sizeof(un);
+
+ int self_netns, tmp_netns, sd;
+
+ if (strcmp(stype, "stream") == 0) {
+ typesym = SOCK_STREAM;
+ typestr = "STREAM";
+ } else if (strcmp(stype, "seqpacket") == 0) {
+ typesym = SOCK_SEQPACKET;
+ typestr = "SEQPACKET";
+ } else if (strcmp(stype, "dgram") == 0) {
+ typesym = SOCK_DGRAM;
+ typestr = "DGRAM";
+ } else {
+ free_arg(&abstract);
+ free_arg(&path);
+ free_arg(&type);
+ errx(EXIT_FAILURE, _("unknown unix socket type: %s"), stype);
+ }
+
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_UNIX;
+ if (babstract) {
+ strncpy(un.sun_path + 1, spath, sizeof(un.sun_path) - 1 - 1);
+ size_t pathlen = strlen(spath);
+ if (sizeof(un.sun_path) - 1 > pathlen)
+ un_len = sizeof(un) - sizeof(un.sun_path) + 1 + pathlen;
+ } else
+ strncpy(un.sun_path, spath, sizeof(un.sun_path) - 1 );
+
+ free_arg(&abstract);
+ free_arg(&path);
+ free_arg(&type);
+
+ self_netns = open("/proc/self/ns/net", O_RDONLY);
+ if (self_netns < 0)
+ err(EXIT_FAILURE, _("failed to open /proc/self/ns/net"));
+ if (self_netns != fdescs[0].fd) {
+ if (dup2(self_netns, fdescs[0].fd) < 0) {
+ int e = errno;
+ close(self_netns);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", self_netns, fdescs[0].fd);
+ }
+ close(self_netns);
+ self_netns = fdescs[0].fd;
+ }
+
+ fdescs[0] = (struct fdesc){
+ .fd = fdescs[0].fd,
+ .close = close_fdesc,
+ .data = NULL,
+ };
+
+ if (unshare(CLONE_NEWNET) < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ errno = e;
+ err(EXIT_FAILURE, "failed in unshare");
+ }
+
+ tmp_netns = open("/proc/self/ns/net", O_RDONLY);
+ if (tmp_netns < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ errno = e;
+ err(EXIT_FAILURE, _("failed to open /proc/self/ns/net for the new netns"));
+ }
+ if (tmp_netns != fdescs[1].fd) {
+ if (dup2(tmp_netns, fdescs[1].fd) < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ close(tmp_netns);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", tmp_netns, fdescs[1].fd);
+ }
+ close(tmp_netns);
+ tmp_netns = fdescs[1].fd;
+ }
+
+ fdescs[1] = (struct fdesc){
+ .fd = fdescs[1].fd,
+ .close = close_fdesc,
+ .data = NULL,
+ };
+
+ sd = socket(AF_UNIX, typesym, 0);
+ if (sd < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ close_fdesc(tmp_netns, NULL);
+ errno = e;
+ err(EXIT_FAILURE,
+ _("failed to make a socket with AF_UNIX + SOCK_%s"),
+ typestr);
+ }
+
+ if (sd != fdescs[2].fd) {
+ if (dup2(sd, fdescs[2].fd) < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ close_fdesc(tmp_netns, NULL);
+ close(sd);
+ errno = e;
+ err(EXIT_FAILURE, "failed to dup %d -> %d", sd, fdescs[2].fd);
+ }
+ close(sd);
+ sd = fdescs[2].fd;
+ }
+
+ fdescs[2] = (struct fdesc){
+ .fd = fdescs[2].fd,
+ .close = close_unix_socket,
+ .data = NULL,
+ };
+
+ if (!babstract)
+ unlink(un.sun_path);
+ if (bind(sd, (const struct sockaddr *)&un, un_len) < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ close_fdesc(tmp_netns, NULL);
+ close_unix_socket(sd, NULL);
+ errno = e;
+ err(EXIT_FAILURE, "failed to bind a socket");
+ }
+
+ if (!babstract)
+ fdescs[2].data = xstrdup(un.sun_path);
+
+ if (typesym != SOCK_DGRAM) {
+ if (listen(sd, 1) < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ close_fdesc(tmp_netns, NULL);
+ close_unix_socket(sd, fdescs[2].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to listen a socket");
+ }
+ }
+
+ if (setns(self_netns, CLONE_NEWNET) < 0) {
+ int e = errno;
+ close_fdesc(self_netns, NULL);
+ close_fdesc(tmp_netns, NULL);
+ close_unix_socket(sd, fdescs[2].data);
+ errno = e;
+ err(EXIT_FAILURE, "failed to swich back to the original net namespace");
+ }
+
+ return NULL;
}
#define PARAM_END { .name = NULL, }
@@ -730,7 +1231,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = open_ro_regular_file,
.params = (struct parameter []) {
{
@@ -754,7 +1254,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 2,
.EX_N = 2,
- .fork = false,
.make = make_pipe,
.params = (struct parameter []) {
{
@@ -784,7 +1283,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = open_directory,
.params = (struct parameter []) {
{
@@ -808,7 +1306,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = open_rw_chrdev,
.params = (struct parameter []) {
{
@@ -826,7 +1323,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 2,
.EX_N = 0,
- .fork = false,
.make = make_socketpair,
.params = (struct parameter []) {
{
@@ -844,7 +1340,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = open_with_opath,
.params = (struct parameter []) {
{
@@ -862,7 +1357,6 @@ static const struct factory factories[] = {
.priv = true,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = open_ro_blkdev,
.params = (struct parameter []) {
{
@@ -880,7 +1374,6 @@ static const struct factory factories[] = {
.priv = true,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = make_mmapped_packet_socket,
.params = (struct parameter []) {
{
@@ -904,7 +1397,6 @@ static const struct factory factories[] = {
.priv = false,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = make_pidfd,
.params = (struct parameter []) {
{
@@ -922,12 +1414,110 @@ static const struct factory factories[] = {
.priv = false,
.N = 1,
.EX_N = 0,
- .fork = false,
.make = make_inotify_fd,
.params = (struct parameter []) {
PARAM_END
},
},
+ {
+ .name = "unix-stream",
+ .desc = "AF_UNIX+SOCK_STREAM sockets",
+ .priv = false,
+ .N = 3,
+ .EX_N = 0,
+ .make = make_unix_stream,
+ .params = (struct parameter []) {
+ {
+ .name = "path",
+ .type = PTYPE_STRING,
+ .desc = "path for listening-socket bound to",
+ .defv.string = "/tmp/test_mkfds-unix-stream",
+ },
+ {
+ .name = "backlog",
+ .type = PTYPE_INTEGER,
+ .desc = "backlog passed to listen(2)",
+ .defv.integer = 5,
+ },
+ {
+ .name = "abstract",
+ .type = PTYPE_BOOLEAN,
+ .desc = "use PATH as an abstract socket address",
+ .defv.boolean = false,
+ },
+ {
+ .name = "server-shutdown",
+ .type = PTYPE_INTEGER,
+ .desc = "shutdown the accepted socket; 1: R, 2: W, 3: RW",
+ .defv.integer = 0,
+ },
+ {
+ .name = "client-shutdown",
+ .type = PTYPE_INTEGER,
+ .desc = "shutdown the client socket; 1: R, 2: W, 3: RW",
+ .defv.integer = 0,
+ },
+ {
+ .name = "type",
+ .type = PTYPE_STRING,
+ .desc = "stream or seqpacket",
+ .defv.string = "stream",
+ },
+ PARAM_END
+ },
+ },
+ {
+ .name = "unix-dgram",
+ .desc = "AF_UNIX+SOCK_DGRAM sockets",
+ .priv = false,
+ .N = 2,
+ .EX_N = 0,
+ .make = make_unix_dgram,
+ .params = (struct parameter []) {
+ {
+ .name = "path",
+ .type = PTYPE_STRING,
+ .desc = "path for unix non-stream bound to",
+ .defv.string = "/tmp/test_mkfds-unix-dgram",
+ },
+ {
+ .name = "abstract",
+ .type = PTYPE_BOOLEAN,
+ .desc = "use PATH as an abstract socket address",
+ .defv.boolean = false,
+ },
+ PARAM_END
+ },
+ },
+ {
+ .name = "unix-in-netns",
+ .desc = "make a unix socket in a new network namespace",
+ .priv = true,
+ .N = 3,
+ .EX_N = 0,
+ .make = make_unix_in_new_netns,
+ .params = (struct parameter []) {
+ {
+ .name = "type",
+ .type = PTYPE_STRING,
+ .desc = "dgram, stream, or seqpacket",
+ .defv.string = "stream",
+ },
+ {
+ .name = "path",
+ .type = PTYPE_STRING,
+ .desc = "path for unix non-stream bound to",
+ .defv.string = "/tmp/test_mkfds-unix-in-netns",
+ },
+ {
+ .name = "abstract",
+ .type = PTYPE_BOOLEAN,
+ .desc = "use PATH as an abstract socket address",
+ .defv.boolean = false,
+ },
+ PARAM_END
+ },
+ },
};
static int count_parameters(const struct factory *factory)
@@ -943,18 +1533,17 @@ static int count_parameters(const struct factory *factory)
static void print_factory(const struct factory *factory)
{
- printf("%-20s %4s %5d %4s %6d %s\n",
+ printf("%-20s %4s %5d %6d %s\n",
factory->name,
factory->priv? "yes": "no",
factory->N,
- factory->fork? "yes": "no",
count_parameters(factory),
factory->desc);
}
static void list_factories(void)
{
- printf("%-20s PRIV COUNT FORK NPARAM DESCRIPTION\n", "FACTORY");
+ printf("%-20s PRIV COUNT NPARAM DESCRIPTION\n", "FACTORY");
for (size_t i = 0; i < ARRAY_SIZE(factories); i++)
print_factory(factories + i);
}
@@ -1012,17 +1601,35 @@ pidfd_open(pid_t pid _U_, unsigned int flags _U_)
}
#endif
+static void wait_event(void)
+{
+ fd_set readfds;
+ sigset_t sigset;
+ int n = 0;
+
+ FD_ZERO(&readfds);
+ /* Monitor the standard input only when the process
+ * is in foreground. */
+ if (tcgetpgrp(STDIN_FILENO) == getpgrp()) {
+ n = 1;
+ FD_SET(0, &readfds);
+ }
+
+ sigemptyset(&sigset);
+
+ if (pselect(n, &readfds, NULL, NULL, NULL, &sigset) < 0
+ && errno != EINTR)
+ errx(EXIT_FAILURE, _("failed in pselect"));
+}
+
int main(int argc, char **argv)
{
int c;
- pid_t pid[2];
const struct factory *factory;
struct fdesc fdescs[MAX_N];
bool quiet = false;
bool cont = false;
-
- pid[0] = getpid();
- pid[1] = -1;
+ void *data;
static const struct option longopts[] = {
{ "list", no_argument, NULL, 'l' },
@@ -1057,7 +1664,6 @@ int main(int argc, char **argv)
}
}
-
if (optind == argc)
errx(EXIT_FAILURE, _("no file descriptor specification given"));
@@ -1071,8 +1677,10 @@ int main(int argc, char **argv)
errx(EXIT_FAILURE, _("not enough file descriptors given for %s"),
factory->name);
- for (int i = 0; i < MAX_N; i++)
+ for (int i = 0; i < MAX_N; i++) {
fdescs[i].fd = -1;
+ fdescs[i].close = NULL;
+ }
for (int i = 0; i < factory->N; i++) {
char *str = argv[optind + i];
@@ -1095,24 +1703,27 @@ int main(int argc, char **argv)
}
optind += factory->N;
- factory->make(factory, fdescs, pid + 1, argc - optind, argv + optind);
+ data = factory->make(factory, fdescs, argc - optind, argv + optind);
signal(SIGCONT, do_nothing);
if (!quiet) {
- printf("%d", pid[0]);
- if (pid[1] != -1)
- printf(" %d", pid[1]);
+ printf("%d", getpid());
putchar('\n');
+ if (factory->report)
+ factory->report(factory, data, stdout);
fflush(stdout);
}
if (!cont)
- pause();
+ wait_event();
for (int i = 0; i < factory->N + factory->EX_N; i++)
- if (fdescs[i].fd >= 0)
+ if (fdescs[i].fd >= 0 && fdescs[i].close)
fdescs[i].close(fdescs[i].fd, fdescs[i].data);
+ if (factory->free)
+ factory->free (factory, data);
+
exit(EXIT_SUCCESS);
}
diff --git a/tests/ts/lsfd/mkfds-unix-dgram b/tests/ts/lsfd/mkfds-unix-dgram
new file mode 100755
index 0000000000..82710e912c
--- /dev/null
+++ b/tests/ts/lsfd/mkfds-unix-dgram
@@ -0,0 +1,60 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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="UNIX dgram sockets"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_LSFD"
+ts_check_test_command "$TS_HELPER_MKFDS"
+
+ts_cd "$TS_OUTDIR"
+
+PID=
+FDS=3
+FDC=4
+EXPR='(TYPE == "UNIX") and ((FD == 3) or (FD == 4))'
+
+{
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-dgram $FDS $FDC \
+ path=test_mkfds-unix-dgram ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo 'ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-dgram $FDS $FDC \
+ path=test_mkfds-unix-dgram \
+ abstract=true ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo 'ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+} > "$TS_OUTPUT" 2>&1
+
+ts_finalize
diff --git a/tests/ts/lsfd/mkfds-unix-in-netns b/tests/ts/lsfd/mkfds-unix-in-netns
new file mode 100755
index 0000000000..d57edc6f03
--- /dev/null
+++ b/tests/ts/lsfd/mkfds-unix-in-netns
@@ -0,0 +1,91 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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="UNIX sockets made in a differenct net namespace"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+ts_skip_nonroot
+
+ts_check_test_command "$TS_CMD_LSFD"
+
+ts_check_test_command "$TS_HELPER_MKFDS"
+
+ts_cd "$TS_OUTDIR"
+
+PID=
+FDSELFNS=3
+FDALTNS=4
+FDSOCK=5
+
+EXPR='((TYPE == "UNIX") or (TYPE == "UNIX-STREAM")) and (FD == 5)'
+
+compare_net_namespaces()
+{
+ local type=$1
+ local pid=$2
+ local altns_inode
+ local sock_netns
+
+ altns_inode=$(${TS_CMD_LSFD} -n -o INODE -p "${pid}" -Q '(FD == 4)')
+ sock_netns=$(${TS_CMD_LSFD} -n -o SOCKNETNS -p "${pid}" -Q '(FD == 5)')
+
+ if [[ "${altns_inode}" == "${sock_netns}" ]]; then
+ echo "the netns for the $type socket is extracted as expectedly"
+ else
+ echo "the netns for the $type socket is not extracted well"
+ echo "altns_inode=${altns_inode}"
+ echo "sock_netns=${sock_netns}"
+ fi
+}
+
+{
+ for t in stream dgram seqpacket; do
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-in-netns $FDSELFNS $FDALTNS $FDSOCK \
+ path=test_mkfds-unix-$t-ns \
+ type=$t ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo 'ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+
+ compare_net_namespaces "$t" "${PID}"
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-in-netns $FDSELFNS $FDALTNS $FDSOCK \
+ path=test_mkfds-unix-$t-ns \
+ abstract=true \
+ type=$t ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo 'ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+
+ compare_net_namespaces "abstract $t" "${PID}"
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+ done
+} > "$TS_OUTPUT" 2>&1
+
+ts_finalize
diff --git a/tests/ts/lsfd/mkfds-unix-stream b/tests/ts/lsfd/mkfds-unix-stream
new file mode 100755
index 0000000000..767dc89349
--- /dev/null
+++ b/tests/ts/lsfd/mkfds-unix-stream
@@ -0,0 +1,82 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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="UNIX stream sockets"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_LSFD"
+ts_check_test_command "$TS_HELPER_MKFDS"
+
+ts_cd "$TS_OUTDIR"
+
+PID=
+FDS=3
+FDC=4
+FDA=5
+EXPR='(((TYPE == "UNIX-STREAM") or (TYPE == "UNIX")) and (FD >= 3) and (FD <= 5))'
+
+{
+ for t in stream seqpacket; do
+ # TYPE is not tested here; AF_UNIX+SOCK_STREAM socket type was changed from "UNIX"
+ # to "UNIX-STREAM" at a time in Linux kernel development history.
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-stream $FDS $FDC $FDA \
+ path=test_mkfds-unix-${t} \
+ type=$t ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo 'ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-stream $FDS $FDC $FDA \
+ path=test_mkfds-unix-${t}-abs \
+ abstract=true ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo '(abs) ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+
+ coproc MKFDS { "$TS_HELPER_MKFDS" unix-stream $FDS $FDC $FDA \
+ path=test_mkfds-unix-${t}-shutdown \
+ server-shutdown=3 \
+ client-shutdown=3 \
+ type=$t ; }
+ if read -r -u "${MKFDS[0]}" PID; then
+ ${TS_CMD_LSFD} -n \
+ -o ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH \
+ -p "${PID}" -Q "${EXPR}"
+ echo '(shutdown) ASSOC,STTYPE,NAME,SOCKSTATE,SOCKTYPE,UNIX.PATH': $?
+ # Surprisingly, the socket status doesn't change at all.
+
+ kill -CONT "${PID}"
+ wait "${MKFDS_PID}"
+ fi
+ done
+} > "$TS_OUTPUT" 2>&1
+
+ts_finalize