[RFC PATCH 3/3] fs: add KUnit tests for tasks with a null root or cwd

John Ericson <[email protected]>
Newsgroups gmane.linux.nfs,gmane.linux.kernel.cross-arch,gmane.linux.kernel,gmane.linux.file-systems,gmane.linux.kernel.api
Message-ID <[email protected]>
From: John Ericson <mail-/[email protected]>

A KUnit suite (`CONFIG_NULL_ROOT_CWD_KUNIT_TEST`) exercises the previous
patch against a task whose own root and/or cwd it nulls: absolute and
`AT_FDCWD`-relative lookups fail, `..` climbs, and descriptor-anchored
lookups keep working. Each test unshares its `fs_struct` so it only ever
touches a private copy, and restores the original root/cwd afterwards.

It is gated by `CONFIG_NULL_ROOT_CWD_KUNIT_TEST` and `#include`d into
`fs/fs_struct.c`, following the `fs/tests/*_kunit.c` pattern.

Link: https://lore.kernel.org/all/a49ce818-f38d-41b0-bbf7-80b8aad998b1-fsFgPW9xikU7lZ9V/[email protected]/
Signed-off-by: John Ericson <mail-/[email protected]>
Assisted-by: Claude:claude-opus-4-8
---
 fs/Kconfig                     |  11 +++
 fs/fs_struct.c                 |   4 +
 fs/tests/null_root_cwd_kunit.c | 147 +++++++++++++++++++++++++++++++++
 3 files changed, 162 insertions(+)
 create mode 100644 fs/tests/null_root_cwd_kunit.c

diff --git a/fs/Kconfig b/fs/Kconfig
index cf6ae64776e6..9023597b6e2b 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -18,6 +18,17 @@ config VALIDATE_FS_PARSER
 config FS_IOMAP
 	bool
 
+config NULL_ROOT_CWD_KUNIT_TEST
+	bool "KUnit tests for tasks with a null root or cwd" if !KUNIT_ALL_TESTS
+	depends on KUNIT=y
+	default KUNIT_ALL_TESTS
+	help
+	  Build KUnit tests that exercise path resolution for tasks whose
+	  fs->root and/or fs->pwd is the NULL path (no root directory and/or
+	  no current working directory).
+
+	  If unsure, say N.
+
 # Stackable filesystems
 config FS_STACK
 	bool
diff --git a/fs/fs_struct.c b/fs/fs_struct.c
index 394875d06fd6..bf620bba7f35 100644
--- a/fs/fs_struct.c
+++ b/fs/fs_struct.c
@@ -153,3 +153,7 @@ struct fs_struct init_fs = {
 	.seq		= __SEQLOCK_UNLOCKED(init_fs.seq),
 	.umask		= 0022,
 };
+
+#ifdef CONFIG_NULL_ROOT_CWD_KUNIT_TEST
+#include "tests/null_root_cwd_kunit.c"
+#endif
diff --git a/fs/tests/null_root_cwd_kunit.c b/fs/tests/null_root_cwd_kunit.c
new file mode 100644
index 000000000000..3fb7e63545f8
--- /dev/null
+++ b/fs/tests/null_root_cwd_kunit.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for tasks whose fs->root and/or fs->pwd is the NULL path.
+ * See "fs: support tasks with a null root or cwd".
+ *
+ * Each test runs against this task's own fs_struct. We unshare it first
+ * so we only ever touch a private copy, never the shared one, and we
+ * restore the original root/cwd afterwards.
+ */
+#include <kunit/test.h>
+#include <linux/fs_struct.h>
+#include <linux/namei.h>
+#include <linux/path.h>
+
+/* The NULL path: { .mnt = NULL, .dentry = NULL }. */
+static const struct path null_path;
+
+struct null_fs_ctx {
+	struct path saved_root;
+	struct path saved_pwd;
+	struct path anchor;	/* a real directory, standing in for a dirfd */
+};
+
+static int null_fs_setup(struct null_fs_ctx *ctx)
+{
+	int err;
+
+	err = unshare_fs_struct();
+	if (err)
+		return err;
+	err = kern_path("/", LOOKUP_DIRECTORY, &ctx->anchor);
+	if (err)
+		return err;
+	get_fs_root(current->fs, &ctx->saved_root);
+	get_fs_pwd(current->fs, &ctx->saved_pwd);
+	return 0;
+}
+
+static void null_fs_teardown(struct null_fs_ctx *ctx)
+{
+	set_fs_root(current->fs, &ctx->saved_root);
+	set_fs_pwd(current->fs, &ctx->saved_pwd);
+	path_put(&ctx->saved_root);
+	path_put(&ctx->saved_pwd);
+	path_put(&ctx->anchor);
+}
+
+/* Resolve @name, drop any reference it returns, and yield the errno. */
+static int try_kern_path(const char *name)
+{
+	struct path out;
+	int err = kern_path(name, 0, &out);
+
+	if (!err)
+		path_put(&out);
+	return err;
+}
+
+static int try_fd_relative(struct null_fs_ctx *ctx, const char *name)
+{
+	struct path out;
+	int err = vfs_path_lookup(ctx->anchor.dentry, ctx->anchor.mnt,
+				  name, 0, &out);
+
+	if (!err)
+		path_put(&out);
+	return err;
+}
+
+/* No root: absolute paths fail, but ".." climbs and the cwd still works. */
+static void null_root_test(struct kunit *test)
+{
+	struct null_fs_ctx ctx;
+
+	KUNIT_ASSERT_EQ(test, null_fs_setup(&ctx), 0);
+	set_fs_root(current->fs, &null_path);
+
+	/* A leading '/' has nothing to anchor to. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("/"), -ENOENT);
+
+	/* ".." is unbounded rather than refused (it would have been
+	 * -ENOENT before this feature). It starts from the still-present
+	 * cwd and runs out of parents at the mount root.
+	 */
+	KUNIT_EXPECT_EQ(test, try_kern_path(".."), 0);
+
+	/* The cwd is untouched: AT_FDCWD-relative lookups still resolve. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("."), 0);
+
+	/* A dirfd-anchored lookup never consults fs->root. */
+	KUNIT_EXPECT_EQ(test, try_fd_relative(&ctx, "."), 0);
+
+	null_fs_teardown(&ctx);
+}
+
+/* No cwd: AT_FDCWD-relative paths fail, but absolute and dirfds work. */
+static void null_cwd_test(struct kunit *test)
+{
+	struct null_fs_ctx ctx;
+
+	KUNIT_ASSERT_EQ(test, null_fs_setup(&ctx), 0);
+	set_fs_pwd(current->fs, &null_path);
+
+	/* Relative-to-cwd lookups have no starting point. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("."), -ENOENT);
+	KUNIT_EXPECT_EQ(test, try_kern_path("foo"), -ENOENT);
+
+	/* The root is untouched: absolute lookups still resolve. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("/"), 0);
+
+	/* A dirfd-anchored lookup never consults fs->pwd. */
+	KUNIT_EXPECT_EQ(test, try_fd_relative(&ctx, "."), 0);
+
+	null_fs_teardown(&ctx);
+}
+
+/* Neither root nor cwd: only descriptor-relative lookups remain. */
+static void null_root_and_cwd_test(struct kunit *test)
+{
+	struct null_fs_ctx ctx;
+
+	KUNIT_ASSERT_EQ(test, null_fs_setup(&ctx), 0);
+	set_fs_root(current->fs, &null_path);
+	set_fs_pwd(current->fs, &null_path);
+
+	KUNIT_EXPECT_EQ(test, try_kern_path("/"), -ENOENT);
+	KUNIT_EXPECT_EQ(test, try_kern_path("."), -ENOENT);
+
+	/* The held descriptor still names files. */
+	KUNIT_EXPECT_EQ(test, try_fd_relative(&ctx, "."), 0);
+
+	null_fs_teardown(&ctx);
+}
+
+static struct kunit_case null_root_cwd_test_cases[] = {
+	KUNIT_CASE(null_root_test),
+	KUNIT_CASE(null_cwd_test),
+	KUNIT_CASE(null_root_and_cwd_test),
+	{},
+};
+
+static struct kunit_suite null_root_cwd_test_suite = {
+	.name = "null_root_cwd",
+	.test_cases = null_root_cwd_test_cases,
+};
+
+kunit_test_suite(null_root_cwd_test_suite);
-- 
2.51.2
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.