* [PATCH] selftests: cover kernfs dentry revalidation
@ 2026-08-29 2:28 Shakeel Butt
0 siblings, 0 replies; only message in thread
From: Shakeel Butt @ 2026-08-29 2:28 UTC (permalink / raw)
To: Greg Kroah-Hartman, Tejun Heo, Christian Brauner
Cc: Meta kernel team, linux-kselftest, driver-core, linux-kernel
kernfs_test only exercised two xattr calls, so nothing covered the part
of kernfs most exposed to userspace: the dentry cache.
kernfs nodes appear and disappear from kernel contexts with no VFS
operation driving them, and removal cannot reliably unhash the cached
dentries, so ->d_revalidate() is the correctness backstop. It had no
test at all.
Add tests that drive kernfs from kernel context rather than through VFS
create/unlink:
- writing cgroup.subtree_control makes the kernel add and remove files
in every child cgroup, exercising both the negative-dentry revision
check and the deactivation check,
- renaming a network interface renames the sysfs node underneath an
already cached dentry,
- sysfs in a fresh network namespace must show only that namespace's
interfaces, covering the KERNFS_NS tagging paths,
- lookup hammered against concurrent mkdir/rmdir must only ever see
success or an errno meaning "it went away",
- removal-while-open, readdir duplicate detection and a
seekdir()/telldir() round trip over kernfs_dir_pos()'s hash cookie.
Also the other direction: walking already cached dentries must not
invalidate them. That is not merely a lost optimisation --
d_invalidate() calls detach_mounts(), so a revalidation that wrongly
fails silently tears down any mount underneath the directory.
Both filesystems are needed: cgroup2 has no ->rename and is not
namespace tagged, and sysfs cannot be mounted with a controllable set of
children. The config fragment keeps a kernel built via kselftest-merge
from reporting a pass while running almost nothing.
Verified the suite reacts to a broken backstop rather than passing:
stubbing kernfs_dir_changed() to return false turns 8 of the 11 tests
red.
Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
---
MAINTAINERS | 1 +
tools/testing/selftests/filesystems/config | 6 +
.../selftests/filesystems/kernfs_test.c | 657 +++++++++++++++++-
3 files changed, 663 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/filesystems/config
diff --git a/MAINTAINERS b/MAINTAINERS
index 549df316f487..605fd2d47788 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14392,6 +14392,7 @@ S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git
F: fs/kernfs/
F: include/linux/kernfs.h
+F: tools/testing/selftests/filesystems/kernfs_test.c
KEXEC
M: Andrew Morton <akpm@linux-foundation.org>
diff --git a/tools/testing/selftests/filesystems/config b/tools/testing/selftests/filesystems/config
new file mode 100644
index 000000000000..6e33f4188af4
--- /dev/null
+++ b/tools/testing/selftests/filesystems/config
@@ -0,0 +1,6 @@
+CONFIG_CGROUPS=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_NET=y
+CONFIG_NET_NS=y
+CONFIG_INET=y
+CONFIG_SYSFS=y
diff --git a/tools/testing/selftests/filesystems/kernfs_test.c b/tools/testing/selftests/filesystems/kernfs_test.c
index 84c2b910a60d..5c9677682d13 100644
--- a/tools/testing/selftests/filesystems/kernfs_test.c
+++ b/tools/testing/selftests/filesystems/kernfs_test.c
@@ -2,8 +2,20 @@
#define _GNU_SOURCE
#define __SANE_USERSPACE_TYPES__
+#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
+#include <net/if.h>
+#include <sched.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/xattr.h>
@@ -34,5 +46,648 @@ TEST(kernfs_getxattr)
EXPECT_EQ(close(fd), 0);
}
-TEST_HARNESS_MAIN
+/*
+ * Exercise the kernfs dentry cache: lookup, revalidation of positive and
+ * negative dentries, readdir and namespace tagging.
+ *
+ * These drive kernfs from kernel context rather than VFS create/unlink,
+ * which is what ->d_revalidate() exists for: writing cgroup.subtree_control
+ * adds and removes files in every child cgroup with no VFS operation
+ * touching those names.
+ */
+
+#define CG_SCRATCH "kernfs_selftest"
+#define TEST_IFNAME "kfstest0"
+
+/* Controllers that add a predictable file to each child cgroup. */
+static const struct {
+ const char *name;
+ const char *probe_file;
+} controllers[] = {
+ { "memory", "memory.current" },
+ { "pids", "pids.current" },
+ { "cpu", "cpu.stat" },
+};
+
+static int find_cgroup2_root(char *buf, size_t len)
+{
+ char line[PATH_MAX * 2];
+ FILE *f;
+ int ret = -1;
+
+ f = fopen("/proc/self/mounts", "re");
+ if (!f)
+ return -1;
+
+ while (fgets(line, sizeof(line), f)) {
+ char mnt[PATH_MAX], type[64];
+
+ /* Octal escaping can expand a path fourfold; bound both %s. */
+ if (sscanf(line, "%*s %4095s %63s", mnt, type) != 2)
+ continue;
+ if (strcmp(type, "cgroup2"))
+ continue;
+ if (strlen(mnt) >= len)
+ break;
+ strcpy(buf, mnt);
+ ret = 0;
+ break;
+ }
+
+ fclose(f);
+ return ret;
+}
+
+static int write_file(const char *path, const char *val)
+{
+ ssize_t len = strlen(val);
+ int fd, ret;
+
+ fd = open(path, O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -1;
+ ret = write(fd, val, len) == len ? 0 : -1;
+ close(fd);
+ return ret;
+}
+
+static bool file_has_word(const char *path, const char *word)
+{
+ char buf[4096], *tok, *save;
+ bool found = false;
+ ssize_t n;
+ int fd;
+
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return false;
+ n = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (n < 0)
+ return false;
+ buf[n] = '\0';
+
+ for (tok = strtok_r(buf, "\n ", &save); tok;
+ tok = strtok_r(NULL, "\n ", &save)) {
+ if (!strcmp(tok, word)) {
+ found = true;
+ break;
+ }
+ }
+ return found;
+}
+
+static bool path_is_mounted(const char *path)
+{
+ char line[PATH_MAX * 2];
+ bool found = false;
+ FILE *f;
+
+ f = fopen("/proc/self/mounts", "re");
+ if (!f)
+ return false;
+ while (fgets(line, sizeof(line), f)) {
+ char mnt[PATH_MAX];
+
+ if (sscanf(line, "%*s %4095s", mnt) != 1)
+ continue;
+ if (!strcmp(mnt, path)) {
+ found = true;
+ break;
+ }
+ }
+ fclose(f);
+ return found;
+}
+
+/* Shared by the stress tests below. */
+static bool stress_deadline(const struct timespec *end)
+{
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_sec > end->tv_sec ||
+ (now.tv_sec == end->tv_sec && now.tv_nsec >= end->tv_nsec);
+}
+
+FIXTURE(kernfs_cgroup)
+{
+ char scratch[PATH_MAX]; /* <cg2>/kernfs_selftest.<pid> */
+ char child[PATH_MAX]; /* <scratch>/child */
+ char probe[PATH_MAX]; /* child's controller file */
+ char scratch_sc[PATH_MAX]; /* scratch's cgroup.subtree_control */
+ char root_sc[PATH_MAX]; /* root's cgroup.subtree_control */
+ char enable[32]; /* "+<controller>" */
+ char disable[32]; /* "-<controller>" */
+ char mnt[PATH_MAX]; /* our own mount, if we made one */
+ bool mounted;
+ bool enabled_at_root;
+};
+
+/* A cgroup stays busy briefly after its last task exits. */
+static void rmdir_retry(const char *path)
+{
+ int i;
+
+ for (i = 0; i < 500; i++) {
+ if (!rmdir(path) || errno != EBUSY)
+ return;
+ usleep(10000);
+ }
+}
+
+/*
+ * Undo whatever SETUP managed to do. The harness skips TEARDOWN after a
+ * failed or skipped SETUP, so SETUP must call this before returning early.
+ */
+static void kernfs_cgroup_undo(FIXTURE_DATA(kernfs_cgroup) *self)
+{
+ rmdir_retry(self->child);
+ rmdir(self->scratch);
+ if (self->enabled_at_root)
+ write_file(self->root_sc, self->disable);
+ if (self->mounted) {
+ umount2(self->mnt, MNT_DETACH);
+ rmdir(self->mnt);
+ }
+ self->enabled_at_root = false;
+ self->mounted = false;
+}
+
+FIXTURE_SETUP(kernfs_cgroup)
+{
+ char root[PATH_MAX], ctl[PATH_MAX];
+ const char *probe_file = NULL;
+ size_t i;
+
+ if (geteuid())
+ SKIP(return, "test needs to run as root");
+
+ /*
+ * A private mount namespace stops our mounts leaking, but does not
+ * isolate the cgroup hierarchy: cgroup2 has one default hierarchy
+ * however many times it is mounted. The scratch cgroups live in the
+ * host's and must be removed, not discarded with the namespace.
+ */
+ if (unshare(CLONE_NEWNS))
+ SKIP(return, "unshare(CLONE_NEWNS): %s", strerror(errno));
+ if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
+ SKIP(return, "make / private: %s", strerror(errno));
+
+ /* Use an existing cgroup2 mount if there is one, else make our own. */
+ if (find_cgroup2_root(root, sizeof(root))) {
+ strcpy(self->mnt, "/tmp/kernfs_selftest_cg2.XXXXXX");
+ if (!mkdtemp(self->mnt))
+ SKIP(return, "mkdtemp: %s", strerror(errno));
+ if (mount("none", self->mnt, "cgroup2", 0, NULL)) {
+ rmdir(self->mnt);
+ SKIP(return, "mount cgroup2: %s", strerror(errno));
+ }
+ self->mounted = true;
+ strcpy(root, self->mnt);
+ }
+
+ snprintf(self->root_sc, sizeof(self->root_sc),
+ "%s/cgroup.subtree_control", root);
+ snprintf(ctl, sizeof(ctl), "%s/cgroup.controllers", root);
+
+ /* Named after our pid so we cannot collide with anything else. */
+ snprintf(self->scratch, sizeof(self->scratch), "%s/%s.%d", root,
+ CG_SCRATCH, getpid());
+ snprintf(self->child, sizeof(self->child), "%s/child", self->scratch);
+ snprintf(self->scratch_sc, sizeof(self->scratch_sc),
+ "%s/cgroup.subtree_control", self->scratch);
+
+ for (i = 0; i < ARRAY_SIZE(controllers); i++) {
+ if (!file_has_word(ctl, controllers[i].name))
+ continue;
+ snprintf(self->enable, sizeof(self->enable), "+%s",
+ controllers[i].name);
+ snprintf(self->disable, sizeof(self->disable), "-%s",
+ controllers[i].name);
+ probe_file = controllers[i].probe_file;
+
+ /*
+ * A controller must be in the root's subtree_control before
+ * it appears in our scratch cgroup. Note if we enabled it,
+ * so it can be put back.
+ */
+ self->enabled_at_root = !file_has_word(self->root_sc,
+ controllers[i].name);
+ if (self->enabled_at_root &&
+ write_file(self->root_sc, self->enable)) {
+ self->enabled_at_root = false;
+ probe_file = NULL;
+ continue;
+ }
+ break;
+ }
+ if (!probe_file) {
+ kernfs_cgroup_undo(self);
+ SKIP(return, "no usable cgroup2 controller");
+ }
+
+ snprintf(self->probe, sizeof(self->probe), "%s/%s", self->child,
+ probe_file);
+
+ /*
+ * Only an unusable environment may skip. A scratch cgroup named
+ * after our own pid should always be creatable, so failing to make
+ * one is a result -- skipping would let a broken kernel look green.
+ */
+ if (mkdir(self->scratch, 0755)) {
+ int err = errno;
+
+ kernfs_cgroup_undo(self);
+ if (err == EROFS || err == EACCES || err == EPERM)
+ SKIP(return, "mkdir %s: %s", self->scratch,
+ strerror(err));
+ ASSERT_EQ(err, 0) TH_LOG("mkdir %s: %s", self->scratch,
+ strerror(err));
+ }
+ if (mkdir(self->child, 0755)) {
+ int err = errno;
+
+ kernfs_cgroup_undo(self);
+ ASSERT_EQ(err, 0) TH_LOG("mkdir %s: %s", self->child,
+ strerror(err));
+ }
+}
+
+FIXTURE_TEARDOWN(kernfs_cgroup)
+{
+ write_file(self->scratch_sc, self->disable);
+ kernfs_cgroup_undo(self);
+}
+
+/*
+ * Walking already-cached dentries must not invalidate them. Spurious
+ * invalidation is not merely slow: d_invalidate() calls detach_mounts(), so
+ * an unrelated lookup would silently tear down any mount below.
+ */
+TEST_F(kernfs_cgroup, path_walk_does_not_invalidate)
+{
+ char src[] = "/tmp/kernfs_selftest_bind.XXXXXX";
+ char sub[PATH_MAX], probe[PATH_MAX];
+ int i;
+
+ snprintf(sub, sizeof(sub), "%s/sub", self->child);
+ ASSERT_EQ(mkdir(sub, 0755), 0);
+
+ if (!mkdtemp(src)) {
+ rmdir(sub);
+ SKIP(return, "mkdtemp: %s", strerror(errno));
+ }
+ if (mount(src, sub, NULL, MS_BIND, NULL)) {
+ int err = errno;
+
+ rmdir(sub);
+ rmdir(src);
+ SKIP(return, "bind mount onto a cgroup dir: %s", strerror(err));
+ }
+ ASSERT_TRUE(path_is_mounted(sub));
+
+ /* Walk a sibling path through the same directory, repeatedly. */
+ snprintf(probe, sizeof(probe), "%s/cgroup.procs", self->child);
+ for (i = 0; i < 8; i++) {
+ int fd = open(probe, O_RDONLY | O_CLOEXEC);
+
+ if (fd >= 0)
+ close(fd);
+ }
+
+ EXPECT_TRUE(path_is_mounted(sub));
+
+ umount2(sub, MNT_DETACH);
+ rmdir(sub);
+ rmdir(src);
+}
+
+/*
+ * A cached negative dentry must be invalidated when the kernel creates the
+ * name behind the dcache's back. That is what kernfs_dir_changed() and
+ * kernfs_elem_dir::rev are for.
+ */
+TEST_F(kernfs_cgroup, negative_dentry_invalidated_by_kernel_create)
+{
+ struct stat st;
+
+ /* Caches a negative dentry for the probe file. */
+ ASSERT_EQ(stat(self->probe, &st), -1);
+ ASSERT_EQ(errno, ENOENT);
+
+ /* The kernel now creates it, with no VFS operation on that name. */
+ ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0);
+
+ EXPECT_EQ(stat(self->probe, &st), 0);
+}
+
+/* The mirror image: a cached positive dentry must go when the node does. */
+TEST_F(kernfs_cgroup, positive_dentry_invalidated_by_kernel_remove)
+{
+ struct stat st;
+
+ ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0);
+ /* Caches a positive dentry. */
+ ASSERT_EQ(stat(self->probe, &st), 0);
+ ASSERT_EQ(write_file(self->scratch_sc, self->disable), 0);
+
+ ASSERT_EQ(stat(self->probe, &st), -1);
+ EXPECT_EQ(errno, ENOENT);
+}
+
+/* Opening a removed node fails; it never returns stale content. */
+TEST_F(kernfs_cgroup, open_after_rmdir_fails)
+{
+ char path[PATH_MAX];
+ char buf[64];
+ int fd;
+
+ snprintf(path, sizeof(path), "%s/cgroup.procs", self->child);
+
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+
+ ASSERT_EQ(rmdir(self->child), 0);
+
+ /* Lookup by path must fail. */
+ EXPECT_EQ(open(path, O_RDONLY | O_CLOEXEC), -1);
+ EXPECT_EQ(errno, ENOENT);
+
+ /*
+ * An fd held across removal must fail cleanly rather than hang or
+ * return garbage. Empty read or error, both fine.
+ */
+ if (read(fd, buf, sizeof(buf)) < 0)
+ EXPECT_EQ(errno, ENODEV);
+ EXPECT_EQ(close(fd), 0);
+
+ ASSERT_EQ(mkdir(self->child, 0755), 0);
+}
+
+/* readdir returns every entry exactly once. */
+TEST_F(kernfs_cgroup, readdir_no_duplicates)
+{
+ char names[512][NAME_MAX + 1];
+ struct dirent *de;
+ int n = 0, i, j;
+ DIR *d;
+
+ ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0);
+
+ d = opendir(self->child);
+ ASSERT_NE(d, NULL);
+ while ((de = readdir(d))) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+ continue;
+ ASSERT_LT(n, (int)ARRAY_SIZE(names));
+ strncpy(names[n], de->d_name, NAME_MAX);
+ names[n][NAME_MAX] = '\0';
+ n++;
+ }
+ closedir(d);
+
+ ASSERT_GT(n, 0);
+ for (i = 0; i < n; i++)
+ for (j = i + 1; j < n; j++)
+ EXPECT_STRNE(names[i], names[j]);
+}
+
+/*
+ * A telldir() cookie must resolve back to the same entry after seekdir().
+ * kernfs encodes the cookie as the node's name hash, so this covers
+ * kernfs_dir_pos() as well as plain iteration.
+ */
+TEST_F(kernfs_cgroup, readdir_seekdir_roundtrip)
+{
+ char names[512][NAME_MAX + 1];
+ struct dirent *de;
+ long pos[512];
+ int n = 0, i;
+ DIR *d;
+
+ ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0);
+
+ d = opendir(self->child);
+ ASSERT_NE(d, NULL);
+
+ /* Record the cookie *before* reading each entry, with its name. */
+ while (1) {
+ long here = telldir(d);
+
+ de = readdir(d);
+ if (!de)
+ break;
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+ continue;
+ ASSERT_LT(n, (int)ARRAY_SIZE(pos));
+ pos[n] = here;
+ strncpy(names[n], de->d_name, NAME_MAX);
+ names[n][NAME_MAX] = '\0';
+ n++;
+ }
+ ASSERT_GT(n, 0);
+
+ /* Seeking back to a cookie must land on the entry it was taken at. */
+ for (i = 0; i < n; i++) {
+ seekdir(d, pos[i]);
+ de = readdir(d);
+ ASSERT_NE(de, NULL);
+ EXPECT_STREQ(de->d_name, names[i]);
+ }
+
+ closedir(d);
+}
+
+#define STRESS_SECS 2
+#define STRESS_DIRS 4
+#define STRESS_READERS 4
+
+/*
+ * Hammer lookup against creation and removal. Revalidation holds no lock
+ * against the writers, so what makes it safe is that every answer it can
+ * give is one the caller already handles: a reader must only ever see
+ * success or an errno meaning "it went away", never garbage or a hang.
+ */
+TEST_F(kernfs_cgroup, lookup_vs_create_remove_stress)
+{
+ pid_t pids[STRESS_DIRS + STRESS_READERS];
+ struct timespec end;
+ int i, status, n = 0;
+
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ end.tv_sec += STRESS_SECS;
+
+ for (i = 0; i < STRESS_DIRS; i++) {
+ pid_t pid = fork();
+
+ ASSERT_GE(pid, 0);
+ if (pid == 0) {
+ char dir[PATH_MAX];
+
+ snprintf(dir, sizeof(dir), "%s/s%d", self->scratch, i);
+ while (!stress_deadline(&end)) {
+ if (mkdir(dir, 0755) && errno != EEXIST)
+ _exit(10);
+ if (rmdir(dir) && errno != ENOENT &&
+ errno != EBUSY)
+ _exit(11);
+ }
+ _exit(0);
+ }
+ pids[n++] = pid;
+ }
+
+ for (i = 0; i < STRESS_READERS; i++) {
+ pid_t pid = fork();
+
+ ASSERT_GE(pid, 0);
+ if (pid == 0) {
+ /* Start each reader on a different directory. */
+ unsigned int seq = i;
+
+ while (!stress_deadline(&end)) {
+ int which = seq++ % STRESS_DIRS;
+ char path[PATH_MAX];
+ struct stat st;
+ int fd;
+
+ snprintf(path, sizeof(path),
+ "%s/s%d/cgroup.procs",
+ self->scratch, which);
+
+ if (stat(path, &st) && errno != ENOENT &&
+ errno != ENODEV)
+ _exit(20);
+
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ if (errno != ENOENT && errno != ENODEV)
+ _exit(21);
+ } else {
+ close(fd);
+ }
+
+ if (access(path, F_OK) && errno != ENOENT &&
+ errno != ENODEV)
+ _exit(22);
+ }
+ _exit(0);
+ }
+ pids[n++] = pid;
+ }
+
+ for (i = 0; i < n; i++) {
+ ASSERT_EQ(waitpid(pids[i], &status, 0), pids[i]);
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(WEXITSTATUS(status), 0);
+ }
+
+ for (i = 0; i < STRESS_DIRS; i++) {
+ char dir[PATH_MAX];
+
+ snprintf(dir, sizeof(dir), "%s/s%d", self->scratch, i);
+ rmdir_retry(dir);
+ }
+}
+
+/*
+ * sysfs is namespace tagged (KERNFS_NS) and supports rename; cgroup2 does
+ * neither. Run in a private netns with its own sysfs so the host is
+ * untouched.
+ */
+FIXTURE(kernfs_netns)
+{
+ char mnt[PATH_MAX];
+ char net[PATH_MAX];
+ bool mounted;
+};
+
+FIXTURE_SETUP(kernfs_netns)
+{
+ if (geteuid())
+ SKIP(return, "test needs to run as root");
+
+ if (unshare(CLONE_NEWNS | CLONE_NEWNET))
+ SKIP(return, "unshare(CLONE_NEWNS|CLONE_NEWNET): %s",
+ strerror(errno));
+
+ /* Don't let our sysfs mount escape into the parent namespace. */
+ ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+ strcpy(self->mnt, "/tmp/kernfs_selftest_sysfs.XXXXXX");
+ ASSERT_NE(mkdtemp(self->mnt), NULL);
+
+ if (mount("none", self->mnt, "sysfs", 0, NULL))
+ SKIP(return, "mount sysfs: %s", strerror(errno));
+ self->mounted = true;
+
+ snprintf(self->net, sizeof(self->net), "%s/class/net", self->mnt);
+}
+
+FIXTURE_TEARDOWN(kernfs_netns)
+{
+ if (self->mounted)
+ umount2(self->mnt, MNT_DETACH);
+ rmdir(self->mnt);
+}
+
+/*
+ * sysfs in a new network namespace must show only that namespace's
+ * interfaces. A fresh netns has exactly one, "lo".
+ */
+TEST_F(kernfs_netns, ns_tag_isolates_class_net)
+{
+ struct dirent *de;
+ int n = 0;
+ DIR *d;
+
+ d = opendir(self->net);
+ ASSERT_NE(d, NULL);
+ while ((de = readdir(d))) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+ continue;
+ EXPECT_STREQ(de->d_name, "lo");
+ n++;
+ }
+ closedir(d);
+
+ EXPECT_EQ(n, 1);
+}
+
+/*
+ * After a rename the old name must stop resolving and the new one must
+ * start, even though both dentries are already cached.
+ */
+TEST_F(kernfs_netns, rename_is_revalidated)
+{
+ char old_path[PATH_MAX], new_path[PATH_MAX];
+ struct ifreq ifr = {};
+ struct stat st;
+ int sk;
+
+ snprintf(old_path, sizeof(old_path), "%s/lo", self->net);
+ snprintf(new_path, sizeof(new_path), "%s/%s", self->net, TEST_IFNAME);
+
+ /* Warm both dentries: one positive, one negative. */
+ ASSERT_EQ(stat(old_path, &st), 0);
+ ASSERT_EQ(stat(new_path, &st), -1);
+ ASSERT_EQ(errno, ENOENT);
+
+ sk = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ ASSERT_GE(sk, 0);
+ strcpy(ifr.ifr_name, "lo");
+ strcpy(ifr.ifr_newname, TEST_IFNAME);
+ if (ioctl(sk, SIOCSIFNAME, &ifr)) {
+ close(sk);
+ SKIP(return, "SIOCSIFNAME: %s", strerror(errno));
+ }
+ close(sk);
+
+ EXPECT_EQ(stat(old_path, &st), -1);
+ EXPECT_EQ(errno, ENOENT);
+ EXPECT_EQ(stat(new_path, &st), 0);
+}
+
+TEST_HARNESS_MAIN
--
2.53.0-Meta
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-29 2:28 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-29 2:28 [PATCH] selftests: cover kernfs dentry revalidation Shakeel Butt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®