diff options
-rw-r--r-- | src/basic/fs-util.c | 165 | ||||
-rw-r--r-- | src/test/test-fs-util.c | 4 |
2 files changed, 58 insertions, 111 deletions
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a4d772777e..e9fdb75a23 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -775,9 +775,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL; _cleanup_close_ int fd = -1; unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ + bool exists = true, append_trail_slash = false; struct stat previous_stat; - bool exists = true; - char *todo; + const char *todo; int r; assert(path); @@ -885,84 +885,51 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, if (fd < 0) return -errno; - if (flags & CHASE_SAFE) { + if (flags & CHASE_SAFE) if (fstat(fd, &previous_stat) < 0) return -errno; - } - if (root) { - _cleanup_free_ char *absolute = NULL; - const char *e; + if (flags & CHASE_TRAIL_SLASH) + append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/."); + if (root) { /* If we are operating on a root directory, let's take the root directory as it is. */ - e = path_startswith(buffer, root); - if (!e) + todo = path_startswith(buffer, root); + if (!todo) return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(ECHRNG), "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", path, root); done = strdup(root); - if (!done) - return -ENOMEM; - - /* Make sure "todo" starts with a slash */ - absolute = strjoin("/", e); - if (!absolute) - return -ENOMEM; - - free_and_replace(buffer, absolute); + } else { + todo = buffer; + done = strdup("/"); } - todo = buffer; for (;;) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -1; struct stat st; - size_t n, m; - - /* Determine length of first component in the path */ - n = strspn(todo, "/"); /* The slashes */ - - if (n > 1) { - /* If we are looking at more than a single slash then skip all but one, so that when - * we are done with everything we have a normalized path with only single slashes - * separating the path components. */ - todo += n - 1; - n = 1; - } - - m = n + strcspn(todo + n, "/"); /* The entire length of the component */ - - /* Extract the first component. */ - first = strndup(todo, m); - if (!first) - return -ENOMEM; - - todo += m; - - /* Empty? Then we reached the end. */ - if (isempty(first)) - break; - - /* Just a single slash? Then we reached the end. */ - if (path_equal(first, "/")) { - /* Preserve the trailing slash */ + const char *e; - if (flags & CHASE_TRAIL_SLASH) + r = path_find_first_component(&todo, true, &e); + if (r < 0) + return r; + if (r == 0) { /* We reached the end. */ + if (append_trail_slash) if (!strextend(&done, "/")) return -ENOMEM; - break; } - /* Just a dot? Then let's eat this up. */ - if (path_equal(first, "/.")) - continue; + first = strndup(e, r); + if (!first) + return -ENOMEM; /* Two dots? Then chop off the last bit of what we already found out. */ - if (path_equal(first, "/..")) { + if (path_equal(first, "..")) { _cleanup_free_ char *parent = NULL; _cleanup_close_ int fd_parent = -1; @@ -1007,22 +974,16 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, } /* Otherwise let's see what this is. */ - child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH); + child = openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH); if (child < 0) { - if (errno == ENOENT && (flags & CHASE_NONEXISTENT) && - (isempty(todo) || path_is_normalized(todo))) { - - /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return - * what we got so far. But don't allow this if the remaining path contains "../ or "./" - * or something else weird. */ + (isempty(todo) || path_is_safe(todo))) { + /* If CHASE_NONEXISTENT is set, and the path does not exist, then + * that's OK, return what we got so far. But don't allow this if the + * remaining path contains "../" or something else weird. */ - /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */ - if (streq_ptr(done, "/")) - *done = '\0'; - - if (!strextend(&done, first, todo)) + if (!path_extend(&done, first, todo)) return -ENOMEM; exists = false; @@ -1045,15 +1006,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, return log_autofs_mount_point(child, path, flags); if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) { - char *joined; _cleanup_free_ char *destination = NULL; - /* This is a symlink, in this case read the destination. But let's make sure we don't follow - * symlinks without bounds. */ + /* This is a symlink, in this case read the destination. But let's make sure we + * don't follow symlinks without bounds. */ if (--max_follow <= 0) return -ELOOP; - r = readlinkat_malloc(fd, first + n, &destination); + r = readlinkat_malloc(fd, first, &destination); if (r < 0) return r; if (isempty(destination)) @@ -1079,27 +1039,19 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, previous_stat = st; } - free(done); - /* Note that we do not revalidate the root, we take it as is. */ - if (isempty(root)) - done = NULL; - else { - done = strdup(root); - if (!done) - return -ENOMEM; - } + r = free_and_strdup(&done, empty_to_root(root)); + if (r < 0) + return r; + } - /* Prefix what's left to do with what we just read, and start the loop again, but - * remain in the current directory. */ - joined = path_join(destination, todo); - } else - joined = path_join("/", destination, todo); - if (!joined) + /* Prefix what's left to do with what we just read, and start the loop again, but + * remain in the current directory. */ + if (!path_extend(&destination, todo)) return -ENOMEM; - free(buffer); - todo = buffer = joined; + free_and_replace(buffer, destination); + todo = buffer; if (flags & CHASE_STEP) goto chased_one; @@ -1108,29 +1060,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, } /* If this is not a symlink, then let's just add the name we read to what we already verified. */ - if (!done) - done = TAKE_PTR(first); - else { - /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */ - if (streq(done, "/")) - *done = '\0'; - - if (!strextend(&done, first)) - return -ENOMEM; - } + if (!path_extend(&done, first)) + return -ENOMEM; /* And iterate again, but go one directory further down. */ safe_close(fd); fd = TAKE_FD(child); } - if (!done) { - /* Special case, turn the empty string into "/", to indicate the root directory. */ - done = strdup("/"); - if (!done) - return -ENOMEM; - } - if (ret_path) *ret_path = TAKE_PTR(done); @@ -1149,13 +1086,23 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, chased_one: if (ret_path) { - char *c; + const char *e; - c = strjoin(strempty(done), todo); - if (!c) - return -ENOMEM; + /* todo may contain slashes at the beginning. */ + r = path_find_first_component(&todo, true, &e); + if (r < 0) + return r; + if (r == 0) + *ret_path = TAKE_PTR(done); + else { + char *c; - *ret_path = c; + c = path_join(done, e); + if (!c) + return -ENOMEM; + + *ret_path = c; + } } return 0; diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index c42c48722f..63bb830370 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -332,7 +332,7 @@ static void test_chase_symlinks(void) { assert_se(S_ISLNK(st.st_mode)); result = mfree(result); - /* Test CHASE_ONE */ + /* Test CHASE_STEP */ p = strjoina(temp, "/start"); r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL); @@ -343,7 +343,7 @@ static void test_chase_symlinks(void) { r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL); assert_se(r == 0); - p = strjoina(temp, "/top/./dotdota"); + p = strjoina(temp, "/top/dotdota"); assert_se(streq(p, result)); result = mfree(result); |