]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
samples: add a mountinfo program to demonstrate statmount()/listmount()
authorJeff Layton <jlayton@kernel.org>
Fri, 15 Nov 2024 15:35:52 +0000 (10:35 -0500)
committerChristian Brauner <brauner@kernel.org>
Thu, 9 Jan 2025 15:58:50 +0000 (16:58 +0100)
Add a new "mountinfo" sample userland program that demonstrates how to
use statmount() and listmount() to get at the same info that
/proc/pid/mountinfo provides.

The output of the program tries to mimic the mountinfo procfile
contents. With the -p flag, it can be pointed at an arbitrary pid to
print out info about its mount namespace. With the -r flag it will
attempt to walk all of the namespaces under the pid's mount namespace
and dump out mount info from all of them.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://lore.kernel.org/r/20241115-statmount-v2-1-cd29aeff9cbb@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
samples/vfs/.gitignore
samples/vfs/Makefile
samples/vfs/mountinfo.c [new file with mode: 0644]

index 79212d91285bca72b0ff85f28aaccd2e803ac092..33a03cffe072fe2466c9df30ad47e9c58b0eea7c 100644 (file)
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 /test-fsmount
 /test-statx
+/mountinfo
index 6377a678134acf0d682151d751d2f5042dbf5e0a..fb9bb33fdc751556e806aa897f0dbd48f7e3a4d8 100644 (file)
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
-userprogs-always-y += test-fsmount test-statx
+userprogs-always-y += test-fsmount test-statx mountinfo
 
 userccflags += -I usr/include
diff --git a/samples/vfs/mountinfo.c b/samples/vfs/mountinfo.c
new file mode 100644 (file)
index 0000000..2b17d24
--- /dev/null
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Use pidfds, nsfds, listmount() and statmount() mimic the
+ * contents of /proc/self/mountinfo.
+ */
+#define _GNU_SOURCE
+#define __SANE_USERSPACE_TYPES__
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <linux/pidfd.h>
+#include <linux/mount.h>
+#include <linux/nsfs.h>
+#include <unistd.h>
+#include <alloca.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+
+/* max mounts per listmount call */
+#define MAXMOUNTS              1024
+
+/* size of struct statmount (including trailing string buffer) */
+#define STATMOUNT_BUFSIZE      4096
+
+static bool ext_format;
+
+/*
+ * There are no bindings in glibc for listmount() and statmount() (yet),
+ * make our own here.
+ */
+static int statmount(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask,
+                           struct statmount *buf, size_t bufsize,
+                           unsigned int flags)
+{
+       struct mnt_id_req req = {
+               .size = MNT_ID_REQ_SIZE_VER0,
+               .mnt_id = mnt_id,
+               .param = mask,
+       };
+
+       if (mnt_ns_id) {
+               req.size = MNT_ID_REQ_SIZE_VER1;
+               req.mnt_ns_id = mnt_ns_id;
+       }
+
+       return syscall(__NR_statmount, &req, buf, bufsize, flags);
+}
+
+static ssize_t listmount(uint64_t mnt_id, uint64_t mnt_ns_id,
+                        uint64_t last_mnt_id, uint64_t list[], size_t num,
+                        unsigned int flags)
+{
+       struct mnt_id_req req = {
+               .size = MNT_ID_REQ_SIZE_VER0,
+               .mnt_id = mnt_id,
+               .param = last_mnt_id,
+       };
+
+       if (mnt_ns_id) {
+               req.size = MNT_ID_REQ_SIZE_VER1;
+               req.mnt_ns_id = mnt_ns_id;
+       }
+
+       return syscall(__NR_listmount, &req, list, num, flags);
+}
+
+static void show_mnt_attrs(uint64_t flags)
+{
+       printf("%s", flags & MOUNT_ATTR_RDONLY ? "ro" : "rw");
+
+       if (flags & MOUNT_ATTR_NOSUID)
+               printf(",nosuid");
+       if (flags & MOUNT_ATTR_NODEV)
+               printf(",nodev");
+       if (flags & MOUNT_ATTR_NOEXEC)
+               printf(",noexec");
+
+       switch (flags & MOUNT_ATTR__ATIME) {
+       case MOUNT_ATTR_RELATIME:
+               printf(",relatime");
+               break;
+       case MOUNT_ATTR_NOATIME:
+               printf(",noatime");
+               break;
+       case MOUNT_ATTR_STRICTATIME:
+               /* print nothing */
+               break;
+       }
+
+       if (flags & MOUNT_ATTR_NODIRATIME)
+               printf(",nodiratime");
+       if (flags & MOUNT_ATTR_NOSYMFOLLOW)
+               printf(",nosymfollow");
+       if (flags & MOUNT_ATTR_IDMAP)
+               printf(",idmapped");
+}
+
+static void show_propagation(struct statmount *sm)
+{
+       if (sm->mnt_propagation & MS_SHARED)
+               printf(" shared:%llu", sm->mnt_peer_group);
+       if (sm->mnt_propagation & MS_SLAVE) {
+               printf(" master:%llu", sm->mnt_master);
+               if (sm->propagate_from && sm->propagate_from != sm->mnt_master)
+                       printf(" propagate_from:%llu", sm->propagate_from);
+       }
+       if (sm->mnt_propagation & MS_UNBINDABLE)
+               printf(" unbindable");
+}
+
+static void show_sb_flags(uint64_t flags)
+{
+       printf("%s", flags & MS_RDONLY ? "ro" : "rw");
+       if (flags & MS_SYNCHRONOUS)
+               printf(",sync");
+       if (flags & MS_DIRSYNC)
+               printf(",dirsync");
+       if (flags & MS_MANDLOCK)
+               printf(",mand");
+       if (flags & MS_LAZYTIME)
+               printf(",lazytime");
+}
+
+static int dump_mountinfo(uint64_t mnt_id, uint64_t mnt_ns_id)
+{
+       int ret;
+       struct statmount *buf = alloca(STATMOUNT_BUFSIZE);
+       const uint64_t mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC |
+                               STATMOUNT_PROPAGATE_FROM | STATMOUNT_FS_TYPE |
+                               STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT |
+                               STATMOUNT_MNT_OPTS | STATMOUNT_FS_SUBTYPE |
+                               STATMOUNT_SB_SOURCE;
+
+       ret = statmount(mnt_id, mnt_ns_id, mask, buf, STATMOUNT_BUFSIZE, 0);
+       if (ret < 0) {
+               perror("statmount");
+               return 1;
+       }
+
+       if (ext_format)
+               printf("0x%lx 0x%lx 0x%llx ", mnt_ns_id, mnt_id, buf->mnt_parent_id);
+
+       printf("%u %u %u:%u %s %s ", buf->mnt_id_old, buf->mnt_parent_id_old,
+                                  buf->sb_dev_major, buf->sb_dev_minor,
+                                  &buf->str[buf->mnt_root],
+                                  &buf->str[buf->mnt_point]);
+       show_mnt_attrs(buf->mnt_attr);
+       show_propagation(buf);
+
+       printf(" - %s", &buf->str[buf->fs_type]);
+       if (buf->mask & STATMOUNT_FS_SUBTYPE)
+               printf(".%s", &buf->str[buf->fs_subtype]);
+       if (buf->mask & STATMOUNT_SB_SOURCE)
+               printf(" %s ", &buf->str[buf->sb_source]);
+       else
+               printf(" :none ");
+
+       show_sb_flags(buf->sb_flags);
+       if (buf->mask & STATMOUNT_MNT_OPTS)
+               printf(",%s", &buf->str[buf->mnt_opts]);
+       printf("\n");
+       return 0;
+}
+
+static int dump_mounts(uint64_t mnt_ns_id)
+{
+       uint64_t mntid[MAXMOUNTS];
+       uint64_t last_mnt_id = 0;
+       ssize_t count;
+       int i;
+
+       /*
+        * Get a list of all mntids in mnt_ns_id. If it returns MAXMOUNTS
+        * mounts, then go again until we get everything.
+        */
+       do {
+               count = listmount(LSMT_ROOT, mnt_ns_id, last_mnt_id, mntid, MAXMOUNTS, 0);
+               if (count < 0 || count > MAXMOUNTS) {
+                       errno = count < 0 ? errno : count;
+                       perror("listmount");
+                       return 1;
+               }
+
+               /* Walk the returned mntids and print info about each */
+               for (i = 0; i < count; ++i) {
+                       int ret = dump_mountinfo(mntid[i], mnt_ns_id);
+
+                       if (ret != 0)
+                               return ret;
+               }
+               /* Set up last_mnt_id to pick up where we left off */
+               last_mnt_id = mntid[count - 1];
+       } while (count == MAXMOUNTS);
+       return 0;
+}
+
+static void usage(const char * const prog)
+{
+       printf("Usage:\n");
+       printf("%s [-e] [-p pid] [-r] [-h]\n", prog);
+       printf("    -e: extended format\n");
+       printf("    -h: print usage message\n");
+       printf("    -p: get mount namespace from given pid\n");
+       printf("    -r: recursively print all mounts in all child namespaces\n");
+}
+
+int main(int argc, char * const *argv)
+{
+       struct mnt_ns_info mni = { .size = MNT_NS_INFO_SIZE_VER0 };
+       int pidfd, mntns, ret, opt;
+       pid_t pid = getpid();
+       bool recursive = false;
+
+       while ((opt = getopt(argc, argv, "ehp:r")) != -1) {
+               switch (opt) {
+               case 'e':
+                       ext_format = true;
+                       break;
+               case 'h':
+                       usage(argv[0]);
+                       return 0;
+               case 'p':
+                       pid = atoi(optarg);
+                       break;
+               case 'r':
+                       recursive = true;
+                       break;
+               }
+       }
+
+       /* Get a pidfd for pid */
+       pidfd = syscall(SYS_pidfd_open, pid, 0);
+       if (pidfd < 0) {
+               perror("pidfd_open");
+               return 1;
+       }
+
+       /* Get the mnt namespace for pidfd */
+       mntns = ioctl(pidfd, PIDFD_GET_MNT_NAMESPACE, NULL);
+       if (mntns < 0) {
+               perror("PIDFD_GET_MNT_NAMESPACE");
+               return 1;
+       }
+       close(pidfd);
+
+       /* get info about mntns. In particular, the mnt_ns_id */
+       ret = ioctl(mntns, NS_MNT_GET_INFO, &mni);
+       if (ret < 0) {
+               perror("NS_MNT_GET_INFO");
+               return 1;
+       }
+
+       do {
+               int ret;
+
+               ret = dump_mounts(mni.mnt_ns_id);
+               if (ret)
+                       return ret;
+
+               if (!recursive)
+                       break;
+
+               /* get the next mntns (and overwrite the old mount ns info) */
+               ret = ioctl(mntns, NS_MNT_GET_NEXT, &mni);
+               close(mntns);
+               mntns = ret;
+       } while (mntns >= 0);
+
+       return 0;
+}