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 <[email protected]>
---
 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 <[email protected]>
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


Reply via email to