Linux filesystem development
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org, Mark Brown <broonie@kernel.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
	 Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>
Subject: [PATCH] kunit: use scoped_with_init_fs() in tests that resolve paths
Date: Wed, 01 Jul 2026 16:54:23 +0200	[thread overview]
Message-ID: <20260701-work-kunit-nullfs-v1-1-dfa60270434f@kernel.org> (raw)

KUnit runs each test in a kthread (kunit_generic_run_threadfn_adapter()).
Since commit 32750c77e811 ("fs: start all kthreads in nullfs") every
kthread starts out with its fs_struct root and pwd pointing at an empty,
immutable nullfs and can no longer resolve paths.

Two test suites resolve paths from test context and now fail. The misc
minor tests create a device node and open it:

  # miscdev_test_static_basic: EXPECTATION FAILED at drivers/char/misc_minor_kunit.c:166
  failed to create node
  # miscdev_test_static_basic: EXPECTATION FAILED at drivers/char/misc_minor_kunit.c:170
  failed to open misc device: -2

init_mknod("/dev/...") resolves relative to current->fs, which is now
nullfs with no /dev, so node creation returns -ENOENT and the following
filp_open() fails with -ENOENT (-2). The initramfs tests hit the same
wall: they call unpack_to_rootfs() (init_mkdir()/init_mknod() under the
hood) and then verify with relative-name init_stat()/init_unlink()/
filp_open(), all of which resolve against the nullfs root and pwd.

Wrap the path-resolving regions of both suites in scoped_with_init_fs(),
which borrows the userspace init fs_struct (set up by init_userspace_fs()
before kunit_run_all_tests()) for the duration. This is the same opt-in
the rest of the tree uses for kthread path resolution. Doing it per test
rather than blanket-wrapping the KUnit runner keeps the nullfs isolation
in place for the many tests that should never touch the filesystem.

Reported-by: Mark Brown <broonie@kernel.org>
Closes: https://lore.kernel.org/r/akOrbOsKUqgZarGw@sirena.org.uk
Fixes: 32750c77e811 ("fs: start all kthreads in nullfs")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/char/misc_minor_kunit.c |  25 +++--
 init/initramfs_test.c           | 216 +++++++++++++++++++++++-----------------
 2 files changed, 137 insertions(+), 104 deletions(-)

diff --git a/drivers/char/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c
index e930c78e1ef9..e85210cbb640 100644
--- a/drivers/char/misc_minor_kunit.c
+++ b/drivers/char/misc_minor_kunit.c
@@ -5,6 +5,7 @@
 #include <linux/miscdevice.h>
 #include <linux/fs.h>
 #include <linux/file.h>
+#include <linux/fs_struct.h>
 #include <linux/init_syscalls.h>
 
 /* static minor (LCD_MINOR) */
@@ -160,18 +161,22 @@ static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice *
 	char *devname;
 
 	devname = kasprintf(GFP_KERNEL, "/dev/%s", misc->name);
-	ret = init_mknod(devname, S_IFCHR | 0600,
-			 new_encode_dev(MKDEV(MISC_MAJOR, misc->minor)));
-	if (ret != 0)
-		KUNIT_FAIL(test, "failed to create node\n");
 
-	filp = filp_open(devname, O_RDONLY, 0);
-	if (IS_ERR(filp))
-		KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp));
-	else
-		fput(filp);
+	/* Tests run in a nullfs kthread; borrow the init fs to resolve /dev. */
+	scoped_with_init_fs() {
+		ret = init_mknod(devname, S_IFCHR | 0600,
+				 new_encode_dev(MKDEV(MISC_MAJOR, misc->minor)));
+		if (ret != 0)
+			KUNIT_FAIL(test, "failed to create node\n");
+
+		filp = filp_open(devname, O_RDONLY, 0);
+		if (IS_ERR(filp))
+			KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp));
+		else
+			fput(filp);
 
-	init_unlink(devname);
+		init_unlink(devname);
+	}
 	kfree(devname);
 }
 
diff --git a/init/initramfs_test.c b/init/initramfs_test.c
index bc55306d226d..1fc990a66563 100644
--- a/init/initramfs_test.c
+++ b/init/initramfs_test.c
@@ -3,6 +3,7 @@
 #include <linux/fcntl.h>
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/fs_struct.h>
 #include <linux/init.h>
 #include <linux/init_syscalls.h>
 #include <linux/initrd.h>
@@ -116,38 +117,41 @@ static void __init initramfs_test_extract(struct kunit *test)
 			      GFP_KERNEL);
 	len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf);
 
-	ktime_get_real_ts64(&ts_before);
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	ktime_get_real_ts64(&ts_after);
-	if (err) {
-		KUNIT_FAIL(test, "unpack failed %s", err);
-		goto out;
-	}
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		ktime_get_real_ts64(&ts_before);
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		ktime_get_real_ts64(&ts_after);
+		if (err) {
+			KUNIT_FAIL(test, "unpack failed %s", err);
+			goto out;
+		}
 
-	KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0);
-	KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode));
-	KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid)));
-	KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid)));
-	KUNIT_EXPECT_EQ(test, st.nlink, 1);
-	if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
-		KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime);
-	} else {
-		KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
-		KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
-	}
-	KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize);
+		KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0);
+		KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode));
+		KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid)));
+		KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid)));
+		KUNIT_EXPECT_EQ(test, st.nlink, 1);
+		if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
+			KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime);
+		} else {
+			KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
+			KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
+		}
+		KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize);
 
-	KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0);
-	KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode));
-	if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
-		KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime);
-	} else {
-		KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
-		KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
-	}
+		KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0);
+		KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode));
+		if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
+			KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime);
+		} else {
+			KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
+			KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
+		}
 
-	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
-	KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0);
+	}
 out:
 	kfree(cpio_srcbuf);
 }
@@ -197,8 +201,11 @@ static void __init initramfs_test_fname_overrun(struct kunit *test)
 		suffix_off--;
 	}
 
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	KUNIT_EXPECT_NOT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		KUNIT_EXPECT_NOT_NULL(test, err);
+	}
 
 	kfree(cpio_srcbuf);
 }
@@ -233,22 +240,25 @@ static void __init initramfs_test_data(struct kunit *test)
 
 	len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf);
 
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	KUNIT_EXPECT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		KUNIT_EXPECT_NULL(test, err);
 
-	file = filp_open(c[0].fname, O_RDONLY, 0);
-	if (IS_ERR(file)) {
-		KUNIT_FAIL(test, "open failed");
-		goto out;
-	}
+		file = filp_open(c[0].fname, O_RDONLY, 0);
+		if (IS_ERR(file)) {
+			KUNIT_FAIL(test, "open failed");
+			goto out;
+		}
 
-	/* read back file contents into @cpio_srcbuf and confirm match */
-	len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL);
-	KUNIT_EXPECT_EQ(test, len, c[0].filesize);
-	KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len);
+		/* read back file contents into @cpio_srcbuf and confirm match */
+		len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL);
+		KUNIT_EXPECT_EQ(test, len, c[0].filesize);
+		KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len);
 
-	fput(file);
-	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+		fput(file);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+	}
 out:
 	kfree(cpio_srcbuf);
 }
@@ -288,25 +298,28 @@ static void __init initramfs_test_csum(struct kunit *test)
 
 	len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf);
 
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	KUNIT_EXPECT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		KUNIT_EXPECT_NULL(test, err);
 
-	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
-	KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
 
-	/* mess up the csum and confirm that unpack fails */
-	c[0].csum--;
-	len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf);
+		/* mess up the csum and confirm that unpack fails */
+		c[0].csum--;
+		len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf);
 
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	KUNIT_EXPECT_NOT_NULL(test, err);
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		KUNIT_EXPECT_NOT_NULL(test, err);
 
-	/*
-	 * file (with content) is still retained in case of bad-csum abort.
-	 * Perhaps we should change this.
-	 */
-	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
-	KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT);
+		/*
+		 * file (with content) is still retained in case of bad-csum abort.
+		 * Perhaps we should change this.
+		 */
+		KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT);
+	}
 	kfree(cpio_srcbuf);
 }
 
@@ -344,17 +357,20 @@ static void __init initramfs_test_hardlink(struct kunit *test)
 
 	len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf);
 
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	KUNIT_EXPECT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		KUNIT_EXPECT_NULL(test, err);
 
-	KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0);
-	KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0);
-	KUNIT_EXPECT_EQ(test, st0.ino, st1.ino);
-	KUNIT_EXPECT_EQ(test, st0.nlink, 2);
-	KUNIT_EXPECT_EQ(test, st1.nlink, 2);
+		KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0);
+		KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0);
+		KUNIT_EXPECT_EQ(test, st0.ino, st1.ino);
+		KUNIT_EXPECT_EQ(test, st0.nlink, 2);
+		KUNIT_EXPECT_EQ(test, st1.nlink, 2);
 
-	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
-	KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
+	}
 
 	kfree(cpio_srcbuf);
 }
@@ -387,12 +403,15 @@ static void __init initramfs_test_many(struct kunit *test)
 	}
 
 	len = p - cpio_srcbuf;
-	err = unpack_to_rootfs(cpio_srcbuf, len);
-	KUNIT_EXPECT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(cpio_srcbuf, len);
+		KUNIT_EXPECT_NULL(test, err);
 
-	for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) {
-		sprintf(thispath, "initramfs_test_many-%d", i);
-		KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0);
+		for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) {
+			sprintf(thispath, "initramfs_test_many-%d", i);
+			KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0);
+		}
 	}
 
 	kfree(cpio_srcbuf);
@@ -439,22 +458,25 @@ static void __init initramfs_test_fname_pad(struct kunit *test)
 	memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname"));
 	len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_srcbuf);
 
-	err = unpack_to_rootfs(tbufs->cpio_srcbuf, len);
-	KUNIT_EXPECT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(tbufs->cpio_srcbuf, len);
+		KUNIT_EXPECT_NULL(test, err);
 
-	file = filp_open(c[0].fname, O_RDONLY, 0);
-	if (IS_ERR(file)) {
-		KUNIT_FAIL(test, "open failed");
-		goto out;
-	}
+		file = filp_open(c[0].fname, O_RDONLY, 0);
+		if (IS_ERR(file)) {
+			KUNIT_FAIL(test, "open failed");
+			goto out;
+		}
 
-	/* read back file contents into @cpio_srcbuf and confirm match */
-	len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL);
-	KUNIT_EXPECT_EQ(test, len, c[0].filesize);
-	KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len);
+		/* read back file contents into @cpio_srcbuf and confirm match */
+		len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL);
+		KUNIT_EXPECT_EQ(test, len, c[0].filesize);
+		KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len);
 
-	fput(file);
-	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+		fput(file);
+		KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
+	}
 out:
 	kfree(tbufs);
 }
@@ -495,13 +517,16 @@ static void __init initramfs_test_fname_path_max(struct kunit *test)
 	memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1);
 	len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_src);
 
-	/* unpack skips over fname_oversize instead of returning an error */
-	err = unpack_to_rootfs(tbufs->cpio_src, len);
-	KUNIT_EXPECT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		/* unpack skips over fname_oversize instead of returning an error */
+		err = unpack_to_rootfs(tbufs->cpio_src, len);
+		KUNIT_EXPECT_NULL(test, err);
 
-	KUNIT_EXPECT_EQ(test, init_stat("fname_oversize", &st0, 0), -ENOENT);
-	KUNIT_EXPECT_EQ(test, init_stat("fname_ok", &st1, 0), 0);
-	KUNIT_EXPECT_EQ(test, init_rmdir("fname_ok"), 0);
+		KUNIT_EXPECT_EQ(test, init_stat("fname_oversize", &st0, 0), -ENOENT);
+		KUNIT_EXPECT_EQ(test, init_stat("fname_ok", &st1, 0), 0);
+		KUNIT_EXPECT_EQ(test, init_rmdir("fname_ok"), 0);
+	}
 
 	kfree(tbufs);
 }
@@ -539,8 +564,11 @@ static void __init initramfs_test_hdr_hex(struct kunit *test)
 	/* inject_ox=true to add "0x" cpio field prefixes */
 	len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src);
 
-	err = unpack_to_rootfs(tbufs->cpio_src, len);
-	KUNIT_EXPECT_NOT_NULL(test, err);
+	/* Tests run in a nullfs kthread; borrow the init fs for path resolution. */
+	scoped_with_init_fs() {
+		err = unpack_to_rootfs(tbufs->cpio_src, len);
+		KUNIT_EXPECT_NOT_NULL(test, err);
+	}
 
 	kfree(tbufs);
 }

---
base-commit: 1b8a585da1b50b3db0e7bbd073ca274875539ea8
change-id: 20260701-work-kunit-nullfs-e47e4bf270e9


             reply	other threads:[~2026-07-01 14:54 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-01 14:54 Christian Brauner [this message]
2026-07-02  6:53 ` [PATCH] kunit: use scoped_with_init_fs() in tests that resolve paths Christian Brauner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260701-work-kunit-nullfs-v1-1-dfa60270434f@kernel.org \
    --to=brauner@kernel.org \
    --cc=broonie@kernel.org \
    --cc=jack@suse.cz \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox