diff options
| author | Karel Zak <kzak@redhat.com> | 2022-09-30 10:44:55 +0200 |
|---|---|---|
| committer | Karel Zak <kzak@redhat.com> | 2022-09-30 10:44:55 +0200 |
| commit | e70e5258c05b856842ce5bd2ab2d6cb4d440162b (patch) | |
| tree | 65521f077e5ce93c500febb376fa174e3c324cf7 | |
| parent | e0b15af484e6e0c05d3af1bf6cd226ca9252849a (diff) | |
| parent | 55529a2620d5fd3cc549c38bf50455748da9020f (diff) | |
| download | util-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.am | 2 | ||||
| -rw-r--r-- | misc-utils/lsfd-sock-xinfo.c | 380 | ||||
| -rw-r--r-- | misc-utils/lsfd-sock.c | 61 | ||||
| -rw-r--r-- | misc-utils/lsfd-sock.h | 72 | ||||
| -rw-r--r-- | misc-utils/lsfd.1.adoc | 28 | ||||
| -rw-r--r-- | misc-utils/lsfd.c | 31 | ||||
| -rw-r--r-- | misc-utils/lsfd.h | 12 | ||||
| -rw-r--r-- | misc-utils/meson.build | 2 | ||||
| -rw-r--r-- | tests/expected/lsfd/column-name | 2 | ||||
| -rw-r--r-- | tests/expected/lsfd/mkfds-unix-dgram | 6 | ||||
| -rw-r--r-- | tests/expected/lsfd/mkfds-unix-in-netns | 18 | ||||
| -rw-r--r-- | tests/expected/lsfd/mkfds-unix-stream | 24 | ||||
| -rw-r--r-- | tests/helpers/test_mkfds.c | 705 | ||||
| -rwxr-xr-x | tests/ts/lsfd/mkfds-unix-dgram | 60 | ||||
| -rwxr-xr-x | tests/ts/lsfd/mkfds-unix-in-netns | 91 | ||||
| -rwxr-xr-x | tests/ts/lsfd/mkfds-unix-stream | 82 |
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 |
