diff options
author | Masatake YAMATO <yamato@redhat.com> | 2024-02-25 04:56:51 +0900 |
---|---|---|
committer | Masatake YAMATO <yamato@redhat.com> | 2024-04-02 23:44:25 +0900 |
commit | 7d5036fdafe0498a09146a5637a9f7f8351e6e72 (patch) | |
tree | 34aab78d58f09e7ab6e828e3ce3e35ac4d3f95f9 | |
parent | d6cdbf8d0d1dcfdb73d138b524acdea6575ead73 (diff) | |
download | util-linux-7d5036fdafe0498a09146a5637a9f7f8351e6e72.tar.gz |
lsns: show namespaces only kept alive by open file descriptors
Close #1884.
Quoted from the original issue comment submitted by @hesch:
It can happen, that a namespace is only kept alive by an open file
descriptor of a program as ilustrated by A.B:
1. 'ip netns add foo' - add a namespace
2. 'sleep 999 4< /run/netns/foo & sleep 2' - open the fd to the
namespace in a background job
3. 'ip netns delete foo' - delete the namespace (only deletes
the /run/netns/foo)
Now there exists a namespace with no process running in it and it has
no bind mount so it does not show up in /proc/mounts, but it is still
there and could be mounted back.
Signed-off-by: Masatake YAMATO <yamato@redhat.com>
-rw-r--r-- | sys-utils/lsns.c | 42 | ||||
-rwxr-xr-x | tests/ts/lsns/filedesc | 71 |
2 files changed, 113 insertions, 0 deletions
diff --git a/sys-utils/lsns.c b/sys-utils/lsns.c index c328f046e1..9d12ca205b 100644 --- a/sys-utils/lsns.c +++ b/sys-utils/lsns.c @@ -218,6 +218,7 @@ struct lsns { no_headings: 1, no_wrap : 1; + dev_t nsfs_dev; struct libmnt_table *tab; struct libscols_filter *filter; @@ -501,6 +502,33 @@ static int get_netnsid(struct path_cxt *pc __attribute__((__unused__)), } #endif /* HAVE_LINUX_NET_NAMESPACE_H */ +static struct lsns_namespace *add_namespace_for_nsfd(struct lsns *ls, int fd, ino_t ino); + +static void read_open_ns_inos(struct lsns *ls, struct path_cxt *pc) +{ + DIR *sub = NULL; + struct dirent *d = NULL; + char path[sizeof("fd/") + sizeof(stringify_value(UINT64_MAX))]; + + while (ul_path_next_dirent(pc, &sub, "fd", &d) == 0) { + uint64_t num; + struct stat st; + + if (ul_strtou64(d->d_name, &num, 10) != 0) /* only numbers */ + continue; + + snprintf(path, sizeof(path), "fd/%ju", (uintmax_t) num); + ul_path_stat(pc, &st, 0, path); + if (st.st_dev == ls->nsfs_dev) { + int fd = ul_path_open(pc, O_RDONLY, path); + if (fd >= 0) { + add_namespace_for_nsfd(ls, fd, st.st_ino); + close(fd); + } + } + } +} + static int read_process(struct lsns *ls, struct path_cxt *pc) { struct lsns_process *p = NULL; @@ -539,6 +567,8 @@ static int read_process(struct lsns *ls, struct path_cxt *pc) DBG(PROC, ul_debugobj(p, "new pid=%d", p->pid)); list_add_tail(&p->processes, &ls->processes); + + read_open_ns_inos(ls, pc); done: if (rc) free(p); @@ -1421,6 +1451,16 @@ static void __attribute__((__noreturn__)) list_colunms(bool raw, bool json) exit(EXIT_SUCCESS); } +static dev_t read_nsfs_dev(void) +{ + struct stat st; + + if (stat("/proc/self/ns/user", &st) < 0) + err(EXIT_FAILURE, _("failed to do stat /proc/self/ns/user")); + + return st.st_dev; +} + int main(int argc, char *argv[]) { struct lsns ls; @@ -1607,6 +1647,8 @@ int main(int argc, char *argv[]) if (!ls.tab) err(MNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO); + ls.nsfs_dev = read_nsfs_dev(); + r = read_processes(&ls); if (!r) r = read_namespaces(&ls); diff --git a/tests/ts/lsns/filedesc b/tests/ts/lsns/filedesc new file mode 100755 index 0000000000..285ad6312c --- /dev/null +++ b/tests/ts/lsns/filedesc @@ -0,0 +1,71 @@ +#!/bin/bash +# +# Copyright (C) 2024 Masatake YAMATO <yamato@redhat.com> +# +# This file is part of util-linux. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +# This test case is based on the issue (#1884) submitted by @hesch +TS_TOPDIR="${0%/*}/../.." +TS_DESC="list the namespace pointed by a file descriptor" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command "$TS_CMD_LSNS" +ts_check_test_command "$TS_CMD_LSFD" +ts_check_test_command "$TS_HELPER_MKFDS" + +ts_check_prog "ip" + +ts_skip_nonroot + +FD=4 +NS=LSNS-TEST-FILEDESC-NS +FILE=/run/netns/$NS +PID= + +cleanup() +{ + ip netns delete $NS 2> /dev/null || : +} + +cleanup + +if ! ip netns add $NS; then + ts_skip "failed to make a namespace" +fi +trap "cleanup" EXIT + +{ + coproc MKFDS { "$TS_HELPER_MKFDS" ro-regular-file $FD file=$FILE; } + if read -u ${MKFDS[0]} PID; then + # Make the namespace invisible from the file system tree. + cleanup + lsfd_expr="PID == ${PID} and FD == $FD" + lsfd_inode=$(${TS_CMD_LSFD} -n --raw -o INODE -Q "${lsfd_expr}") + lsns_expr="NS == $lsfd_inode" + lsns_output=$(${TS_CMD_LSNS} -n --raw -o TYPE,NPROCS,USER -Q "${lsns_expr}") + if ! [ "${lsns_output}" == "net 0 root" ]; then + echo lsfd_inode: $lsfd_inode + echo lsns_output: $lsns_output + echo LSFD: + ${TS_CMD_LSFD} -Q "PID == $PID" + echo LSNS: + ${TS_CMD_LSNS} + fi + echo DONE >&"${MKFDS[1]}" + fi + wait "${MKFDS_PID}" +} > $TS_OUTPUT 2>&1 +ts_finalize |