diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index bfdd9282f2..c0ba1f18d1 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -594,6 +594,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if ((flags & (CHASE_NONEXISTENT|CHASE_OPEN)) == (CHASE_NONEXISTENT|CHASE_OPEN)) return -EINVAL; + if ((flags & (CHASE_STEP|CHASE_OPEN)) == (CHASE_STEP|CHASE_OPEN)) + return -EINVAL; + if (isempty(path)) return -EINVAL; @@ -615,13 +618,34 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this * function what to do when encountering a symlink with an absolute path as directory: prefix it by the - * specified path. */ + * specified path. + * + * There are three ways to invoke this function: + * + * 1. Without CHASE_STEP or CHASE_OPEN: in this case the path is resolved and the normalized path is returned + * in `ret`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set 0 is returned if the file + * doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set >= 0 is returned if the destination was + * found, -ENOENT if it doesn't. + * + * 2. With CHASE_OPEN: in this case the destination is opened after chasing it as O_PATH and this file + * descriptor is returned as return value. This is useful to open files relative to some root + * directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using + * fd_reopen() or such) before it can be used for reading/writing. CHASE_OPEN may not be combined with + * CHASE_NONEXISTENT. + * + * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first + * symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if + * a caller wants to trace the a path through the file system verbosely. Returns < 0 on error, > 0 if the + * path is fully normalized, and == 0 for each normalization step. This may be combined with + * CHASE_NONEXISTENT, in which case 1 is returned when a component is not found. + * + * */ /* A root directory of "/" or "" is identical to none */ if (noop_root(original_root)) original_root = NULL; - if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN)) == CHASE_OPEN) { + if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) { /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set * and doesn't care about any of the other special features we provide either. */ r = open(path, O_PATH|O_CLOEXEC); @@ -718,6 +742,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, free_and_replace(done, parent); + if (flags & CHASE_STEP) + goto chased_one; + fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH); if (fd_parent < 0) return -errno; @@ -834,6 +861,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, free(buffer); todo = buffer = joined; + if (flags & CHASE_STEP) + goto chased_one; + continue; } @@ -872,7 +902,36 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, return TAKE_FD(fd); } + if (flags & CHASE_STEP) + return 1; + return exists; + +chased_one: + + if (ret) { + char *c; + + if (done) { + if (todo) { + c = strjoin(done, todo); + if (!c) + return -ENOMEM; + } else + c = TAKE_PTR(done); + } else { + if (todo) + c = strdup(todo); + else + c = strdup("/"); + if (!c) + return -ENOMEM; + } + + *ret = c; + } + + return 0; } int chase_symlinks_and_open( diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index d50ac7f972..16f20cecdf 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -75,6 +75,7 @@ enum { CHASE_SAFE = 1U << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */ CHASE_OPEN = 1U << 4, /* If set, return an O_PATH object to the final component */ CHASE_TRAIL_SLASH = 1U << 5, /* If set, any trailing slash will be preserved */ + CHASE_STEP = 1U << 6, /* If set, just execute a single step of the normalization */ }; int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 284167cff2..9e9650fced 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -24,7 +24,7 @@ #include "util.h" static void test_chase_symlinks(void) { - _cleanup_free_ char *result = NULL; + _cleanup_free_ char *result = NULL, *z = NULL, *w = NULL; char temp[] = "/tmp/test-chase.XXXXXX"; const char *top, *p, *pslash, *q, *qslash; int r, pfd; @@ -271,6 +271,49 @@ static void test_chase_symlinks(void) { assert_se(sd_id128_equal(a, b)); } + /* Test CHASE_ONE */ + + p = strjoina(temp, "/start"); + r = chase_symlinks(p, NULL, CHASE_STEP, &result); + assert_se(r == 0); + p = strjoina(temp, "/top/dot/dotdota"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase_symlinks(p, NULL, CHASE_STEP, &result); + assert_se(r == 0); + p = strjoina(temp, "/top/./dotdota"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase_symlinks(p, NULL, CHASE_STEP, &result); + assert_se(r == 0); + p = strjoina(temp, "/top/../a"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase_symlinks(p, NULL, CHASE_STEP, &result); + assert_se(r == 0); + p = strjoina(temp, "/a"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase_symlinks(p, NULL, CHASE_STEP, &result); + assert_se(r == 0); + p = strjoina(temp, "/b"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase_symlinks(p, NULL, CHASE_STEP, &result); + assert_se(r == 0); + assert_se(streq("/usr", result)); + result = mfree(result); + + r = chase_symlinks("/usr", NULL, CHASE_STEP, &result); + assert_se(r > 0); + assert_se(streq("/usr", result)); + result = mfree(result); + assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); }