]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test: add test cases for path_is_root_at()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 19 Aug 2025 13:46:04 +0000 (22:46 +0900)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 13 Oct 2025 08:04:47 +0000 (10:04 +0200)
Prompted by PR #29842 and issue #29559.

(cherry picked from commit d486ae5adfc198915caa466b4fe2a34b53cb47f4)

src/test/test-fd-util.c

index a359efa0525c67c47ba48aa5983fe33d8a9eee23..1fd48c01742f61d2e93c1e0662d1ea5d02ca2c9f 100644 (file)
@@ -681,6 +681,134 @@ TEST(dir_fd_is_root) {
         assert_se(dir_fd_is_root_or_cwd(fd) == 0);
 }
 
+static void test_path_is_root_at_one(bool expected) {
+        ASSERT_OK_POSITIVE(path_is_root("/"));
+        ASSERT_OK_POSITIVE(path_is_root("/."));
+        ASSERT_OK_EQ(path_is_root("/./.."), expected);
+        ASSERT_OK_EQ(path_is_root("/.."), expected);
+        ASSERT_OK_EQ(path_is_root("/../"), expected);
+        ASSERT_OK_EQ(path_is_root("/../."), expected);
+        ASSERT_OK_EQ(path_is_root("/../.."), expected);
+
+        ASSERT_OK_ZERO(path_is_root("/usr"));
+        ASSERT_OK_ZERO(path_is_root("/./usr"));
+        ASSERT_OK_ZERO(path_is_root("/../usr"));
+        ASSERT_OK_ZERO(path_is_root("/.././usr"));
+        ASSERT_OK_ZERO(path_is_root("/../../usr"));
+
+        _cleanup_close_ int fd = -EBADF;
+        ASSERT_OK_ERRNO(fd = open("/", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW));
+
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, NULL));
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, ""));
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, "."));
+        ASSERT_OK_EQ(path_is_root_at(fd, "./../"), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "../"), expected);
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, "/"));
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, "/."));
+        ASSERT_OK_EQ(path_is_root_at(fd, "/./.."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/.."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/../"), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/../."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/../.."), expected);
+
+        ASSERT_OK_ZERO(path_is_root_at(fd, "usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "./usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "../usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/./usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/../usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/.././usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/../../usr"));
+
+        safe_close(fd);
+        ASSERT_OK_ERRNO(fd = open("/../", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW));
+
+        ASSERT_OK_EQ(path_is_root_at(fd, NULL), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, ""), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "./.."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "../"), expected);
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, "/"));
+        ASSERT_OK_POSITIVE(path_is_root_at(fd, "/."));
+        ASSERT_OK_EQ(path_is_root_at(fd, "/./.."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/.."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/../"), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/../."), expected);
+        ASSERT_OK_EQ(path_is_root_at(fd, "/../.."), expected);
+
+        ASSERT_OK_ZERO(path_is_root_at(fd, "usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "./usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "../usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/./usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/../usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/.././usr"));
+        ASSERT_OK_ZERO(path_is_root_at(fd, "/../../usr"));
+}
+
+TEST(path_is_root_at) {
+        int r;
+
+        test_path_is_root_at_one(true);
+
+        r = detach_mount_namespace();
+        if (r < 0)
+                return (void) log_tests_skipped_errno(r, "Failed to detach mount namespace");
+
+        /* Interestingly, even after bind mount a path on "/", still "/" points to the previous root
+         * directory, but "/../" points to the new root directory. Hence, path_is_root("/") is true but
+         * path_is_root("/../") is false. Such spurious situation is resolved after chroot()ing to the new
+         * root directory. */
+        ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "/", "/", NULL, MS_BIND|MS_REC, NULL));
+        log_debug("/* %s: bind mount(\"/\", \"/\") */", __func__);
+        test_path_is_root_at_one(false);
+
+        /* chroot("/") does not change anything. */
+        ASSERT_OK_ERRNO(chroot("/"));
+        log_debug("/* %s: chroot(\"/\") */", __func__);
+        test_path_is_root_at_one(false);
+
+        /* chdir("/") neither change anything. */
+        ASSERT_OK_ERRNO(chdir("/"));
+        log_debug("/* %s: chdir(\"/\") */", __func__);
+        test_path_is_root_at_one(false);
+
+        /* chdir("/../") neither change anything. */
+        ASSERT_OK_ERRNO(chdir("/../"));
+        log_debug("/* %s: chdir(\"/../\") */", __func__);
+        test_path_is_root_at_one(false);
+
+        /* After chroot("/../"), both "/" and "/../" point to the new root directory. */
+        ASSERT_OK_ERRNO(chroot("/../"));
+        log_debug("/* %s: chroot(\"/../\") */", __func__);
+        test_path_is_root_at_one(true);
+
+        /* chdir("/../") does not change anything. */
+        ASSERT_OK_ERRNO(chdir("/../"));
+        log_debug("/* %s: chdir(\"/../\") again */", __func__);
+        test_path_is_root_at_one(true);
+
+        /* bind mounting to non-root directory has no problem, of course. */
+        _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL;
+        ASSERT_OK(mkdtemp_malloc("/tmp/test-path_is_root-XXXXXX", &tmp));
+        ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "/", tmp, NULL, MS_BIND|MS_REC, NULL));
+        log_debug("/* %s: bind mount(\"/\", \"%s\") */", __func__, tmp);
+        test_path_is_root_at_one(true);
+
+        ASSERT_OK_ERRNO(chdir(tmp));
+        log_debug("/* %s: chdir(\"%s\") */", __func__, tmp);
+        test_path_is_root_at_one(true);
+
+        ASSERT_OK_ERRNO(chroot(tmp));
+        log_debug("/* %s: chroot(\"%s\") */", __func__, tmp);
+        test_path_is_root_at_one(true);
+
+        ASSERT_OK_ERRNO(chdir(tmp));
+        log_debug("/* %s: chdir(\"%s\") again */", __func__, tmp);
+        test_path_is_root_at_one(true);
+}
+
 TEST(fds_are_same_mount) {
         _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF, fd3 = -EBADF, fd4 = -EBADF;