test: add test cases for path_is_root_at()

Prompted by PR #29842 and issue #29559.
This commit is contained in:
Yu Watanabe
2025-08-19 22:48:08 +09:00
parent e1f3d790f3
commit d486ae5adf
+128
View File
@@ -680,6 +680,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;