Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE
@ 2026-01-22 10:48 Christian Brauner
  2026-01-22 10:48 ` [PATCH 1/7] mount: start iterating from start of rbtree Christian Brauner
                   ` (6 more replies)
  0 siblings, 7 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
namespace with the newly created filesystem attached to a copy of the
real rootfs. This returns a namespace file descriptor instead of an
O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for
open_tree().

This allows creating a new filesystem and immediately placing it in a
new mount namespace in a single operation, which is useful for container
runtimes and other namespace-based isolation mechanisms.

This accompanies OPEN_TREE_NAMESPACE and avoids a needless detour via
OPEN_TREE_NAMESPACE to get the same effect. Will be especially useful
when you mount an actual filesystem to be used as the container rootfs.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
Christian Brauner (7):
      mount: start iterating from start of rbtree
      mount: simplify __do_loopback()
      mount: add FSMOUNT_NAMESPACE
      tools: update mount.h header
      selftests/statmount: add statmount_alloc() helper
      selftests: add FSMOUNT_NAMESPACE tests
      selftests/open_tree_ns: fix compilation

 fs/namespace.c                                     |   84 +-
 include/uapi/linux/mount.h                         |    1 +
 tools/include/uapi/linux/mount.h                   |   14 +-
 .../selftests/filesystems/fsmount_ns/.gitignore    |    1 +
 .../selftests/filesystems/fsmount_ns/Makefile      |   10 +
 .../filesystems/fsmount_ns/fsmount_ns_test.c       | 1138 ++++++++++++++++++++
 .../selftests/filesystems/open_tree_ns/Makefile    |    2 +-
 .../filesystems/open_tree_ns/open_tree_ns_test.c   |   33 +-
 .../selftests/filesystems/statmount/statmount.h    |   27 +
 9 files changed, 1242 insertions(+), 68 deletions(-)
---
base-commit: 1bce1a664ac25d37a327c433a01bc347f0a81bd6
change-id: 20260121-work-fsmount-namespace-4242e3df359e


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/7] mount: start iterating from start of rbtree
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  2026-01-22 10:48 ` [PATCH 2/7] mount: simplify __do_loopback() Christian Brauner
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

If the root of the namespace has an id that's greater than the child
we'd not find it. Handle that case.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 fs/namespace.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 59557019e422..695ea0c37a7b 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -5645,14 +5645,14 @@ static int grab_requested_root(struct mnt_namespace *ns, struct path *root)
 	if (mnt_ns_empty(ns))
 		return -ENOENT;
 
-	first = child = ns->root;
-	for (;;) {
-		child = listmnt_next(child, false);
-		if (!child)
-			return -ENOENT;
-		if (child->mnt_parent == first)
+	first = ns->root;
+	for (child = node_to_mount(ns->mnt_first_node); child;
+	     child = listmnt_next(child, false)) {
+		if (child != first && child->mnt_parent == first)
 			break;
 	}
+	if (!child)
+		return -ENOENT;
 
 	root->mnt = mntget(&child->mnt);
 	root->dentry = dget(root->mnt->mnt_root);

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/7] mount: simplify __do_loopback()
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
  2026-01-22 10:48 ` [PATCH 1/7] mount: start iterating from start of rbtree Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  2026-01-22 10:48 ` [PATCH 3/7] mount: add FSMOUNT_NAMESPACE Christian Brauner
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Remove the OPEN_TREE_NAMESPACE flag checking from __do_loopback() and
instead have callers pass CL_COPY_MNT_NS_FILE directly in copy_flags.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 fs/namespace.c | 31 +++++++++----------------------
 1 file changed, 9 insertions(+), 22 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 695ea0c37a7b..46d2eb1c9c3d 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2950,10 +2950,9 @@ static inline bool may_copy_tree(const struct path *path)
 }
 
 static struct mount *__do_loopback(const struct path *old_path,
-				   unsigned int flags, unsigned int copy_flags)
+				   bool recurse, unsigned int copy_flags)
 {
 	struct mount *old = real_mount(old_path->mnt);
-	bool recurse = flags & AT_RECURSIVE;
 
 	if (IS_MNT_UNBINDABLE(old))
 		return ERR_PTR(-EINVAL);
@@ -2964,18 +2963,6 @@ static struct mount *__do_loopback(const struct path *old_path,
 	if (!recurse && __has_locked_children(old, old_path->dentry))
 		return ERR_PTR(-EINVAL);
 
-	/*
-	 * When creating a new mount namespace we don't want to copy over
-	 * mounts of mount namespaces to avoid the risk of cycles and also to
-	 * minimize the default complex interdependencies between mount
-	 * namespaces.
-	 *
-	 * We could ofc just check whether all mount namespace files aren't
-	 * creating cycles but really let's keep this simple.
-	 */
-	if (!(flags & OPEN_TREE_NAMESPACE))
-		copy_flags |= CL_COPY_MNT_NS_FILE;
-
 	if (recurse)
 		return copy_tree(old, old_path->dentry, copy_flags);
 
@@ -2990,7 +2977,6 @@ static int do_loopback(const struct path *path, const char *old_name,
 {
 	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL;
-	unsigned int flags = recurse ? AT_RECURSIVE : 0;
 	int err;
 
 	if (!old_name || !*old_name)
@@ -3009,7 +2995,7 @@ static int do_loopback(const struct path *path, const char *old_name,
 	if (!check_mnt(mp.parent))
 		return -EINVAL;
 
-	mnt = __do_loopback(&old_path, flags, 0);
+	mnt = __do_loopback(&old_path, recurse, CL_COPY_MNT_NS_FILE);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
@@ -3047,7 +3033,7 @@ static struct mnt_namespace *get_detached_copy(const struct path *path, unsigned
 			ns->seq_origin = src_mnt_ns->ns.ns_id;
 	}
 
-	mnt = __do_loopback(path, flags, 0);
+	mnt = __do_loopback(path, (flags & AT_RECURSIVE), CL_COPY_MNT_NS_FILE);
 	if (IS_ERR(mnt)) {
 		emptied_ns = ns;
 		return ERR_CAST(mnt);
@@ -3082,7 +3068,8 @@ static struct file *open_detached_copy(struct path *path, unsigned int flags)
 DEFINE_FREE(put_empty_mnt_ns, struct mnt_namespace *,
 	    if (!IS_ERR_OR_NULL(_T)) free_mnt_ns(_T))
 
-static struct mnt_namespace *create_new_namespace(struct path *path, unsigned int flags)
+static struct mnt_namespace *create_new_namespace(struct path *path,
+						  bool recurse)
 {
 	struct mnt_namespace *new_ns __free(put_empty_mnt_ns) = NULL;
 	struct path to_path __free(path_put) = {};
@@ -3140,7 +3127,7 @@ static struct mnt_namespace *create_new_namespace(struct path *path, unsigned in
 	 * restrictions of creating detached bind-mounts. It has a lot
 	 * saner and simpler semantics.
 	 */
-	mnt = __do_loopback(path, flags, copy_flags);
+	mnt = __do_loopback(path, recurse, copy_flags);
 	if (IS_ERR(mnt))
 		return ERR_CAST(mnt);
 
@@ -3167,11 +3154,11 @@ static struct mnt_namespace *create_new_namespace(struct path *path, unsigned in
 	return no_free_ptr(new_ns);
 }
 
-static struct file *open_new_namespace(struct path *path, unsigned int flags)
+static struct file *open_new_namespace(struct path *path, bool recurse)
 {
 	struct mnt_namespace *new_ns;
 
-	new_ns = create_new_namespace(path, flags);
+	new_ns = create_new_namespace(path, recurse);
 	if (IS_ERR(new_ns))
 		return ERR_CAST(new_ns);
 	return open_namespace_file(to_ns_common(new_ns));
@@ -3221,7 +3208,7 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned
 		return ERR_PTR(ret);
 
 	if (flags & OPEN_TREE_NAMESPACE)
-		return open_new_namespace(&path, flags);
+		return open_new_namespace(&path, (flags & AT_RECURSIVE));
 
 	if (flags & OPEN_TREE_CLONE)
 		return open_detached_copy(&path, flags);

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/7] mount: add FSMOUNT_NAMESPACE
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
  2026-01-22 10:48 ` [PATCH 1/7] mount: start iterating from start of rbtree Christian Brauner
  2026-01-22 10:48 ` [PATCH 2/7] mount: simplify __do_loopback() Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  2026-02-11 11:47   ` Mark Brown
  2026-01-22 10:48 ` [PATCH 4/7] tools: update mount.h header Christian Brauner
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
namespace with the newly created filesystem attached to a copy of the
real rootfs. This returns a namespace file descriptor instead of an
O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for open_tree().

This allows creating a new filesystem and immediately placing it in a
new mount namespace in a single operation, which is useful for container
runtimes and other namespace-based isolation mechanisms.

The rootfs mount is created before copying the real rootfs for the new
namespace meaning that the mount namespace id for the mount of the root
of the namespace is bigger than the child mounted on top of it. We've
never explicitly given the guarantee for such ordering and I doubt
anyone relies on it. Accepting that lets us avoid copying the mount
again and also avoids having to massage may_copy_tree() to grant an
exception for fsmount->mnt->mnt_ns being NULL.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 fs/namespace.c             | 53 +++++++++++++++++++++++++++++++++-------------
 include/uapi/linux/mount.h |  1 +
 2 files changed, 39 insertions(+), 15 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 46d2eb1c9c3d..30f2991b4a7f 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3068,8 +3068,13 @@ static struct file *open_detached_copy(struct path *path, unsigned int flags)
 DEFINE_FREE(put_empty_mnt_ns, struct mnt_namespace *,
 	    if (!IS_ERR_OR_NULL(_T)) free_mnt_ns(_T))
 
+enum open_newns_flags_t {
+	OPEN_NEWNS_RECURSIVE	= BIT(0),
+	OPEN_NEWNS_CLONE	= BIT(1),
+};
+
 static struct mnt_namespace *create_new_namespace(struct path *path,
-						  bool recurse)
+						  enum open_newns_flags_t flags)
 {
 	struct mnt_namespace *new_ns __free(put_empty_mnt_ns) = NULL;
 	struct path to_path __free(path_put) = {};
@@ -3080,6 +3085,9 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
 	unsigned int copy_flags = 0;
 	bool locked = false;
 
+	if ((flags & (OPEN_NEWNS_RECURSIVE | OPEN_NEWNS_CLONE)) == OPEN_NEWNS_RECURSIVE)
+		return ERR_PTR(-EINVAL);
+
 	if (user_ns != ns->user_ns)
 		copy_flags |= CL_SLAVE;
 
@@ -3122,14 +3130,18 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
 	if (unlikely(IS_ERR(mp.parent)))
 		return ERR_CAST(mp.parent);
 
-	/*
-	 * We don't emulate unshare()ing a mount namespace. We stick to the
-	 * restrictions of creating detached bind-mounts. It has a lot
-	 * saner and simpler semantics.
-	 */
-	mnt = __do_loopback(path, recurse, copy_flags);
-	if (IS_ERR(mnt))
-		return ERR_CAST(mnt);
+	if (flags & OPEN_NEWNS_CLONE) {
+		/*
+		 * We don't emulate unshare()ing a mount namespace. We stick to
+		 * the restrictions of creating detached bind-mounts. It has a
+		 * lot saner and simpler semantics.
+		 */
+		mnt = __do_loopback(path, flags & OPEN_NEWNS_RECURSIVE, copy_flags);
+		if (IS_ERR(mnt))
+			return ERR_CAST(mnt);
+	} else {
+		mnt = real_mount(mntget(path->mnt));
+	}
 
 	scoped_guard(mount_writer) {
 		if (locked)
@@ -3154,11 +3166,12 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
 	return no_free_ptr(new_ns);
 }
 
-static struct file *open_new_namespace(struct path *path, bool recurse)
+static struct file *open_new_namespace(struct path *path,
+				       enum open_newns_flags_t flags)
 {
 	struct mnt_namespace *new_ns;
 
-	new_ns = create_new_namespace(path, recurse);
+	new_ns = create_new_namespace(path, flags);
 	if (IS_ERR(new_ns))
 		return ERR_CAST(new_ns);
 	return open_namespace_file(to_ns_common(new_ns));
@@ -3208,7 +3221,9 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned
 		return ERR_PTR(ret);
 
 	if (flags & OPEN_TREE_NAMESPACE)
-		return open_new_namespace(&path, (flags & AT_RECURSIVE));
+		return open_new_namespace(&path,
+				((flags & AT_RECURSIVE) ? OPEN_NEWNS_RECURSIVE : 0) |
+				OPEN_NEWNS_CLONE);
 
 	if (flags & OPEN_TREE_CLONE)
 		return open_detached_copy(&path, flags);
@@ -4395,11 +4410,15 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 	unsigned int mnt_flags = 0;
 	long ret;
 
-	if (!may_mount())
+	if ((flags & ~(FSMOUNT_CLOEXEC | FSMOUNT_NAMESPACE)) != 0)
+		return -EINVAL;
+
+	if ((flags & FSMOUNT_NAMESPACE) &&
+	    !ns_capable(current_user_ns(), CAP_SYS_ADMIN))
 		return -EPERM;
 
-	if ((flags & ~(FSMOUNT_CLOEXEC)) != 0)
-		return -EINVAL;
+	if (!(flags & FSMOUNT_NAMESPACE) && !may_mount())
+		return -EPERM;
 
 	if (attr_flags & ~FSMOUNT_VALID_FLAGS)
 		return -EINVAL;
@@ -4466,6 +4485,10 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 	 */
 	vfs_clean_context(fc);
 
+	if (flags & FSMOUNT_NAMESPACE)
+		return FD_ADD((flags & FSMOUNT_CLOEXEC) ? O_CLOEXEC : 0,
+			      open_new_namespace(&new_path, 0));
+
 	ns = alloc_mnt_ns(current->nsproxy->mnt_ns->user_ns, true);
 	if (IS_ERR(ns))
 		return PTR_ERR(ns);
diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h
index d9d86598d100..2204708dbf7a 100644
--- a/include/uapi/linux/mount.h
+++ b/include/uapi/linux/mount.h
@@ -110,6 +110,7 @@ enum fsconfig_command {
  * fsmount() flags.
  */
 #define FSMOUNT_CLOEXEC		0x00000001
+#define FSMOUNT_NAMESPACE	0x00000002	/* Create the mount in a new mount namespace */
 
 /*
  * Mount attributes.

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 4/7] tools: update mount.h header
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
                   ` (2 preceding siblings ...)
  2026-01-22 10:48 ` [PATCH 3/7] mount: add FSMOUNT_NAMESPACE Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  2026-01-22 10:48 ` [PATCH 5/7] selftests/statmount: add statmount_alloc() helper Christian Brauner
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Update the mount.h header so we can rely on it in the selftests.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 tools/include/uapi/linux/mount.h | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/tools/include/uapi/linux/mount.h b/tools/include/uapi/linux/mount.h
index 7fa67c2031a5..2204708dbf7a 100644
--- a/tools/include/uapi/linux/mount.h
+++ b/tools/include/uapi/linux/mount.h
@@ -61,7 +61,8 @@
 /*
  * open_tree() flags.
  */
-#define OPEN_TREE_CLONE		1		/* Clone the target tree and attach the clone */
+#define OPEN_TREE_CLONE		(1 << 0)	/* Clone the target tree and attach the clone */
+#define OPEN_TREE_NAMESPACE	(1 << 1)	/* Clone the target tree into a new mount namespace */
 #define OPEN_TREE_CLOEXEC	O_CLOEXEC	/* Close the file on execve() */
 
 /*
@@ -109,6 +110,7 @@ enum fsconfig_command {
  * fsmount() flags.
  */
 #define FSMOUNT_CLOEXEC		0x00000001
+#define FSMOUNT_NAMESPACE	0x00000002	/* Create the mount in a new mount namespace */
 
 /*
  * Mount attributes.
@@ -197,7 +199,10 @@ struct statmount {
  */
 struct mnt_id_req {
 	__u32 size;
-	__u32 spare;
+	union {
+		__u32 mnt_ns_fd;
+		__u32 mnt_fd;
+	};
 	__u64 mnt_id;
 	__u64 param;
 	__u64 mnt_ns_id;
@@ -232,4 +237,9 @@ struct mnt_id_req {
 #define LSMT_ROOT		0xffffffffffffffff	/* root mount */
 #define LISTMOUNT_REVERSE	(1 << 0) /* List later mounts first */
 
+/*
+ * @flag bits for statmount(2)
+ */
+#define STATMOUNT_BY_FD		0x00000001U	/* want mountinfo for given fd */
+
 #endif /* _UAPI_LINUX_MOUNT_H */

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 5/7] selftests/statmount: add statmount_alloc() helper
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
                   ` (3 preceding siblings ...)
  2026-01-22 10:48 ` [PATCH 4/7] tools: update mount.h header Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  2026-01-22 10:48 ` [PATCH 6/7] selftests: add FSMOUNT_NAMESPACE tests Christian Brauner
  2026-01-22 10:48 ` [PATCH 7/7] selftests/open_tree_ns: fix compilation Christian Brauner
  6 siblings, 0 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Add a helper to allocate a statmount buffer and call statmount(). This
helper will be shared by multiple test suites that need to query mount
information via statmount().

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 .../selftests/filesystems/statmount/statmount.h    | 27 ++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/tools/testing/selftests/filesystems/statmount/statmount.h b/tools/testing/selftests/filesystems/statmount/statmount.h
index e1cba4bfd8d9..4ef23e54212c 100644
--- a/tools/testing/selftests/filesystems/statmount/statmount.h
+++ b/tools/testing/selftests/filesystems/statmount/statmount.h
@@ -3,10 +3,14 @@
 #ifndef __STATMOUNT_H
 #define __STATMOUNT_H
 
+#include <errno.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <linux/mount.h>
 #include <asm/unistd.h>
 
+#define STATMOUNT_BUFSIZE (1 << 15)
+
 #ifndef __NR_statmount
 	#if defined __alpha__
 		#define __NR_statmount 567
@@ -84,4 +88,27 @@ static inline ssize_t listmount(uint64_t mnt_id, uint64_t mnt_ns_id,
 	return syscall(__NR_listmount, &req, list, num, flags);
 }
 
+static inline struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask)
+{
+	struct statmount *buf;
+	size_t bufsize = STATMOUNT_BUFSIZE;
+	int ret;
+
+	for (;;) {
+		buf = malloc(bufsize);
+		if (!buf)
+			return NULL;
+
+		ret = statmount(mnt_id, mnt_ns_id, 0, mask, buf, bufsize, 0);
+		if (ret == 0)
+			return buf;
+
+		free(buf);
+		if (errno != EOVERFLOW)
+			return NULL;
+
+		bufsize <<= 1;
+	}
+}
+
 #endif /* __STATMOUNT_H */

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 6/7] selftests: add FSMOUNT_NAMESPACE tests
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
                   ` (4 preceding siblings ...)
  2026-01-22 10:48 ` [PATCH 5/7] selftests/statmount: add statmount_alloc() helper Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  2026-01-22 10:48 ` [PATCH 7/7] selftests/open_tree_ns: fix compilation Christian Brauner
  6 siblings, 0 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Add selftests for FSMOUNT_NAMESPACE which creates a new mount namespace
with the newly created filesystem mounted onto a copy of the real
rootfs.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 .../selftests/filesystems/fsmount_ns/.gitignore    |    1 +
 .../selftests/filesystems/fsmount_ns/Makefile      |   10 +
 .../filesystems/fsmount_ns/fsmount_ns_test.c       | 1138 ++++++++++++++++++++
 3 files changed, 1149 insertions(+)

diff --git a/tools/testing/selftests/filesystems/fsmount_ns/.gitignore b/tools/testing/selftests/filesystems/fsmount_ns/.gitignore
new file mode 100644
index 000000000000..f1ecf6c6e37b
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fsmount_ns/.gitignore
@@ -0,0 +1 @@
+fsmount_ns_test
diff --git a/tools/testing/selftests/filesystems/fsmount_ns/Makefile b/tools/testing/selftests/filesystems/fsmount_ns/Makefile
new file mode 100644
index 000000000000..d9647efc0739
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fsmount_ns/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := fsmount_ns_test
+
+CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+LDLIBS := -lcap
+
+include ../../lib.mk
+
+$(OUTPUT)/fsmount_ns_test: fsmount_ns_test.c ../utils.c
+	$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)
diff --git a/tools/testing/selftests/filesystems/fsmount_ns/fsmount_ns_test.c b/tools/testing/selftests/filesystems/fsmount_ns/fsmount_ns_test.c
new file mode 100644
index 000000000000..705c04d7dfb6
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fsmount_ns/fsmount_ns_test.c
@@ -0,0 +1,1138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Christian Brauner <brauner@kernel.org>
+ *
+ * Test for FSMOUNT_NAMESPACE flag.
+ *
+ * Test that fsmount() with FSMOUNT_NAMESPACE creates a new mount
+ * namespace containing the specified mount.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/nsfs.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../wrappers.h"
+#include "../statmount/statmount.h"
+#include "../utils.h"
+#include "../../kselftest_harness.h"
+
+#ifndef FSMOUNT_NAMESPACE
+#define FSMOUNT_NAMESPACE	0x00000002
+#endif
+
+#ifndef FSMOUNT_CLOEXEC
+#define FSMOUNT_CLOEXEC		0x00000001
+#endif
+
+#ifndef FSCONFIG_CMD_CREATE
+#define FSCONFIG_CMD_CREATE	6
+#endif
+
+static int get_mnt_ns_id(int fd, uint64_t *mnt_ns_id)
+{
+	if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0)
+		return -errno;
+	return 0;
+}
+
+static int get_mnt_ns_id_from_path(const char *path, uint64_t *mnt_ns_id)
+{
+	int fd, ret;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+
+	ret = get_mnt_ns_id(fd, mnt_ns_id);
+	close(fd);
+	return ret;
+}
+
+static void log_mount(struct __test_metadata *_metadata, struct statmount *sm)
+{
+	const char *fs_type = "";
+	const char *mnt_root = "";
+	const char *mnt_point = "";
+
+	if (sm->mask & STATMOUNT_FS_TYPE)
+		fs_type = sm->str + sm->fs_type;
+	if (sm->mask & STATMOUNT_MNT_ROOT)
+		mnt_root = sm->str + sm->mnt_root;
+	if (sm->mask & STATMOUNT_MNT_POINT)
+		mnt_point = sm->str + sm->mnt_point;
+
+	TH_LOG("  mnt_id: %llu, parent_id: %llu, fs_type: %s, root: %s, point: %s",
+	       (unsigned long long)sm->mnt_id,
+	       (unsigned long long)sm->mnt_parent_id,
+	       fs_type, mnt_root, mnt_point);
+}
+
+static void dump_mounts(struct __test_metadata *_metadata, uint64_t mnt_ns_id)
+{
+	uint64_t list[256];
+	ssize_t nr_mounts;
+
+	nr_mounts = listmount(LSMT_ROOT, mnt_ns_id, 0, list, 256, 0);
+	if (nr_mounts < 0) {
+		TH_LOG("listmount failed: %s", strerror(errno));
+		return;
+	}
+
+	TH_LOG("Mount namespace %llu contains %zd mount(s):",
+	       (unsigned long long)mnt_ns_id, nr_mounts);
+
+	for (ssize_t i = 0; i < nr_mounts; i++) {
+		struct statmount *sm;
+
+		sm = statmount_alloc(list[i], mnt_ns_id,
+				     STATMOUNT_MNT_BASIC |
+				     STATMOUNT_FS_TYPE |
+				     STATMOUNT_MNT_ROOT |
+				     STATMOUNT_MNT_POINT);
+		if (!sm) {
+			TH_LOG("  [%zd] mnt_id %llu: statmount failed: %s",
+			       i, (unsigned long long)list[i], strerror(errno));
+			continue;
+		}
+
+		log_mount(_metadata, sm);
+		free(sm);
+	}
+}
+
+static int create_tmpfs_fd(void)
+{
+	int fs_fd, ret;
+
+	fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
+	if (fs_fd < 0)
+		return -errno;
+
+	ret = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
+	if (ret < 0) {
+		close(fs_fd);
+		return -errno;
+	}
+
+	return fs_fd;
+}
+
+FIXTURE(fsmount_ns)
+{
+	int fd;
+	int fs_fd;
+	uint64_t current_ns_id;
+};
+
+FIXTURE_VARIANT(fsmount_ns)
+{
+	const char *fstype;
+	unsigned int flags;
+	bool expect_success;
+	bool expect_different_ns;
+	int min_mounts;
+};
+
+FIXTURE_VARIANT_ADD(fsmount_ns, basic_tmpfs)
+{
+	.fstype = "tmpfs",
+	.flags = FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC,
+	.expect_success = true,
+	.expect_different_ns = true,
+	.min_mounts = 1,
+};
+
+FIXTURE_VARIANT_ADD(fsmount_ns, cloexec_only)
+{
+	.fstype = "tmpfs",
+	.flags = FSMOUNT_CLOEXEC,
+	.expect_success = true,
+	.expect_different_ns = false,
+	.min_mounts = 1,
+};
+
+FIXTURE_VARIANT_ADD(fsmount_ns, namespace_only)
+{
+	.fstype = "tmpfs",
+	.flags = FSMOUNT_NAMESPACE,
+	.expect_success = true,
+	.expect_different_ns = true,
+	.min_mounts = 1,
+};
+
+FIXTURE_SETUP(fsmount_ns)
+{
+	int ret;
+
+	self->fd = -1;
+	self->fs_fd = -1;
+
+	/* Check if fsopen syscall is supported */
+	ret = sys_fsopen("tmpfs", 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "fsopen() syscall not supported");
+	if (ret >= 0)
+		close(ret);
+
+	/* Check if statmount/listmount are supported */
+	ret = statmount(0, 0, 0, 0, NULL, 0, 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "statmount() syscall not supported");
+
+	/* Get current mount namespace ID for comparison */
+	ret = get_mnt_ns_id_from_path("/proc/self/ns/mnt", &self->current_ns_id);
+	if (ret < 0)
+		SKIP(return, "Failed to get current mount namespace ID");
+}
+
+FIXTURE_TEARDOWN(fsmount_ns)
+{
+	if (self->fd >= 0)
+		close(self->fd);
+	if (self->fs_fd >= 0)
+		close(self->fs_fd);
+}
+
+TEST_F(fsmount_ns, create_namespace)
+{
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, variant->flags, 0);
+
+	if (!variant->expect_success) {
+		ASSERT_LT(self->fd, 0);
+		return;
+	}
+
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	if (variant->expect_different_ns) {
+		/* Verify we can get the namespace ID from the fd */
+		ret = get_mnt_ns_id(self->fd, &new_ns_id);
+		ASSERT_EQ(ret, 0);
+
+		/* Verify it's a different namespace */
+		ASSERT_NE(new_ns_id, self->current_ns_id);
+
+		/* List mounts in the new namespace */
+		nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+		ASSERT_GE(nr_mounts, 0) {
+			TH_LOG("%m - listmount failed");
+		}
+
+		/* Verify minimum expected mounts */
+		ASSERT_GE(nr_mounts, variant->min_mounts);
+		TH_LOG("Namespace contains %zd mounts", nr_mounts);
+	}
+}
+
+TEST_F(fsmount_ns, setns_into_namespace)
+{
+	uint64_t new_ns_id;
+	pid_t pid;
+	int status;
+	int ret;
+
+	/* Only test with FSMOUNT_NAMESPACE flag */
+	if (!(variant->flags & FSMOUNT_NAMESPACE))
+		SKIP(return, "setns test only for FSMOUNT_NAMESPACE case");
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, variant->flags, 0);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	/* Get namespace ID and dump all mounts */
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	dump_mounts(_metadata, new_ns_id);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		/* Child: try to enter the namespace */
+		if (setns(self->fd, CLONE_NEWNS) < 0)
+			_exit(1);
+		_exit(0);
+	}
+
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_F(fsmount_ns, verify_mount_properties)
+{
+	struct statmount sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	/* Only test with basic FSMOUNT_NAMESPACE flags */
+	if (variant->flags != (FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC))
+		SKIP(return, "mount properties test only for basic case");
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	/* Get info about the root mount */
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ASSERT_EQ(ret, 0);
+
+	TH_LOG("Root mount id: %llu, parent: %llu",
+	       (unsigned long long)sm.mnt_id,
+	       (unsigned long long)sm.mnt_parent_id);
+}
+
+TEST_F(fsmount_ns, verify_tmpfs_type)
+{
+	struct statmount *sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	const char *fs_type;
+	int ret;
+
+	/* Only test with basic FSMOUNT_NAMESPACE flags */
+	if (variant->flags != (FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC))
+		SKIP(return, "fs type test only for basic case");
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	sm = statmount_alloc(list[0], new_ns_id, STATMOUNT_FS_TYPE);
+	ASSERT_NE(sm, NULL);
+
+	fs_type = sm->str + sm->fs_type;
+	ASSERT_STREQ(fs_type, "tmpfs");
+
+	free(sm);
+}
+
+FIXTURE(fsmount_ns_caps)
+{
+	bool has_caps;
+};
+
+FIXTURE_SETUP(fsmount_ns_caps)
+{
+	int ret;
+
+	/* Check if fsopen syscall is supported */
+	ret = sys_fsopen("tmpfs", 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "fsopen() syscall not supported");
+	if (ret >= 0)
+		close(ret);
+
+	self->has_caps = (geteuid() == 0);
+}
+
+FIXTURE_TEARDOWN(fsmount_ns_caps)
+{
+}
+
+TEST_F(fsmount_ns_caps, requires_cap_sys_admin)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		int fs_fd, fd;
+
+		/* Child: drop privileges using utils.h helper */
+		if (enter_userns() != 0)
+			_exit(2);
+
+		/* Drop all caps using utils.h helper */
+		if (caps_down() == 0)
+			_exit(3);
+
+		fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
+		if (fs_fd < 0)
+			_exit(4);
+
+		if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
+			close(fs_fd);
+			_exit(5);
+		}
+
+		fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+		close(fs_fd);
+
+		if (fd >= 0) {
+			close(fd);
+			/* Should have failed without caps */
+			_exit(1);
+		}
+
+		if (errno == EPERM)
+			_exit(0);
+
+		/* EINVAL means FSMOUNT_NAMESPACE not supported */
+		if (errno == EINVAL)
+			_exit(6);
+
+		/* Unexpected error */
+		_exit(7);
+	}
+
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+
+	switch (WEXITSTATUS(status)) {
+	case 0:
+		/* Expected: EPERM without caps */
+		break;
+	case 1:
+		ASSERT_FALSE(true) TH_LOG("FSMOUNT_NAMESPACE succeeded without caps");
+		break;
+	case 2:
+		SKIP(return, "enter_userns failed");
+		break;
+	case 3:
+		SKIP(return, "caps_down failed");
+		break;
+	case 4:
+		SKIP(return, "fsopen failed in userns");
+		break;
+	case 5:
+		SKIP(return, "fsconfig CMD_CREATE failed in userns");
+		break;
+	case 6:
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+		break;
+	default:
+		ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)",
+					  WEXITSTATUS(status));
+		break;
+	}
+}
+
+FIXTURE(fsmount_ns_userns)
+{
+	int fd;
+	int fs_fd;
+};
+
+FIXTURE_SETUP(fsmount_ns_userns)
+{
+	int ret;
+
+	self->fd = -1;
+	self->fs_fd = -1;
+
+	/* Check if fsopen syscall is supported */
+	ret = sys_fsopen("tmpfs", 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "fsopen() syscall not supported");
+	if (ret >= 0)
+		close(ret);
+
+	/* Check if statmount/listmount are supported */
+	ret = statmount(0, 0, 0, 0, NULL, 0, 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "statmount() syscall not supported");
+}
+
+FIXTURE_TEARDOWN(fsmount_ns_userns)
+{
+	if (self->fd >= 0)
+		close(self->fd);
+	if (self->fs_fd >= 0)
+		close(self->fs_fd);
+}
+
+TEST_F(fsmount_ns_userns, create_in_userns)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		uint64_t new_ns_id;
+		uint64_t list[256];
+		ssize_t nr_mounts;
+		int fs_fd, fd;
+
+		/* Create new user namespace (also creates mount namespace) */
+		if (setup_userns() != 0)
+			_exit(2);
+
+		/* Now we have CAP_SYS_ADMIN in the user namespace */
+		fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
+		if (fs_fd < 0)
+			_exit(3);
+
+		if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
+			close(fs_fd);
+			_exit(4);
+		}
+
+		fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+		close(fs_fd);
+
+		if (fd < 0) {
+			if (errno == EINVAL)
+				_exit(6); /* FSMOUNT_NAMESPACE not supported */
+			_exit(1);
+		}
+
+		/* Verify we can get the namespace ID */
+		if (get_mnt_ns_id(fd, &new_ns_id) != 0)
+			_exit(7);
+
+		/* Verify we can list mounts in the new namespace */
+		nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+		if (nr_mounts < 0)
+			_exit(8);
+
+		/* Should have at least 1 mount (the tmpfs) */
+		if (nr_mounts < 1)
+			_exit(9);
+
+		close(fd);
+		_exit(0);
+	}
+
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+
+	switch (WEXITSTATUS(status)) {
+	case 0:
+		/* Success */
+		break;
+	case 1:
+		ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed in userns");
+		break;
+	case 2:
+		SKIP(return, "setup_userns failed");
+		break;
+	case 3:
+		SKIP(return, "fsopen failed in userns");
+		break;
+	case 4:
+		SKIP(return, "fsconfig CMD_CREATE failed in userns");
+		break;
+	case 6:
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+		break;
+	case 7:
+		ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID");
+		break;
+	case 8:
+		ASSERT_FALSE(true) TH_LOG("listmount failed in new namespace");
+		break;
+	case 9:
+		ASSERT_FALSE(true) TH_LOG("New namespace has no mounts");
+		break;
+	default:
+		ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)",
+					  WEXITSTATUS(status));
+		break;
+	}
+}
+
+TEST_F(fsmount_ns_userns, setns_in_userns)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		uint64_t new_ns_id;
+		int fs_fd, fd;
+		pid_t inner_pid;
+		int inner_status;
+
+		/* Create new user namespace */
+		if (setup_userns() != 0)
+			_exit(2);
+
+		fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
+		if (fs_fd < 0)
+			_exit(3);
+
+		if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
+			close(fs_fd);
+			_exit(4);
+		}
+
+		fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+		close(fs_fd);
+
+		if (fd < 0) {
+			if (errno == EINVAL)
+				_exit(6);
+			_exit(1);
+		}
+
+		if (get_mnt_ns_id(fd, &new_ns_id) != 0)
+			_exit(7);
+
+		/* Fork again to test setns into the new namespace */
+		inner_pid = fork();
+		if (inner_pid < 0)
+			_exit(10);
+
+		if (inner_pid == 0) {
+			/* Inner child: enter the new namespace */
+			if (setns(fd, CLONE_NEWNS) < 0)
+				_exit(1);
+			_exit(0);
+		}
+
+		if (waitpid(inner_pid, &inner_status, 0) != inner_pid)
+			_exit(11);
+
+		if (!WIFEXITED(inner_status) || WEXITSTATUS(inner_status) != 0)
+			_exit(12);
+
+		close(fd);
+		_exit(0);
+	}
+
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+
+	switch (WEXITSTATUS(status)) {
+	case 0:
+		/* Success */
+		break;
+	case 1:
+		ASSERT_FALSE(true) TH_LOG("fsmount or setns failed in userns");
+		break;
+	case 2:
+		SKIP(return, "setup_userns failed");
+		break;
+	case 3:
+		SKIP(return, "fsopen failed in userns");
+		break;
+	case 4:
+		SKIP(return, "fsconfig CMD_CREATE failed in userns");
+		break;
+	case 6:
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+		break;
+	case 7:
+		ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID");
+		break;
+	case 10:
+		ASSERT_FALSE(true) TH_LOG("Inner fork failed");
+		break;
+	case 11:
+		ASSERT_FALSE(true) TH_LOG("Inner waitpid failed");
+		break;
+	case 12:
+		ASSERT_FALSE(true) TH_LOG("setns into new namespace failed");
+		break;
+	default:
+		ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)",
+					  WEXITSTATUS(status));
+		break;
+	}
+}
+
+TEST_F(fsmount_ns_userns, umount_fails_einval)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		uint64_t new_ns_id;
+		uint64_t list[256];
+		ssize_t nr_mounts;
+		int fs_fd, fd;
+		ssize_t i;
+
+		/* Create new user namespace */
+		if (setup_userns() != 0)
+			_exit(2);
+
+		fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
+		if (fs_fd < 0)
+			_exit(3);
+
+		if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
+			close(fs_fd);
+			_exit(4);
+		}
+
+		fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+		close(fs_fd);
+
+		if (fd < 0) {
+			if (errno == EINVAL)
+				_exit(6);
+			_exit(1);
+		}
+
+		if (get_mnt_ns_id(fd, &new_ns_id) != 0)
+			_exit(7);
+
+		/* Get all mounts in the new namespace */
+		nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, LISTMOUNT_REVERSE);
+		if (nr_mounts < 0)
+			_exit(13);
+
+		if (nr_mounts < 1)
+			_exit(14);
+
+		/* Enter the new namespace */
+		if (setns(fd, CLONE_NEWNS) < 0)
+			_exit(8);
+
+		for (i = 0; i < nr_mounts; i++) {
+			struct statmount *sm;
+			const char *mnt_point;
+
+			sm = statmount_alloc(list[i], new_ns_id,
+					     STATMOUNT_MNT_POINT);
+			if (!sm)
+				_exit(15);
+
+			mnt_point = sm->str + sm->mnt_point;
+
+			if (umount2(mnt_point, MNT_DETACH) == 0) {
+				free(sm);
+				_exit(9);
+			}
+
+			if (errno != EINVAL) {
+				/* Wrong error */
+				free(sm);
+				_exit(10);
+			}
+
+			free(sm);
+		}
+
+		close(fd);
+		_exit(0);
+	}
+
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+
+	switch (WEXITSTATUS(status)) {
+	case 0:
+		break;
+	case 1:
+		ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed");
+		break;
+	case 2:
+		SKIP(return, "setup_userns failed");
+		break;
+	case 3:
+		SKIP(return, "fsopen failed in userns");
+		break;
+	case 4:
+		SKIP(return, "fsconfig CMD_CREATE failed in userns");
+		break;
+	case 6:
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+		break;
+	case 7:
+		ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID");
+		break;
+	case 8:
+		ASSERT_FALSE(true) TH_LOG("setns into new namespace failed");
+		break;
+	case 9:
+		ASSERT_FALSE(true) TH_LOG("umount succeeded but should have failed with EINVAL");
+		break;
+	case 10:
+		ASSERT_FALSE(true) TH_LOG("umount failed with wrong error (expected EINVAL)");
+		break;
+	case 13:
+		ASSERT_FALSE(true) TH_LOG("listmount failed");
+		break;
+	case 14:
+		ASSERT_FALSE(true) TH_LOG("No mounts in new namespace");
+		break;
+	case 15:
+		ASSERT_FALSE(true) TH_LOG("statmount_alloc failed");
+		break;
+	default:
+		ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)",
+					  WEXITSTATUS(status));
+		break;
+	}
+}
+
+TEST_F(fsmount_ns_userns, umount_succeeds)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+
+	if (pid == 0) {
+		uint64_t new_ns_id;
+		uint64_t list[256];
+		ssize_t nr_mounts;
+		int fs_fd, fd;
+		ssize_t i;
+
+		if (unshare(CLONE_NEWNS))
+			_exit(1);
+
+		if (sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) != 0)
+			_exit(1);
+
+		fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC);
+		if (fs_fd < 0)
+			_exit(3);
+
+		if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
+			close(fs_fd);
+			_exit(4);
+		}
+
+		fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0);
+		close(fs_fd);
+
+		if (fd < 0) {
+			if (errno == EINVAL)
+				_exit(6);
+			_exit(1);
+		}
+
+		if (get_mnt_ns_id(fd, &new_ns_id) != 0)
+			_exit(7);
+
+		/* Get all mounts in the new namespace */
+		nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, LISTMOUNT_REVERSE);
+		if (nr_mounts < 0)
+			_exit(13);
+
+		if (nr_mounts < 1)
+			_exit(14);
+
+		/* Enter the new namespace */
+		if (setns(fd, CLONE_NEWNS) < 0)
+			_exit(8);
+
+		for (i = 0; i < nr_mounts; i++) {
+			struct statmount *sm;
+			const char *mnt_point;
+
+			sm = statmount_alloc(list[i], new_ns_id,
+					     STATMOUNT_MNT_POINT);
+			if (!sm)
+				_exit(15);
+
+			mnt_point = sm->str + sm->mnt_point;
+
+			if (umount2(mnt_point, MNT_DETACH) != 0) {
+				free(sm);
+				_exit(9);
+			}
+
+			free(sm);
+		}
+
+		close(fd);
+		_exit(0);
+	}
+
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+
+	switch (WEXITSTATUS(status)) {
+	case 0:
+		break;
+	case 1:
+		ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed or unshare failed");
+		break;
+	case 3:
+		SKIP(return, "fsopen failed");
+		break;
+	case 4:
+		SKIP(return, "fsconfig CMD_CREATE failed");
+		break;
+	case 6:
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+		break;
+	case 7:
+		ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID");
+		break;
+	case 8:
+		ASSERT_FALSE(true) TH_LOG("setns into new namespace failed");
+		break;
+	case 9:
+		ASSERT_FALSE(true) TH_LOG("umount failed but should have succeeded");
+		break;
+	case 13:
+		ASSERT_FALSE(true) TH_LOG("listmount failed");
+		break;
+	case 14:
+		ASSERT_FALSE(true) TH_LOG("No mounts in new namespace");
+		break;
+	case 15:
+		ASSERT_FALSE(true) TH_LOG("statmount_alloc failed");
+		break;
+	default:
+		ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)",
+					  WEXITSTATUS(status));
+		break;
+	}
+}
+
+FIXTURE(fsmount_ns_mount_attrs)
+{
+	int fd;
+	int fs_fd;
+};
+
+FIXTURE_SETUP(fsmount_ns_mount_attrs)
+{
+	int ret;
+
+	self->fd = -1;
+	self->fs_fd = -1;
+
+	/* Check if fsopen syscall is supported */
+	ret = sys_fsopen("tmpfs", 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "fsopen() syscall not supported");
+	if (ret >= 0)
+		close(ret);
+
+	/* Check if statmount/listmount are supported */
+	ret = statmount(0, 0, 0, 0, NULL, 0, 0);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "statmount() syscall not supported");
+}
+
+FIXTURE_TEARDOWN(fsmount_ns_mount_attrs)
+{
+	if (self->fd >= 0)
+		close(self->fd);
+	if (self->fs_fd >= 0)
+		close(self->fs_fd);
+}
+
+TEST_F(fsmount_ns_mount_attrs, readonly)
+{
+	struct statmount sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC,
+			       MOUNT_ATTR_RDONLY);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ASSERT_EQ(ret, 0);
+
+	/* Verify the mount is read-only */
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_RDONLY);
+}
+
+TEST_F(fsmount_ns_mount_attrs, noexec)
+{
+	struct statmount sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC,
+			       MOUNT_ATTR_NOEXEC);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ASSERT_EQ(ret, 0);
+
+	/* Verify the mount is noexec */
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOEXEC);
+}
+
+TEST_F(fsmount_ns_mount_attrs, nosuid)
+{
+	struct statmount sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC,
+			       MOUNT_ATTR_NOSUID);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ASSERT_EQ(ret, 0);
+
+	/* Verify the mount is nosuid */
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOSUID);
+}
+
+TEST_F(fsmount_ns_mount_attrs, noatime)
+{
+	struct statmount sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC,
+			       MOUNT_ATTR_NOATIME);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ASSERT_EQ(ret, 0);
+
+	/* Verify the mount is noatime */
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOATIME);
+}
+
+TEST_F(fsmount_ns_mount_attrs, combined)
+{
+	struct statmount sm;
+	uint64_t new_ns_id;
+	uint64_t list[256];
+	ssize_t nr_mounts;
+	int ret;
+
+	self->fs_fd = create_tmpfs_fd();
+	ASSERT_GE(self->fs_fd, 0);
+
+	self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC,
+			       MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC |
+			       MOUNT_ATTR_NOSUID | MOUNT_ATTR_NOATIME);
+	if (self->fd < 0 && errno == EINVAL)
+		SKIP(return, "FSMOUNT_NAMESPACE not supported");
+
+	ASSERT_GE(self->fd, 0);
+
+	ret = get_mnt_ns_id(self->fd, &new_ns_id);
+	ASSERT_EQ(ret, 0);
+
+	nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0);
+	ASSERT_GE(nr_mounts, 1);
+
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ASSERT_EQ(ret, 0);
+
+	/* Verify all attributes are set */
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_RDONLY);
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOEXEC);
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOSUID);
+	ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOATIME);
+}
+
+TEST_HARNESS_MAIN

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 7/7] selftests/open_tree_ns: fix compilation
  2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
                   ` (5 preceding siblings ...)
  2026-01-22 10:48 ` [PATCH 6/7] selftests: add FSMOUNT_NAMESPACE tests Christian Brauner
@ 2026-01-22 10:48 ` Christian Brauner
  6 siblings, 0 replies; 13+ messages in thread
From: Christian Brauner @ 2026-01-22 10:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Alexander Viro, Jan Kara, Jeff Layton, Amir Goldstein,
	Josef Bacik, Aleksa Sarai, Christian Brauner

Fix open_tree_ns selftests and remove it's own local version of the
statmount() allocation helper.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 .../selftests/filesystems/open_tree_ns/Makefile    |  2 +-
 .../filesystems/open_tree_ns/open_tree_ns_test.c   | 33 ++++------------------
 2 files changed, 6 insertions(+), 29 deletions(-)

diff --git a/tools/testing/selftests/filesystems/open_tree_ns/Makefile b/tools/testing/selftests/filesystems/open_tree_ns/Makefile
index 73c03c4a7ef6..4976ed1d7d4a 100644
--- a/tools/testing/selftests/filesystems/open_tree_ns/Makefile
+++ b/tools/testing/selftests/filesystems/open_tree_ns/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 TEST_GEN_PROGS := open_tree_ns_test
 
-CFLAGS := -Wall -Werror -g $(KHDR_INCLUDES)
+CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
 LDLIBS := -lcap
 
 include ../../lib.mk
diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c
index 9711556280ae..7511696bea25 100644
--- a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c
+++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
+ * Copyright (c) 2026 Christian Brauner <brauner@kernel.org>
+ *
  * Test for OPEN_TREE_NAMESPACE flag.
  *
  * Test that open_tree() with OPEN_TREE_NAMESPACE creates a new mount
@@ -50,31 +52,6 @@ static int get_mnt_ns_id_from_path(const char *path, uint64_t *mnt_ns_id)
 	return ret;
 }
 
-#define STATMOUNT_BUFSIZE (1 << 15)
-
-static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask)
-{
-	struct statmount *buf;
-	size_t bufsize = STATMOUNT_BUFSIZE;
-	int ret;
-
-	for (;;) {
-		buf = malloc(bufsize);
-		if (!buf)
-			return NULL;
-
-		ret = statmount(mnt_id, mnt_ns_id, mask, buf, bufsize, 0);
-		if (ret == 0)
-			return buf;
-
-		free(buf);
-		if (errno != EOVERFLOW)
-			return NULL;
-
-		bufsize <<= 1;
-	}
-}
-
 static void log_mount(struct __test_metadata *_metadata, struct statmount *sm)
 {
 	const char *fs_type = "";
@@ -221,7 +198,7 @@ FIXTURE_SETUP(open_tree_ns)
 		SKIP(return, "open_tree() syscall not supported");
 
 	/* Check if statmount/listmount are supported */
-	ret = statmount(0, 0, 0, NULL, 0, 0);
+	ret = statmount(0, 0, 0, 0, NULL, 0, 0);
 	if (ret == -1 && errno == ENOSYS)
 		SKIP(return, "statmount() syscall not supported");
 
@@ -340,7 +317,7 @@ TEST_F(open_tree_ns, verify_mount_properties)
 	ASSERT_GE(nr_mounts, 1);
 
 	/* Get info about the root mount (the bind mount, rootfs is hidden) */
-	ret = statmount(list[0], new_ns_id, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
+	ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0);
 	ASSERT_EQ(ret, 0);
 
 	ASSERT_NE(sm.mnt_id, sm.mnt_parent_id);
@@ -452,7 +429,7 @@ FIXTURE_SETUP(open_tree_ns_userns)
 		SKIP(return, "open_tree() syscall not supported");
 
 	/* Check if statmount/listmount are supported */
-	ret = statmount(0, 0, 0, NULL, 0, 0);
+	ret = statmount(0, 0, 0, 0, NULL, 0, 0);
 	if (ret == -1 && errno == ENOSYS)
 		SKIP(return, "statmount() syscall not supported");
 }

-- 
2.47.3


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/7] mount: add FSMOUNT_NAMESPACE
  2026-01-22 10:48 ` [PATCH 3/7] mount: add FSMOUNT_NAMESPACE Christian Brauner
@ 2026-02-11 11:47   ` Mark Brown
  2026-02-11 12:13     ` Christian Brauner
  0 siblings, 1 reply; 13+ messages in thread
From: Mark Brown @ 2026-02-11 11:47 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, Jeff Layton,
	Amir Goldstein, Josef Bacik, Aleksa Sarai

[-- Attachment #1: Type: text/plain, Size: 9805 bytes --]

On Thu, Jan 22, 2026 at 11:48:48AM +0100, Christian Brauner wrote:
> Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
> namespace with the newly created filesystem attached to a copy of the
> real rootfs. This returns a namespace file descriptor instead of an
> O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for open_tree().

I'm seeing a regression in the LTP fsmount02 test in yesterday's -next
on arm64 which bisect to this patch, the test reports some unexpected
successes with logs like this:

  tst_test.c:1956: TINFO: === Testing on ext2 ===
  tst_test.c:1280: TINFO: Formatting /dev/loop0 with ext2 opts='' extra opts=''
  mke2fs 1.47.2 (1-Jan-2025)
  fsmount02.c:67: TPASS: invalid-fd: fsmount() failed as expected: EBADF (9)
  fsmount02.c:56: TFAIL: invalid-flags: fsmount() succeeded unexpectedly (index: 1)
  fsmount02.c:67: TPASS: invalid-attrs: fsmount() failed as expected: EINVAL (22)

for each filesystem it tests.

Full log:

   https://lava.sirena.org.uk/scheduler/job/2445995#L5878

Bisect log:

# bad: [fd9678829d6dd0c10fde080b536abf4b1121c346] Add linux-next specific files for 20260210
# good: [0b6f6fa3363fdb9234e4b91e4cf22b5d50bff4f7] Merge branch 'for-linux-next-fixes' of https://gitlab.freedesktop.org/drm/misc/kernel.git
# good: [a9aabb3b839aba094ed80861054993785c61462c] Merge tag 'rust-6.20-7.0' of git://git.kernel.org/pub/scm/linux/kernel/git/ojeda/linux
# good: [1a4b0c999101b2532723f9bd9818b70ffa7580f4] regulator: mt6363: Fix interrmittent timeout
# good: [5578da7d957fbaf91f6c39ba2363c2d2e4273183] ASoC: rt721-sdca: Fix issue of fail to detect OMTP jack type
# good: [3a17ba6557e28d5d99b7e3cad31f22ad28a36cc2] mfd: sec: Add support for S2MPG11 PMIC via ACPM
# good: [fe8429a2717fc01082502b0adf680a50b230eff7] regulator: s2mps11: more descriptive gpio consumer name
# good: [6ffdc7eb48bd2268c37c2accad454c043b9cc987] regcache: Demote defaults readback from HW to debug print
# good: [20c4701b75a3d6ce09d61e17125aefe77e7eb333] dt-bindings: regulator: mark regulator-suspend-microvolt as deprecated
# good: [dccc66b0e92d48d9a1908a3ccb8142e0ee3381f5] regmap: Enable REGMAP when REGMAP_SLIMBUS is enabled
# good: [62b04225e99a5d1c71c5c73d2aa6618bc2c0738f] regulator: dt-bindings: rpi-panel: Mark 7" Raspberry Pi as GPIO controller
# good: [de9f1b1583aecb246b659effb03f2456604fab64] regulator: dt-bindings: mediatek,mt6331: Add missing ldo-vio28 vreg
# good: [b0fc1e7701940d12ea2c41f386aa552bc4cc3629] regulator: Add TPS65185 driver
# good: [09dc08b396c954820f119e1ab0c7d72333c18323] regulator: dummy, make dummy_regulator_driver static
# good: [8d38423d9dea7353a8a54a3ab2e0d0aa04ed34d0] regulator: core: don't fail regulator_register() with missing required supply
# good: [b0655377aa5a410df02d89170c20141a1a5bbc28] rust: regulator: replace `kernel::c_str!` with C-Strings
# good: [32a708ba5db50cf928a1f1b2039ceef33de2c286] regulator: Add rt8092 support
# good: [9e92c559d49d6fb903af17a31a469aac51b1766d] regulator: max77675: Add MAX77675 regulator driver
# good: [03d281f384768610bf90697bce9e35d3d596de77] rust: regulator: add __rust_helper to helpers
# good: [6c177775dcc5e70a64ddf4ee842c66af498f2c7c] Merge branch 'next/drivers' into for-next
git bisect start 'fd9678829d6dd0c10fde080b536abf4b1121c346' '0b6f6fa3363fdb9234e4b91e4cf22b5d50bff4f7' 'a9aabb3b839aba094ed80861054993785c61462c' '1a4b0c999101b2532723f9bd9818b70ffa7580f4' '5578da7d957fbaf91f6c39ba2363c2d2e4273183' '3a17ba6557e28d5d99b7e3cad31f22ad28a36cc2' 'fe8429a2717fc01082502b0adf680a50b230eff7' '6ffdc7eb48bd2268c37c2accad454c043b9cc987' '20c4701b75a3d6ce09d61e17125aefe77e7eb333' 'dccc66b0e92d48d9a1908a3ccb8142e0ee3381f5' '62b04225e99a5d1c71c5c73d2aa6618bc2c0738f' 'de9f1b1583aecb246b659effb03f2456604fab64' 'b0fc1e7701940d12ea2c41f386aa552bc4cc3629' '09dc08b396c954820f119e1ab0c7d72333c18323' '8d38423d9dea7353a8a54a3ab2e0d0aa04ed34d0' 'b0655377aa5a410df02d89170c20141a1a5bbc28' '32a708ba5db50cf928a1f1b2039ceef33de2c286' '9e92c559d49d6fb903af17a31a469aac51b1766d' '03d281f384768610bf90697bce9e35d3d596de77' '6c177775dcc5e70a64ddf4ee842c66af498f2c7c'
# test job: [a9aabb3b839aba094ed80861054993785c61462c] https://lava.sirena.org.uk/scheduler/job/2445657
# test job: [1a4b0c999101b2532723f9bd9818b70ffa7580f4] https://lava.sirena.org.uk/scheduler/job/2444892
# test job: [5578da7d957fbaf91f6c39ba2363c2d2e4273183] https://lava.sirena.org.uk/scheduler/job/2445060
# test job: [3a17ba6557e28d5d99b7e3cad31f22ad28a36cc2] https://lava.sirena.org.uk/scheduler/job/2430473
# test job: [fe8429a2717fc01082502b0adf680a50b230eff7] https://lava.sirena.org.uk/scheduler/job/2429403
# test job: [6ffdc7eb48bd2268c37c2accad454c043b9cc987] https://lava.sirena.org.uk/scheduler/job/2409190
# test job: [20c4701b75a3d6ce09d61e17125aefe77e7eb333] https://lava.sirena.org.uk/scheduler/job/2386850
# test job: [dccc66b0e92d48d9a1908a3ccb8142e0ee3381f5] https://lava.sirena.org.uk/scheduler/job/2377205
# test job: [62b04225e99a5d1c71c5c73d2aa6618bc2c0738f] https://lava.sirena.org.uk/scheduler/job/2369398
# test job: [de9f1b1583aecb246b659effb03f2456604fab64] https://lava.sirena.org.uk/scheduler/job/2368840
# test job: [b0fc1e7701940d12ea2c41f386aa552bc4cc3629] https://lava.sirena.org.uk/scheduler/job/2364654
# test job: [09dc08b396c954820f119e1ab0c7d72333c18323] https://lava.sirena.org.uk/scheduler/job/2365445
# test job: [8d38423d9dea7353a8a54a3ab2e0d0aa04ed34d0] https://lava.sirena.org.uk/scheduler/job/2354317
# test job: [b0655377aa5a410df02d89170c20141a1a5bbc28] https://lava.sirena.org.uk/scheduler/job/2291672
# test job: [32a708ba5db50cf928a1f1b2039ceef33de2c286] https://lava.sirena.org.uk/scheduler/job/2279437
# test job: [9e92c559d49d6fb903af17a31a469aac51b1766d] https://lava.sirena.org.uk/scheduler/job/2232506
# test job: [03d281f384768610bf90697bce9e35d3d596de77] https://lava.sirena.org.uk/scheduler/job/2231124
# test job: [6c177775dcc5e70a64ddf4ee842c66af498f2c7c] https://lava.sirena.org.uk/scheduler/job/1780443
# test job: [fd9678829d6dd0c10fde080b536abf4b1121c346] https://lava.sirena.org.uk/scheduler/job/2445995
# bad: [fd9678829d6dd0c10fde080b536abf4b1121c346] Add linux-next specific files for 20260210
git bisect bad fd9678829d6dd0c10fde080b536abf4b1121c346
# test job: [1256bc7912123bf6f6c0b97809af184a55b0d9df] https://lava.sirena.org.uk/scheduler/job/2446075
# bad: [1256bc7912123bf6f6c0b97809af184a55b0d9df] Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git
git bisect bad 1256bc7912123bf6f6c0b97809af184a55b0d9df
# test job: [8fb729d87a37ced2889940c150622cd44f2d029d] https://lava.sirena.org.uk/scheduler/job/2446199
# good: [8fb729d87a37ced2889940c150622cd44f2d029d] Merge branch 'xtensa-for-next' of https://github.com/jcmvbkbc/linux-xtensa.git
git bisect good 8fb729d87a37ced2889940c150622cd44f2d029d
# test job: [275da93ce2b8fa2f82da1e8785d6f1930670ef88] https://lava.sirena.org.uk/scheduler/job/2446280
# good: [275da93ce2b8fa2f82da1e8785d6f1930670ef88] gve: Remove jumbo_remove step from TX path
git bisect good 275da93ce2b8fa2f82da1e8785d6f1930670ef88
# test job: [9329068ac66b4a8cc64faea43691f95f29cd32ab] https://lava.sirena.org.uk/scheduler/job/2446342
# bad: [9329068ac66b4a8cc64faea43691f95f29cd32ab] Merge branch 'for-next/pstore' of https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git
git bisect bad 9329068ac66b4a8cc64faea43691f95f29cd32ab
# test job: [dbfcce773f91a093d32cfd02821257765bf5ae78] https://lava.sirena.org.uk/scheduler/job/2446442
# good: [dbfcce773f91a093d32cfd02821257765bf5ae78] Merge branch 'nfsd-next' of https://git.kernel.org/pub/scm/linux/kernel/git/cel/linux
git bisect good dbfcce773f91a093d32cfd02821257765bf5ae78
# test job: [cb3ca564682a0a02a9f95b7b22ad434a7389daaf] https://lava.sirena.org.uk/scheduler/job/2446484
# good: [cb3ca564682a0a02a9f95b7b22ad434a7389daaf] Merge branch 'pci/controller/cadence-j721e'
git bisect good cb3ca564682a0a02a9f95b7b22ad434a7389daaf
# test job: [522a46affdceda863df9bf652119f463f480479d] https://lava.sirena.org.uk/scheduler/job/2446519
# good: [522a46affdceda863df9bf652119f463f480479d] Merge branch 'pci/controller/misc'
git bisect good 522a46affdceda863df9bf652119f463f480479d
# test job: [ee80126d7dee1f0e0a72683b6f38388e90ffaaf1] https://lava.sirena.org.uk/scheduler/job/2446657
# good: [ee80126d7dee1f0e0a72683b6f38388e90ffaaf1] Merge branch '9p-next' of https://github.com/martinetd/linux
git bisect good ee80126d7dee1f0e0a72683b6f38388e90ffaaf1
# test job: [19d1ad6cc1e8e435bbbeb9310388f1d3ba089c4e] https://lava.sirena.org.uk/scheduler/job/2446772
# bad: [19d1ad6cc1e8e435bbbeb9310388f1d3ba089c4e] Merge branch 'deferred.namespace-7.0' into vfs.all
git bisect bad 19d1ad6cc1e8e435bbbeb9310388f1d3ba089c4e
# test job: [a39162f77f49b618df5a721a1e48d8b903280fbd] https://lava.sirena.org.uk/scheduler/job/2446808
# good: [a39162f77f49b618df5a721a1e48d8b903280fbd] exportfs: clarify the documentation of open()/permission() expotrfs ops
git bisect good a39162f77f49b618df5a721a1e48d8b903280fbd
# test job: [30d2122405f27f19de5e8c38762c78088a6abe8d] https://lava.sirena.org.uk/scheduler/job/2446835
# bad: [30d2122405f27f19de5e8c38762c78088a6abe8d] selftests: add FSMOUNT_NAMESPACE tests
git bisect bad 30d2122405f27f19de5e8c38762c78088a6abe8d
# test job: [4f5ba37ddcdf5eaac2408178050183345d56b2d3] https://lava.sirena.org.uk/scheduler/job/2446918
# bad: [4f5ba37ddcdf5eaac2408178050183345d56b2d3] mount: add FSMOUNT_NAMESPACE
git bisect bad 4f5ba37ddcdf5eaac2408178050183345d56b2d3
# test job: [1d497d97fb22dfd3cd215a38f8dd56fc974c76a7] https://lava.sirena.org.uk/scheduler/job/2446991
# good: [1d497d97fb22dfd3cd215a38f8dd56fc974c76a7] mount: simplify __do_loopback()
git bisect good 1d497d97fb22dfd3cd215a38f8dd56fc974c76a7
# first bad commit: [4f5ba37ddcdf5eaac2408178050183345d56b2d3] mount: add FSMOUNT_NAMESPACE

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/7] mount: add FSMOUNT_NAMESPACE
  2026-02-11 11:47   ` Mark Brown
@ 2026-02-11 12:13     ` Christian Brauner
  2026-03-18 20:16       ` Mark Brown
  0 siblings, 1 reply; 13+ messages in thread
From: Christian Brauner @ 2026-02-11 12:13 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, Jeff Layton,
	Amir Goldstein, Josef Bacik, Aleksa Sarai

On Wed, Feb 11, 2026 at 11:47:39AM +0000, Mark Brown wrote:
> On Thu, Jan 22, 2026 at 11:48:48AM +0100, Christian Brauner wrote:
> > Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
> > namespace with the newly created filesystem attached to a copy of the
> > real rootfs. This returns a namespace file descriptor instead of an
> > O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for open_tree().
> 
> I'm seeing a regression in the LTP fsmount02 test in yesterday's -next
> on arm64 which bisect to this patch, the test reports some unexpected
> successes with logs like this:

Thanks. This is postponed until next cycle.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/7] mount: add FSMOUNT_NAMESPACE
  2026-02-11 12:13     ` Christian Brauner
@ 2026-03-18 20:16       ` Mark Brown
  2026-03-20 13:40         ` Christian Brauner
  0 siblings, 1 reply; 13+ messages in thread
From: Mark Brown @ 2026-03-18 20:16 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, Jeff Layton,
	Amir Goldstein, Josef Bacik, Aleksa Sarai

[-- Attachment #1: Type: text/plain, Size: 991 bytes --]

On Wed, Feb 11, 2026 at 01:13:17PM +0100, Christian Brauner wrote:
> On Wed, Feb 11, 2026 at 11:47:39AM +0000, Mark Brown wrote:

> > On Thu, Jan 22, 2026 at 11:48:48AM +0100, Christian Brauner wrote:
> > > Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
> > > namespace with the newly created filesystem attached to a copy of the
> > > real rootfs. This returns a namespace file descriptor instead of an
> > > O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for open_tree().

> > I'm seeing a regression in the LTP fsmount02 test in yesterday's -next
> > on arm64 which bisect to this patch, the test reports some unexpected
> > successes with logs like this:

> Thanks. This is postponed until next cycle.

The patch is now back in -next and I'm seeing this issue again on 32 bit
arm:

  https://lava.sirena.org.uk/scheduler/job/2577791#L5864

and arm64 too:

  https://lava.sirena.org.uk/scheduler/job/2577791#L5864

with bisection pointing to the same commit.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/7] mount: add FSMOUNT_NAMESPACE
  2026-03-18 20:16       ` Mark Brown
@ 2026-03-20 13:40         ` Christian Brauner
  2026-03-20 14:04           ` Mark Brown
  0 siblings, 1 reply; 13+ messages in thread
From: Christian Brauner @ 2026-03-20 13:40 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, Jeff Layton,
	Amir Goldstein, Josef Bacik, Aleksa Sarai

On Wed, Mar 18, 2026 at 08:16:58PM +0000, Mark Brown wrote:
> On Wed, Feb 11, 2026 at 01:13:17PM +0100, Christian Brauner wrote:
> > On Wed, Feb 11, 2026 at 11:47:39AM +0000, Mark Brown wrote:
> 
> > > On Thu, Jan 22, 2026 at 11:48:48AM +0100, Christian Brauner wrote:
> > > > Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
> > > > namespace with the newly created filesystem attached to a copy of the
> > > > real rootfs. This returns a namespace file descriptor instead of an
> > > > O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for open_tree().
> 
> > > I'm seeing a regression in the LTP fsmount02 test in yesterday's -next
> > > on arm64 which bisect to this patch, the test reports some unexpected
> > > successes with logs like this:
> 
> > Thanks. This is postponed until next cycle.
> 
> The patch is now back in -next and I'm seeing this issue again on 32 bit
> arm:
> 
>   https://lava.sirena.org.uk/scheduler/job/2577791#L5864
> 
> and arm64 too:
> 
>   https://lava.sirena.org.uk/scheduler/job/2577791#L5864
> 
> with bisection pointing to the same commit.

This isn't a bug in the kernel. This is LTP testing the value the new
flag took to test for invalid flags. My advise would be to always use
the uppermost bit or something...

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/7] mount: add FSMOUNT_NAMESPACE
  2026-03-20 13:40         ` Christian Brauner
@ 2026-03-20 14:04           ` Mark Brown
  0 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2026-03-20 14:04 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, Jeff Layton,
	Amir Goldstein, Josef Bacik, Aleksa Sarai

[-- Attachment #1: Type: text/plain, Size: 509 bytes --]

On Fri, Mar 20, 2026 at 02:40:59PM +0100, Christian Brauner wrote:
> On Wed, Mar 18, 2026 at 08:16:58PM +0000, Mark Brown wrote:

> > The patch is now back in -next and I'm seeing this issue again on 32 bit
> > arm:

> > and arm64 too:

> > with bisection pointing to the same commit.

> This isn't a bug in the kernel. This is LTP testing the value the new
> flag took to test for invalid flags. My advise would be to always use
> the uppermost bit or something...

*sigh* not again :/  Thanks for checking.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-03-20 14:04 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-22 10:48 [PATCH 0/7] fsmount: add FSMOUNT_NAMESPACE Christian Brauner
2026-01-22 10:48 ` [PATCH 1/7] mount: start iterating from start of rbtree Christian Brauner
2026-01-22 10:48 ` [PATCH 2/7] mount: simplify __do_loopback() Christian Brauner
2026-01-22 10:48 ` [PATCH 3/7] mount: add FSMOUNT_NAMESPACE Christian Brauner
2026-02-11 11:47   ` Mark Brown
2026-02-11 12:13     ` Christian Brauner
2026-03-18 20:16       ` Mark Brown
2026-03-20 13:40         ` Christian Brauner
2026-03-20 14:04           ` Mark Brown
2026-01-22 10:48 ` [PATCH 4/7] tools: update mount.h header Christian Brauner
2026-01-22 10:48 ` [PATCH 5/7] selftests/statmount: add statmount_alloc() helper Christian Brauner
2026-01-22 10:48 ` [PATCH 6/7] selftests: add FSMOUNT_NAMESPACE tests Christian Brauner
2026-01-22 10:48 ` [PATCH 7/7] selftests/open_tree_ns: fix compilation Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox