samples: add a mountinfo program to demonstrate statmount()/listmount()

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>
This commit is contained in:
Jeff Layton 2024-11-15 10:35:52 -05:00 committed by Christian Brauner
parent a48bdf80ce
commit 0c8f746452
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
3 changed files with 273 additions and 1 deletions

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
/test-fsmount
/test-statx
/mountinfo

View 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

271
samples/vfs/mountinfo.c Normal file
View File

@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Use pidfds, nsfds, listmount() and statmount() mimic the
* contents of /proc/self/mountinfo.
*/
#define _GNU_SOURCE
#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_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->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;
}