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);