samples/configfs had nothing to link from, so symlink(2) went untested beyond the EPERM a subsystem without ->allow_link() returns. Cover creating and removing a link, the relative body configfs stores, the count ->allow_link() and ->drop_link() keep, the busy errors both ends return, and what get_target() rejects. Signed-off-by: Breno Leitao --- .../selftests/filesystems/configfs/configfs_test.c | 85 +++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/filesystems/configfs/configfs_test.c b/tools/testing/selftests/filesystems/configfs/configfs_test.c index 072d0dcf3f96b..9b15e1fd69e5b 100644 --- a/tools/testing/selftests/filesystems/configfs/configfs_test.c +++ b/tools/testing/selftests/filesystems/configfs/configfs_test.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Exercise the configfs userspace interface through the three subsystems + * Exercise the configfs userspace interface through the subsystems * registered by samples/configfs. * * Copyright (c) 2026 Meta Platforms, Inc. and affiliates @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -31,16 +32,24 @@ #define CHILDLESS "01-childless" #define SIMPLE "02-simple-children" #define GROUPS "03-group-children" +#define SYMLINKS "04-symlink-children" #define ITEM_A SIMPLE "/kselftest-a" #define ITEM_B SIMPLE "/kselftest-b" #define GROUP GROUPS "/kselftest-group" #define GROUP_ITEM GROUP "/kselftest-a" +#define LINK_SRC SYMLINKS "/kselftest-src" +#define LINK LINK_SRC "/kselftest-link" + +static const char * const test_links[] = { + LINK, +}; /* Deepest first, so one pass empties the tree. */ static const char * const test_dirs[] = { GROUP_ITEM, GROUP, + LINK_SRC, ITEM_A, ITEM_B, }; @@ -49,6 +58,10 @@ static void drop_test_dirs(void) { size_t i; + /* Links first: they hold both their source and their target. */ + for (i = 0; i < ARRAY_SIZE(test_links); i++) + unlink(test_links[i]); + for (i = 0; i < ARRAY_SIZE(test_dirs); i++) rmdir(test_dirs[i]); } @@ -131,7 +144,7 @@ FIXTURE_TEARDOWN(configfs) TEST_F(configfs, mount_and_subsystems) { - const char * const subsys[] = { CHILDLESS, SIMPLE, GROUPS }; + const char * const subsys[] = { CHILDLESS, SIMPLE, GROUPS, SYMLINKS }; struct statfs sfs; struct stat st; size_t i; @@ -324,6 +337,74 @@ TEST_F(configfs, symlink_without_allow_link) EXPECT_EQ(errno, EPERM); } +TEST_F(configfs, symlink_and_unlink) +{ + char buf[PATH_MAX]; + struct stat st; + ssize_t n; + + ASSERT_EQ(mkdir(ITEM_A, 0755), 0); + ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); + + ASSERT_EQ(symlink(ITEM_A, LINK), 0); + + /* configfs stores its own body, a path relative to the link. */ + n = readlink(LINK, buf, sizeof(buf) - 1); + ASSERT_GT(n, 0); + buf[n] = '\0'; + EXPECT_STREQ(buf, "../../" ITEM_A); + EXPECT_EQ(stat(LINK "/storeme", &st), 0); + + /* ->allow_link() ran on the source, not on the target. */ + ASSERT_GT(read_attr(LINK_SRC "/nlinks", buf, sizeof(buf)), 0); + EXPECT_STREQ(buf, "1\n"); + + ASSERT_EQ(unlink(LINK), 0); + ASSERT_GT(read_attr(LINK_SRC "/nlinks", buf, sizeof(buf)), 0); + EXPECT_STREQ(buf, "0\n"); +} + +TEST_F(configfs, symlink_pins_both_ends) +{ + ASSERT_EQ(mkdir(ITEM_A, 0755), 0); + ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); + ASSERT_EQ(symlink(ITEM_A, LINK), 0); + + /* A linked item cannot go away under the link. */ + ASSERT_EQ(rmdir(ITEM_A), -1); + EXPECT_EQ(errno, EBUSY); + + /* The link counts as a child of its source. */ + ASSERT_EQ(rmdir(LINK_SRC), -1); + EXPECT_EQ(errno, ENOTEMPTY); + + ASSERT_EQ(unlink(LINK), 0); + EXPECT_EQ(rmdir(ITEM_A), 0); +} + +TEST_F(configfs, symlink_target_outside_configfs) +{ + ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); + ASSERT_EQ(symlink("/", LINK), -1); + EXPECT_EQ(errno, EPERM); +} + +TEST_F(configfs, symlink_target_missing) +{ + ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); + ASSERT_EQ(symlink(SIMPLE "/kselftest-gone", LINK), -1); + EXPECT_EQ(errno, ENOENT); +} + +TEST_F(configfs, symlink_target_is_an_attribute) +{ + ASSERT_EQ(mkdir(LINK_SRC, 0755), 0); + + /* The target is resolved with LOOKUP_DIRECTORY. */ + ASSERT_EQ(symlink(CHILDLESS "/storeme", LINK), -1); + EXPECT_EQ(errno, ENOTDIR); +} + TEST_F(configfs, module_pinned_by_item) { ASSERT_EQ(mkdir(ITEM_A, 0755), 0); -- 2.53.0-Meta