From 63bfd52f48a772c86ff84e3a1ba1fcd19e0008f8 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 14 Mar 2023 18:37:55 +0100 Subject: [PATCH 1/3] chase-symlinks: Add CHASE_EXTRACT_FILENAME flag Useful in combination with CHASE_PARENT to get a directory file descriptor to the parent directory of the resolved path and the filename of the resolved path in the parent directory (which might not be the same as the filename of the input path because of symlinks). --- src/basic/chase-symlinks.c | 40 ++++++++++++++++++++++++++++---------- src/basic/chase-symlinks.h | 1 + src/test/test-fs-util.c | 22 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 5c2b56ea20..16d36693b1 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -89,6 +89,8 @@ int chase_symlinks_at( assert(path); assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); + assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); + assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME)); assert(dir_fd >= 0 || dir_fd == AT_FDCWD); /* Either the file may be missing, or we return an fd to the final object, but both make no sense */ @@ -98,6 +100,9 @@ int chase_symlinks_at( if ((flags & CHASE_STEP)) assert(!ret_fd); + if ((flags & CHASE_EXTRACT_FILENAME)) + assert(ret_path); + if (isempty(path)) path = "."; @@ -403,6 +408,17 @@ int chase_symlinks_at( } if (ret_path) { + if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) { + _cleanup_free_ char *f = NULL; + + r = path_extract_filename(done, &f); + if (r < 0 && r != -EDESTADDRREQ) + return r; + + /* If we get EDESTADDRREQ we clear done and it will get reinitialized by the next block. */ + free_and_replace(done, f); + } + if (!done) { done = strdup(append_trail_slash ? "./" : "."); if (!done) @@ -520,19 +536,23 @@ int chase_symlinks( return r; if (ret_path) { - _cleanup_free_ char *q = NULL; + if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) { + _cleanup_free_ char *q = NULL; - q = path_join(empty_to_root(root), p); - if (!q) - return -ENOMEM; - - path_simplify(q); - - if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/.")) - if (!strextend(&q, "/")) + q = path_join(empty_to_root(root), p); + if (!q) return -ENOMEM; - *ret_path = TAKE_PTR(q); + path_simplify(q); + + if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/.")) + if (!strextend(&q, "/")) + return -ENOMEM; + + free_and_replace(p, q); + } + + *ret_path = TAKE_PTR(p); } if (ret_fd) diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index 448471fa6d..0064b88cb3 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -24,6 +24,7 @@ typedef enum ChaseSymlinksFlags { * full path is still stored in ret_path and only the returned * file descriptor will point to the parent directory. */ CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. */ + CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ } ChaseSymlinksFlags; bool unsafe_transition(const struct stat *a, const struct stat *b); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 3d73e8e5b1..69c43485f1 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -517,6 +517,28 @@ TEST(chase_symlinks_at) { assert_se(chase_symlinks_at(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT); + /* Test CHASE_FILENAME */ + + assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0); + assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0); + assert_se(streq(result, "parent")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0); + assert_se(faccessat(fd, result, F_OK, 0) >= 0); + assert_se(streq(result, "chase")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + /* Test chase_symlinks_at_and_open() */ fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); From 88f2ee86012ac8accf31e806f2f682fc78a85304 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 17 Mar 2023 10:06:13 +0100 Subject: [PATCH 2/3] chase-symlinks: Return zero from access() and stat() helpers We never check if r > 0 when using these helpers, so let's just return zero like we usually do. --- src/basic/chase-symlinks.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 16d36693b1..2eb1701e44 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -667,14 +667,10 @@ int chase_symlinks_and_stat( assert(ret_stat); if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) /* Shortcut this call if none of the special features of this call are requested */ - - if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) - return -errno; - - return 1; - } + return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, + FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) @@ -687,7 +683,7 @@ int chase_symlinks_and_stat( if (ret_path) *ret_path = TAKE_PTR(p); - return 1; + return 0; } int chase_symlinks_and_access( @@ -705,14 +701,10 @@ int chase_symlinks_and_access( assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) /* Shortcut this call if none of the special features of this call are requested */ - - if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) - return -errno; - - return 1; - } + return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, + FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) @@ -726,7 +718,7 @@ int chase_symlinks_and_access( if (ret_path) *ret_path = TAKE_PTR(p); - return 1; + return 0; } int chase_symlinks_and_fopen_unlocked( From 12ef261794714c5db45e7bbb539e4cc3e1110143 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 14 Mar 2023 14:03:28 +0100 Subject: [PATCH 3/3] chase-symlinks: Add more chase_symlinks_at() helpers Copies of the corresponding chase_symlinks() helpers. --- src/basic/chase-symlinks.c | 177 +++++++++++++++++++++++++++++++++++++ src/basic/chase-symlinks.h | 6 ++ src/test/test-fs-util.c | 40 +++++++++ 3 files changed, 223 insertions(+) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 2eb1701e44..e3041ad6be 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -829,3 +829,180 @@ int chase_symlinks_at_and_open( return r; } +int chase_symlinks_at_and_opendir( + int dir_fd, + const char *path, + ChaseSymlinksFlags chase_flags, + char **ret_path, + DIR **ret_dir) { + + _cleanup_close_ int path_fd = -EBADF; + _cleanup_free_ char *p = NULL; + DIR *d; + int r; + + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); + assert(ret_dir); + + if (dir_fd == AT_FDCWD && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { + /* Shortcut this call if none of the special features of this call are requested */ + d = opendir(path); + if (!d) + return -errno; + + *ret_dir = d; + return 0; + } + + r = chase_symlinks_at(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + if (r < 0) + return r; + assert(path_fd >= 0); + + d = xopendirat(path_fd, ".", O_NOFOLLOW); + if (!d) + return -errno; + + if (ret_path) + *ret_path = TAKE_PTR(p); + + *ret_dir = d; + return 0; +} + +int chase_symlinks_at_and_stat( + int dir_fd, + const char *path, + ChaseSymlinksFlags chase_flags, + char **ret_path, + struct stat *ret_stat) { + + _cleanup_close_ int path_fd = -EBADF; + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); + assert(ret_stat); + + if (dir_fd == AT_FDCWD && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) + /* Shortcut this call if none of the special features of this call are requested */ + return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, + FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); + + r = chase_symlinks_at(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + if (r < 0) + return r; + assert(path_fd >= 0); + + if (fstat(path_fd, ret_stat) < 0) + return -errno; + + if (ret_path) + *ret_path = TAKE_PTR(p); + + return 0; +} + +int chase_symlinks_at_and_access( + int dir_fd, + const char *path, + ChaseSymlinksFlags chase_flags, + int access_mode, + char **ret_path) { + + _cleanup_close_ int path_fd = -EBADF; + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); + + if (dir_fd == AT_FDCWD && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) + /* Shortcut this call if none of the special features of this call are requested */ + return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, + FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); + + r = chase_symlinks_at(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + if (r < 0) + return r; + assert(path_fd >= 0); + + r = access_fd(path_fd, access_mode); + if (r < 0) + return r; + + if (ret_path) + *ret_path = TAKE_PTR(p); + + return 0; +} + +int chase_symlinks_at_and_fopen_unlocked( + int dir_fd, + const char *path, + ChaseSymlinksFlags chase_flags, + const char *open_flags, + char **ret_path, + FILE **ret_file) { + + _cleanup_free_ char *final_path = NULL; + _cleanup_close_ int fd = -EBADF; + int mode_flags, r; + + assert(path); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); + assert(open_flags); + assert(ret_file); + + mode_flags = fopen_mode_to_flags(open_flags); + if (mode_flags < 0) + return mode_flags; + + fd = chase_symlinks_at_and_open(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); + if (fd < 0) + return fd; + + r = take_fdopen_unlocked(&fd, open_flags, ret_file); + if (r < 0) + return r; + + if (ret_path) + *ret_path = TAKE_PTR(final_path); + + return 0; +} + +int chase_symlinks_at_and_unlink( + int dir_fd, + const char *path, + ChaseSymlinksFlags chase_flags, + int unlink_flags, + char **ret_path) { + + _cleanup_free_ char *p = NULL, *fname = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + assert(path); + assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT))); + + fd = chase_symlinks_at_and_open(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); + if (fd < 0) + return fd; + + r = path_extract_filename(p, &fname); + if (r < 0) + return r; + + if (unlinkat(fd, fname, unlink_flags) < 0) + return -errno; + + if (ret_path) + *ret_path = TAKE_PTR(p); + + return 0; +} diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index 0064b88cb3..de7910a5e6 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -42,4 +42,10 @@ int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseS int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path); int chase_symlinks_at(int dir_fd, const char *path, ChaseSymlinksFlags flags, char **ret_path, int *ret_fd); + int chase_symlinks_at_and_open(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path); +int chase_symlinks_at_and_opendir(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, char **ret_path, DIR **ret_dir); +int chase_symlinks_at_and_stat(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat); +int chase_symlinks_at_and_access(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path); +int chase_symlinks_at_and_fopen_unlocked(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); +int chase_symlinks_at_and_unlink(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 69c43485f1..ce61f28017 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "chase-symlinks.h" #include "copy.h" +#include "dirent-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -434,6 +435,9 @@ TEST(chase_symlinks_at) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; _cleanup_free_ char *result = NULL; + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct stat st; const char *p; assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); @@ -550,6 +554,42 @@ TEST(chase_symlinks_at) { assert_se(fd >= 0); assert_se(fd_verify_directory(fd) >= 0); fd = safe_close(fd); + + /* Test chase_symlinks_at_and_opendir() */ + + assert_se(chase_symlinks_at_and_opendir(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0); + FOREACH_DIRENT(de, dir, assert_not_reached()) + assert_se(streq(de->d_name, "r")); + assert_se(streq(result, "o/p/e/n/d/i")); + result = mfree(result); + + /* Test chase_symlinks_at_and_stat() */ + + assert_se(chase_symlinks_at_and_stat(tfd, "o/p", 0, &result, &st) >= 0); + assert_se(stat_verify_directory(&st) >= 0); + assert_se(streq(result, "o/p")); + result = mfree(result); + + /* Test chase_symlinks_at_and_access() */ + + assert_se(chase_symlinks_at_and_access(tfd, "o/p/e", 0, F_OK, &result) >= 0); + assert_se(streq(result, "o/p/e")); + result = mfree(result); + + /* Test chase_symlinks_at_and_fopen_unlocked() */ + + assert_se(chase_symlinks_at_and_fopen_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0); + assert_se(fread(&(char[1]) {}, 1, 1, f) == 0); + assert_se(feof(f)); + f = safe_fclose(f); + assert_se(streq(result, "o/p/e/n/f/i/l/e")); + result = mfree(result); + + /* Test chase_symlinks_at_and_unlink() */ + + assert_se(chase_symlinks_at_and_unlink(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0); + assert_se(streq(result, "o/p/e/n/f/i/l/e")); + result = mfree(result); } TEST(readlink_and_make_absolute) {