From: Lennart Poettering Date: Wed, 4 Apr 2018 15:03:45 +0000 (+0200) Subject: fs-util: add new CHASE_STEP flag to chase_symlinks() X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=df4fe5c7fc39a5241713516cc3ab12449b3e5459;p=elogind.git fs-util: add new CHASE_STEP flag to chase_symlinks() If the flag is set only a single step of the normalization is executed, and the resulting path is returned. This allows callers to normalize piecemeal, taking into account every single intermediary path of the normalization. --- diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 44185b16b..890a20a3e 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -579,6 +579,10 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) { } #endif // 0 +static bool noop_root(const char *root) { + return isempty(root) || path_equal(root, "/"); +} + static bool safe_transition(const struct stat *a, const struct stat *b) { /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files @@ -605,6 +609,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; @@ -626,13 +633,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 (empty_or_root(original_root)) + 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); @@ -714,7 +742,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, /* If we already are at the top, then going up will not change anything. This is in-line with * how the kernel handles this. */ - if (empty_or_root(done)) + if (isempty(done) || path_equal(done, "/")) continue; parent = dirname_malloc(done); @@ -729,6 +757,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; @@ -845,6 +876,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; } @@ -883,7 +917,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( @@ -900,7 +963,7 @@ int chase_symlinks_and_open( if (chase_flags & CHASE_NONEXISTENT) return -EINVAL; - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { + if (noop_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { /* Shortcut this call if none of the special features of this call are requested */ r = open(path, open_flags); if (r < 0) @@ -940,7 +1003,7 @@ int chase_symlinks_and_opendir( if (chase_flags & CHASE_NONEXISTENT) return -EINVAL; - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { + if (noop_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { /* Shortcut this call if none of the special features of this call are requested */ d = opendir(path); if (!d) @@ -966,46 +1029,6 @@ int chase_symlinks_and_opendir( return 0; } -int chase_symlinks_and_stat( - const char *path, - const char *root, - unsigned chase_flags, - char **ret_path, - struct stat *ret_stat) { - - _cleanup_close_ int path_fd = -1; - _cleanup_free_ char *p = NULL; - - assert(path); - assert(ret_stat); - - if (chase_flags & CHASE_NONEXISTENT) - return -EINVAL; - - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { - /* Shortcut this call if none of the special features of this call are requested */ - if (stat(path, ret_stat) < 0) - return -errno; - - return 1; - } - - path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL); - if (path_fd < 0) - return path_fd; - - if (fstat(path_fd, ret_stat) < 0) - return -errno; - - if (ret_path) - *ret_path = TAKE_PTR(p); - - if (chase_flags & CHASE_OPEN) - return TAKE_FD(path_fd); - - return 1; -} - int access_fd(int fd, int mode) { char p[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; int r; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 4c5286390..4abbd0ea6 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -91,13 +91,13 @@ 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); int chase_symlinks_and_open(const char *path, const char *root, unsigned chase_flags, int open_flags, char **ret_path); int chase_symlinks_and_opendir(const char *path, const char *root, unsigned chase_flags, char **ret_path, DIR **ret_dir); -int chase_symlinks_and_stat(const char *path, const char *root, unsigned chase_flags, char **ret_path, struct stat *ret_stat); /* Useful for usage with _cleanup_(), removes a directory and frees the pointer */ static inline void rmdir_and_free(char *p) { diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 639342dbb..2c5cf9583 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); }