diff mbox series

[5/5] samples: add test-list-all-mounts

Message ID 20241210-work-mount-rbtree-lockless-v1-5-338366b9bbe4@kernel.org (mailing list archive)
State New
Headers show
Series fs: lockless mntns rbtree lookup | expand

Commit Message

Christian Brauner Dec. 10, 2024, 8:58 p.m. UTC
Add a sample program illustrating how to list all mounts in all mount
namespaces.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 samples/vfs/.gitignore             |   1 +
 samples/vfs/Makefile               |   2 +-
 samples/vfs/test-list-all-mounts.c | 235 +++++++++++++++++++++++++++++++++++++
 3 files changed, 237 insertions(+), 1 deletion(-)

Comments

Jeff Layton Dec. 11, 2024, 2:52 p.m. UTC | #1
On Tue, 2024-12-10 at 21:58 +0100, Christian Brauner wrote:
> Add a sample program illustrating how to list all mounts in all mount
> namespaces.
> 
> Signed-off-by: Christian Brauner <brauner@kernel.org>
> ---
>  samples/vfs/.gitignore             |   1 +
>  samples/vfs/Makefile               |   2 +-
>  samples/vfs/test-list-all-mounts.c | 235 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 237 insertions(+), 1 deletion(-)
> 
> diff --git a/samples/vfs/.gitignore b/samples/vfs/.gitignore
> index 79212d91285bca72b0ff85f28aaccd2e803ac092..8694dd17b318768b975ece5c7cd450c2cca67318 100644
> --- a/samples/vfs/.gitignore
> +++ b/samples/vfs/.gitignore
> @@ -1,3 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>  /test-fsmount
> +/test-list-all-mounts
>  /test-statx
> diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
> index 6377a678134acf0d682151d751d2f5042dbf5e0a..301be72a52a0e376c7ebe235cc2058992919cc78 100644
> --- a/samples/vfs/Makefile
> +++ b/samples/vfs/Makefile
> @@ -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 test-list-all-mounts
> 
> 

There will be a minor merge conflict above vs. the new mountinfo
program sitting in your tree.

>  
>  userccflags += -I usr/include
> diff --git a/samples/vfs/test-list-all-mounts.c b/samples/vfs/test-list-all-mounts.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..f372d5aea4717fd1ab3d4b3f9af79316cd5dd3d3
> --- /dev/null
> +++ b/samples/vfs/test-list-all-mounts.c
> @@ -0,0 +1,235 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +// Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
> +
> +#define _GNU_SOURCE
> +#include <errno.h>
> +#include <limits.h>
> +#include <linux/types.h>
> +#include <stdio.h>
> +#include <sys/ioctl.h>
> +#include <sys/syscall.h>
> +
> +#include "../../tools/testing/selftests/pidfd/pidfd.h"
> +
> +#define die_errno(format, ...)                                             \
> +	do {                                                               \
> +		fprintf(stderr, "%m | %s: %d: %s: " format "\n", __FILE__, \
> +			__LINE__, __func__, ##__VA_ARGS__);                \
> +		exit(EXIT_FAILURE);                                        \
> +	} while (0)
> +
> +/* Get the id for a mount namespace */
> +#define NS_GET_MNTNS_ID _IO(0xb7, 0x5)
> +/* Get next mount namespace. */
> +
> +struct mnt_ns_info {
> +	__u32 size;
> +	__u32 nr_mounts;
> +	__u64 mnt_ns_id;
> +};
> +
> +#define MNT_NS_INFO_SIZE_VER0 16 /* size of first published struct */
> +
> +/* Get information about namespace. */
> +#define NS_MNT_GET_INFO _IOR(0xb7, 10, struct mnt_ns_info)
> +/* Get next namespace. */
> +#define NS_MNT_GET_NEXT _IOR(0xb7, 11, struct mnt_ns_info)
> +/* Get previous namespace. */
> +#define NS_MNT_GET_PREV _IOR(0xb7, 12, struct mnt_ns_info)
> +
> +#define PIDFD_GET_MNT_NAMESPACE _IO(0xFF, 3)
> +
> +#ifndef __NR_listmount
> +#define __NR_listmount 458
> +#endif
> +
> +#ifndef __NR_statmount
> +#define __NR_statmount 457
> +#endif
> +
> +/* @mask bits for statmount(2) */
> +#define STATMOUNT_SB_BASIC		0x00000001U /* Want/got sb_... */
> +#define STATMOUNT_MNT_BASIC		0x00000002U /* Want/got mnt_... */
> +#define STATMOUNT_PROPAGATE_FROM	0x00000004U /* Want/got propagate_from */
> +#define STATMOUNT_MNT_ROOT		0x00000008U /* Want/got mnt_root  */
> +#define STATMOUNT_MNT_POINT		0x00000010U /* Want/got mnt_point */
> +#define STATMOUNT_FS_TYPE		0x00000020U /* Want/got fs_type */
> +#define STATMOUNT_MNT_NS_ID		0x00000040U /* Want/got mnt_ns_id */
> +#define STATMOUNT_MNT_OPTS		0x00000080U /* Want/got mnt_opts */
> +
> +#define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */
> +
> +struct statmount {
> +	__u32 size;
> +	__u32 mnt_opts;
> +	__u64 mask;
> +	__u32 sb_dev_major;
> +	__u32 sb_dev_minor;
> +	__u64 sb_magic;
> +	__u32 sb_flags;
> +	__u32 fs_type;
> +	__u64 mnt_id;
> +	__u64 mnt_parent_id;
> +	__u32 mnt_id_old;
> +	__u32 mnt_parent_id_old;
> +	__u64 mnt_attr;
> +	__u64 mnt_propagation;
> +	__u64 mnt_peer_group;
> +	__u64 mnt_master;
> +	__u64 propagate_from;
> +	__u32 mnt_root;
> +	__u32 mnt_point;
> +	__u64 mnt_ns_id;
> +	__u64 __spare2[49];
> +	char str[];
> +};
> +
> +struct mnt_id_req {
> +	__u32 size;
> +	__u32 spare;
> +	__u64 mnt_id;
> +	__u64 param;
> +	__u64 mnt_ns_id;
> +};
> +
> +#define MNT_ID_REQ_SIZE_VER1 32 /* sizeof second published struct */
> +
> +#define LSMT_ROOT 0xffffffffffffffff /* root mount */
> +
> +static int __statmount(__u64 mnt_id, __u64 mnt_ns_id, __u64 mask,
> +		       struct statmount *stmnt, size_t bufsize,
> +		       unsigned int flags)
> +{
> +	struct mnt_id_req req = {
> +		.size		= MNT_ID_REQ_SIZE_VER1,
> +		.mnt_id		= mnt_id,
> +		.param		= mask,
> +		.mnt_ns_id	= mnt_ns_id,
> +	};
> +
> +	return syscall(__NR_statmount, &req, stmnt, bufsize, flags);
> +}
> +
> +static struct statmount *sys_statmount(__u64 mnt_id, __u64 mnt_ns_id,
> +				       __u64 mask, unsigned int flags)
> +{
> +	size_t bufsize = 1 << 15;
> +	struct statmount *stmnt = NULL, *tmp = NULL;
> +	int ret;
> +
> +	for (;;) {
> +		tmp = realloc(stmnt, bufsize);
> +		if (!tmp)
> +			goto out;
> +
> +		stmnt = tmp;
> +		ret = __statmount(mnt_id, mnt_ns_id, mask, stmnt, bufsize, flags);
> +		if (!ret)
> +			return stmnt;
> +
> +		if (errno != EOVERFLOW)
> +			goto out;
> +
> +		bufsize <<= 1;
> +		if (bufsize >= UINT_MAX / 2)
> +			goto out;
> +	}
> +
> +out:
> +	free(stmnt);
> +	return NULL;
> +}
> +
> +static ssize_t sys_listmount(__u64 mnt_id, __u64 last_mnt_id, __u64 mnt_ns_id,
> +			     __u64 list[], size_t num, unsigned int flags)
> +{
> +	struct mnt_id_req req = {
> +		.size		= MNT_ID_REQ_SIZE_VER1,
> +		.mnt_id		= mnt_id,
> +		.param		= last_mnt_id,
> +		.mnt_ns_id	= mnt_ns_id,
> +	};
> +
> +	return syscall(__NR_listmount, &req, list, num, flags);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +#define LISTMNT_BUFFER 10
> +	__u64 list[LISTMNT_BUFFER], last_mnt_id = 0;
> +	int ret, pidfd, fd_mntns;
> +	struct mnt_ns_info info = {};
> +
> +	pidfd = sys_pidfd_open(getpid(), 0);
> +	if (pidfd < 0)
> +		die_errno("pidfd_open failed");
> +
> +	fd_mntns = ioctl(pidfd, PIDFD_GET_MNT_NAMESPACE, 0);
> +	if (fd_mntns < 0)
> +		die_errno("ioctl(PIDFD_GET_MNT_NAMESPACE) failed");
> +
> +	ret = ioctl(fd_mntns, NS_MNT_GET_INFO, &info);
> +	if (ret < 0)
> +		die_errno("ioctl(NS_GET_MNTNS_ID) failed");
> +
> +	printf("Listing %u mounts for mount namespace %llu\n",
> +	       info.nr_mounts, info.mnt_ns_id);
> +	for (;;) {
> +		ssize_t nr_mounts;
> +next:
> +		nr_mounts = sys_listmount(LSMT_ROOT, last_mnt_id,
> +					  info.mnt_ns_id, list, LISTMNT_BUFFER,
> +					  0);
> +		if (nr_mounts <= 0) {
> +			int fd_mntns_next;
> +
> +			printf("Finished listing %u mounts for mount namespace %llu\n\n",
> +			       info.nr_mounts, info.mnt_ns_id);
> +			fd_mntns_next = ioctl(fd_mntns, NS_MNT_GET_NEXT, &info);
> +			if (fd_mntns_next < 0) {
> +				if (errno == ENOENT) {
> +					printf("Finished listing all mount namespaces\n");
> +					exit(0);
> +				}
> +				die_errno("ioctl(NS_MNT_GET_NEXT) failed");
> +			}
> +			close(fd_mntns);
> +			fd_mntns = fd_mntns_next;
> +			last_mnt_id = 0;
> +			printf("Listing %u mounts for mount namespace %llu\n",
> +			       info.nr_mounts, info.mnt_ns_id);
> +			goto next;
> +		}
> +
> +		for (size_t cur = 0; cur < nr_mounts; cur++) {
> +			struct statmount *stmnt;
> +
> +			last_mnt_id = list[cur];
> +
> +			stmnt = sys_statmount(last_mnt_id, info.mnt_ns_id,
> +					      STATMOUNT_SB_BASIC |
> +					      STATMOUNT_MNT_BASIC |
> +					      STATMOUNT_MNT_ROOT |
> +					      STATMOUNT_MNT_POINT |
> +					      STATMOUNT_MNT_NS_ID |
> +					      STATMOUNT_MNT_OPTS |
> +					      STATMOUNT_FS_TYPE, 0);
> +			if (!stmnt) {
> +				printf("Failed to statmount(%llu) in mount namespace(%llu)\n",
> +				       last_mnt_id, info.mnt_ns_id);
> +				continue;
> +			}
> +
> +			printf("mnt_id:\t\t%llu\nmnt_parent_id:\t%llu\nfs_type:\t%s\nmnt_root:\t%s\nmnt_point:\t%s\nmnt_opts:\t%s\n\n",
> +			       stmnt->mnt_id,
> +			       stmnt->mnt_parent_id,
> +			       stmnt->str + stmnt->fs_type,
> +			       stmnt->str + stmnt->mnt_root,
> +			       stmnt->str + stmnt->mnt_point,
> +			       stmnt->str + stmnt->mnt_opts);
> +			free(stmnt);
> +		}
> +	}
> +
> +	exit(0);
> +}
> 

Note that this replicates a lot of the functionality of the "mountinfo"
program. You could also extend that program with a different output
format instead of adding a new one.
Christian Brauner Dec. 11, 2024, 4:34 p.m. UTC | #2
> Note that this replicates a lot of the functionality of the "mountinfo"
> program. You could also extend that program with a different output
> format instead of adding a new one.

Fair. I'll see what I can do.
diff mbox series

Patch

diff --git a/samples/vfs/.gitignore b/samples/vfs/.gitignore
index 79212d91285bca72b0ff85f28aaccd2e803ac092..8694dd17b318768b975ece5c7cd450c2cca67318 100644
--- a/samples/vfs/.gitignore
+++ b/samples/vfs/.gitignore
@@ -1,3 +1,4 @@ 
 # SPDX-License-Identifier: GPL-2.0-only
 /test-fsmount
+/test-list-all-mounts
 /test-statx
diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
index 6377a678134acf0d682151d751d2f5042dbf5e0a..301be72a52a0e376c7ebe235cc2058992919cc78 100644
--- a/samples/vfs/Makefile
+++ b/samples/vfs/Makefile
@@ -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 test-list-all-mounts
 
 userccflags += -I usr/include
diff --git a/samples/vfs/test-list-all-mounts.c b/samples/vfs/test-list-all-mounts.c
new file mode 100644
index 0000000000000000000000000000000000000000..f372d5aea4717fd1ab3d4b3f9af79316cd5dd3d3
--- /dev/null
+++ b/samples/vfs/test-list-all-mounts.c
@@ -0,0 +1,235 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <limits.h>
+#include <linux/types.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+
+#include "../../tools/testing/selftests/pidfd/pidfd.h"
+
+#define die_errno(format, ...)                                             \
+	do {                                                               \
+		fprintf(stderr, "%m | %s: %d: %s: " format "\n", __FILE__, \
+			__LINE__, __func__, ##__VA_ARGS__);                \
+		exit(EXIT_FAILURE);                                        \
+	} while (0)
+
+/* Get the id for a mount namespace */
+#define NS_GET_MNTNS_ID _IO(0xb7, 0x5)
+/* Get next mount namespace. */
+
+struct mnt_ns_info {
+	__u32 size;
+	__u32 nr_mounts;
+	__u64 mnt_ns_id;
+};
+
+#define MNT_NS_INFO_SIZE_VER0 16 /* size of first published struct */
+
+/* Get information about namespace. */
+#define NS_MNT_GET_INFO _IOR(0xb7, 10, struct mnt_ns_info)
+/* Get next namespace. */
+#define NS_MNT_GET_NEXT _IOR(0xb7, 11, struct mnt_ns_info)
+/* Get previous namespace. */
+#define NS_MNT_GET_PREV _IOR(0xb7, 12, struct mnt_ns_info)
+
+#define PIDFD_GET_MNT_NAMESPACE _IO(0xFF, 3)
+
+#ifndef __NR_listmount
+#define __NR_listmount 458
+#endif
+
+#ifndef __NR_statmount
+#define __NR_statmount 457
+#endif
+
+/* @mask bits for statmount(2) */
+#define STATMOUNT_SB_BASIC		0x00000001U /* Want/got sb_... */
+#define STATMOUNT_MNT_BASIC		0x00000002U /* Want/got mnt_... */
+#define STATMOUNT_PROPAGATE_FROM	0x00000004U /* Want/got propagate_from */
+#define STATMOUNT_MNT_ROOT		0x00000008U /* Want/got mnt_root  */
+#define STATMOUNT_MNT_POINT		0x00000010U /* Want/got mnt_point */
+#define STATMOUNT_FS_TYPE		0x00000020U /* Want/got fs_type */
+#define STATMOUNT_MNT_NS_ID		0x00000040U /* Want/got mnt_ns_id */
+#define STATMOUNT_MNT_OPTS		0x00000080U /* Want/got mnt_opts */
+
+#define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */
+
+struct statmount {
+	__u32 size;
+	__u32 mnt_opts;
+	__u64 mask;
+	__u32 sb_dev_major;
+	__u32 sb_dev_minor;
+	__u64 sb_magic;
+	__u32 sb_flags;
+	__u32 fs_type;
+	__u64 mnt_id;
+	__u64 mnt_parent_id;
+	__u32 mnt_id_old;
+	__u32 mnt_parent_id_old;
+	__u64 mnt_attr;
+	__u64 mnt_propagation;
+	__u64 mnt_peer_group;
+	__u64 mnt_master;
+	__u64 propagate_from;
+	__u32 mnt_root;
+	__u32 mnt_point;
+	__u64 mnt_ns_id;
+	__u64 __spare2[49];
+	char str[];
+};
+
+struct mnt_id_req {
+	__u32 size;
+	__u32 spare;
+	__u64 mnt_id;
+	__u64 param;
+	__u64 mnt_ns_id;
+};
+
+#define MNT_ID_REQ_SIZE_VER1 32 /* sizeof second published struct */
+
+#define LSMT_ROOT 0xffffffffffffffff /* root mount */
+
+static int __statmount(__u64 mnt_id, __u64 mnt_ns_id, __u64 mask,
+		       struct statmount *stmnt, size_t bufsize,
+		       unsigned int flags)
+{
+	struct mnt_id_req req = {
+		.size		= MNT_ID_REQ_SIZE_VER1,
+		.mnt_id		= mnt_id,
+		.param		= mask,
+		.mnt_ns_id	= mnt_ns_id,
+	};
+
+	return syscall(__NR_statmount, &req, stmnt, bufsize, flags);
+}
+
+static struct statmount *sys_statmount(__u64 mnt_id, __u64 mnt_ns_id,
+				       __u64 mask, unsigned int flags)
+{
+	size_t bufsize = 1 << 15;
+	struct statmount *stmnt = NULL, *tmp = NULL;
+	int ret;
+
+	for (;;) {
+		tmp = realloc(stmnt, bufsize);
+		if (!tmp)
+			goto out;
+
+		stmnt = tmp;
+		ret = __statmount(mnt_id, mnt_ns_id, mask, stmnt, bufsize, flags);
+		if (!ret)
+			return stmnt;
+
+		if (errno != EOVERFLOW)
+			goto out;
+
+		bufsize <<= 1;
+		if (bufsize >= UINT_MAX / 2)
+			goto out;
+	}
+
+out:
+	free(stmnt);
+	return NULL;
+}
+
+static ssize_t sys_listmount(__u64 mnt_id, __u64 last_mnt_id, __u64 mnt_ns_id,
+			     __u64 list[], size_t num, unsigned int flags)
+{
+	struct mnt_id_req req = {
+		.size		= MNT_ID_REQ_SIZE_VER1,
+		.mnt_id		= mnt_id,
+		.param		= last_mnt_id,
+		.mnt_ns_id	= mnt_ns_id,
+	};
+
+	return syscall(__NR_listmount, &req, list, num, flags);
+}
+
+int main(int argc, char *argv[])
+{
+#define LISTMNT_BUFFER 10
+	__u64 list[LISTMNT_BUFFER], last_mnt_id = 0;
+	int ret, pidfd, fd_mntns;
+	struct mnt_ns_info info = {};
+
+	pidfd = sys_pidfd_open(getpid(), 0);
+	if (pidfd < 0)
+		die_errno("pidfd_open failed");
+
+	fd_mntns = ioctl(pidfd, PIDFD_GET_MNT_NAMESPACE, 0);
+	if (fd_mntns < 0)
+		die_errno("ioctl(PIDFD_GET_MNT_NAMESPACE) failed");
+
+	ret = ioctl(fd_mntns, NS_MNT_GET_INFO, &info);
+	if (ret < 0)
+		die_errno("ioctl(NS_GET_MNTNS_ID) failed");
+
+	printf("Listing %u mounts for mount namespace %llu\n",
+	       info.nr_mounts, info.mnt_ns_id);
+	for (;;) {
+		ssize_t nr_mounts;
+next:
+		nr_mounts = sys_listmount(LSMT_ROOT, last_mnt_id,
+					  info.mnt_ns_id, list, LISTMNT_BUFFER,
+					  0);
+		if (nr_mounts <= 0) {
+			int fd_mntns_next;
+
+			printf("Finished listing %u mounts for mount namespace %llu\n\n",
+			       info.nr_mounts, info.mnt_ns_id);
+			fd_mntns_next = ioctl(fd_mntns, NS_MNT_GET_NEXT, &info);
+			if (fd_mntns_next < 0) {
+				if (errno == ENOENT) {
+					printf("Finished listing all mount namespaces\n");
+					exit(0);
+				}
+				die_errno("ioctl(NS_MNT_GET_NEXT) failed");
+			}
+			close(fd_mntns);
+			fd_mntns = fd_mntns_next;
+			last_mnt_id = 0;
+			printf("Listing %u mounts for mount namespace %llu\n",
+			       info.nr_mounts, info.mnt_ns_id);
+			goto next;
+		}
+
+		for (size_t cur = 0; cur < nr_mounts; cur++) {
+			struct statmount *stmnt;
+
+			last_mnt_id = list[cur];
+
+			stmnt = sys_statmount(last_mnt_id, info.mnt_ns_id,
+					      STATMOUNT_SB_BASIC |
+					      STATMOUNT_MNT_BASIC |
+					      STATMOUNT_MNT_ROOT |
+					      STATMOUNT_MNT_POINT |
+					      STATMOUNT_MNT_NS_ID |
+					      STATMOUNT_MNT_OPTS |
+					      STATMOUNT_FS_TYPE, 0);
+			if (!stmnt) {
+				printf("Failed to statmount(%llu) in mount namespace(%llu)\n",
+				       last_mnt_id, info.mnt_ns_id);
+				continue;
+			}
+
+			printf("mnt_id:\t\t%llu\nmnt_parent_id:\t%llu\nfs_type:\t%s\nmnt_root:\t%s\nmnt_point:\t%s\nmnt_opts:\t%s\n\n",
+			       stmnt->mnt_id,
+			       stmnt->mnt_parent_id,
+			       stmnt->str + stmnt->fs_type,
+			       stmnt->str + stmnt->mnt_root,
+			       stmnt->str + stmnt->mnt_point,
+			       stmnt->str + stmnt->mnt_opts);
+			free(stmnt);
+		}
+	}
+
+	exit(0);
+}