diff --git a/src/basic/chase.c b/src/basic/chase.c index 969f88afd29..323e4623754 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -128,6 +128,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ bool exists = true, append_trail_slash = false; struct stat st; /* stat obtained from fd */ + bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; int r; @@ -220,6 +221,33 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int _cleanup_close_ int _dir_fd = -EBADF; if (dir_fd == XAT_FDROOT) { + + /* Shortcut the common case where no root dir is specified, and no special flags are given to + * a regular open() */ + if (!ret_path && + (flags & (CHASE_STEP|CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_SAFE|CHASE_WARN|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { + _cleanup_free_ char *slash_path = NULL; + + if (!path_is_absolute(path)) { + slash_path = strjoin("/", path); + if (!slash_path) + return -ENOMEM; + } + + /* We use open_tree() rather than regular open() here, because it gives us direct + * control over automount behaviour, and otherwise is equivalent to open() with + * O_PATH */ + fd = open_tree(-EBADF, slash_path ?: path, OPEN_TREE_CLOEXEC|(FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? 0 : AT_NO_AUTOMOUNT)); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + exists = true; + goto success; + } + _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC); if (_dir_fd < 0) return -errno; @@ -259,7 +287,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (r < 0) return r; - bool need_absolute = r; + need_absolute = r; if (need_absolute) { done = strdup("/"); if (!done) @@ -522,6 +550,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int close_and_replace(fd, child); } +success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { r = stat_verify_directory(&st); diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 2dc554ebd18..129ea19b723 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -500,15 +500,34 @@ TEST(chaseat) { fd = safe_close(fd); /* Same but with XAT_FDROOT */ - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, NULL)); + _cleanup_close_ int found_fd1 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, &found_fd1)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + _cleanup_close_ int found_fd2 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, &found_fd2)); ASSERT_STREQ(result, "/usr"); result = mfree(result); + assert(fd_inode_same(found_fd1, found_fd2) > 0); - fd = safe_close(fd); + /* Do the same XAT_FDROOT tests again, this time without querying the path, so that the open_tree() + * shortcut can work */ + _cleanup_close_ int found_fd3 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, 0, NULL, &found_fd3)); + assert(fd_inode_same(found_fd1, found_fd3) > 0); + assert(fd_inode_same(found_fd2, found_fd3) > 0); + + _cleanup_close_ int found_fd4 = -EBADF; + ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, NULL, &found_fd4)); + assert(fd_inode_same(found_fd1, found_fd4) > 0); + assert(fd_inode_same(found_fd2, found_fd4) > 0); + assert(fd_inode_same(found_fd3, found_fd4) > 0); + + found_fd1 = safe_close(found_fd1); + found_fd2 = safe_close(found_fd2); + found_fd3 = safe_close(found_fd3); + found_fd4 = safe_close(found_fd4); /* If the file descriptor does not point to the root directory, the result will be relative * unless the result is outside of the specified file descriptor. */