diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index e39bfd9bc7..1ff6160dc8 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -506,7 +506,7 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch if (!t) return -ENOMEM; - *fs = path_simplify(t, false); + *fs = path_simplify(t); return 0; } @@ -523,7 +523,7 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch if (r < 0) return r; - path_simplify(*fs, false); + path_simplify(*fs); return 0; } @@ -919,7 +919,7 @@ int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) { if (!path) return -ENOMEM; - path_simplify(path, false); + path_simplify(path); } } else { @@ -942,7 +942,7 @@ int cg_split_spec(const char *spec, char **ret_controller, char **ret_path) { !path_is_absolute(path)) return -EINVAL; - path_simplify(path, false); + path_simplify(path); } } else { @@ -979,7 +979,7 @@ int cg_mangle_path(const char *path, char **result) { if (!t) return -ENOMEM; - *result = path_simplify(t, false); + *result = path_simplify(t); return 0; } diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 16f36359eb..213f9cef7b 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -972,7 +972,7 @@ static int search_and_fopen_internal( f = fopen(p, mode); if (f) { if (ret_path) - *ret_path = path_simplify(TAKE_PTR(p), true); + *ret_path = path_simplify(TAKE_PTR(p)); *ret = f; return 0; @@ -1013,7 +1013,7 @@ int search_and_fopen( if (!p) return -ENOMEM; - *ret_path = path_simplify(p, true); + *ret_path = path_simplify(p); } *ret = TAKE_PTR(f); @@ -1051,7 +1051,7 @@ int search_and_fopen_nulstr( if (!p) return -ENOMEM; - *ret_path = path_simplify(p, true); + *ret_path = path_simplify(p); } *ret = TAKE_PTR(f); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a4d772777e..f2999ba077 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); @@ -866,7 +866,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * anyway. Moreover at the end of this function after processing everything we'll always turn * the empty string back to "/". */ delete_trailing_chars(root, "/"); - path_simplify(root, true); + path_simplify(root); if (flags & CHASE_PREFIX_ROOT) { /* We don't support relative paths in combination with a root directory */ @@ -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 (flags & CHASE_TRAIL_SLASH) + append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/."); if (root) { - _cleanup_free_ char *absolute = NULL; - const char *e; - /* 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; + const char *e; - /* 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; + 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; } - m = n + strcspn(todo + n, "/"); /* The entire length of the component */ - - /* Extract the first component. */ - first = strndup(todo, m); + first = strndup(e, r); 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 */ - - if (flags & CHASE_TRAIL_SLASH) - if (!strextend(&done, "/")) - return -ENOMEM; - - break; - } - - /* Just a dot? Then let's eat this up. */ - if (path_equal(first, "/.")) - continue; - /* 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))) { + (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 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. */ - - /* 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/basic/hash-funcs.c b/src/basic/hash-funcs.c index 608131a1c1..084ed0c0a2 100644 --- a/src/basic/hash-funcs.c +++ b/src/basic/hash-funcs.c @@ -17,7 +17,7 @@ DEFINE_HASH_OPS_FULL(string_hash_ops_free_free, void, free); void path_hash_func(const char *q, struct siphash *state) { - size_t n; + bool add_slash = false; assert(q); assert(state); @@ -27,30 +27,31 @@ void path_hash_func(const char *q, struct siphash *state) { * similar checks and also doesn't care for trailing slashes. Note that relative and absolute paths (i.e. those * which begin in a slash or not) will hash differently though. */ - n = strspn(q, "/"); - if (n > 0) { /* Eat up initial slashes, and add one "/" to the hash for all of them */ - siphash24_compress(q, 1, state); - q += n; - } + /* if path is absolute, add one "/" to the hash. */ + if (path_is_absolute(q)) + siphash24_compress("/", 1, state); for (;;) { - /* Determine length of next component */ - n = strcspn(q, "/"); - if (n == 0) /* Reached the end? */ - break; + const char *e; + int r; - /* Add this component to the hash and skip over it */ - siphash24_compress(q, n, state); - q += n; + r = path_find_first_component(&q, true, &e); + if (r == 0) + return; - /* How many slashes follow this component? */ - n = strspn(q, "/"); - if (q[n] == 0) /* Is this a trailing slash? If so, we are at the end, and don't care about the slashes anymore */ - break; + if (add_slash) + siphash24_compress_byte('/', state); - /* We are not add the end yet. Hash exactly one slash for all of the ones we just encountered. */ - siphash24_compress(q, 1, state); - q += n; + if (r < 0) { + /* if a component is invalid, then add remaining part as a string. */ + string_hash_func(q, state); + return; + } + + /* Add this component to the hash. */ + siphash24_compress(e, r, state); + + add_slash = true; } } diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 28b2c66b6e..2742a27672 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -107,93 +107,96 @@ int path_make_absolute_cwd(const char *p, char **ret) { return 0; } -int path_make_relative(const char *from_dir, const char *to_path, char **_r) { - char *f, *t, *r, *p; - unsigned n_parents = 0; +int path_make_relative(const char *from, const char *to, char **ret) { + _cleanup_free_ char *result = NULL; + unsigned n_parents; + const char *f, *t; + int r, k; + char *p; - assert(from_dir); - assert(to_path); - assert(_r); + assert(from); + assert(to); + assert(ret); /* Strips the common part, and adds ".." elements as necessary. */ - if (!path_is_absolute(from_dir) || !path_is_absolute(to_path)) + if (!path_is_absolute(from) || !path_is_absolute(to)) return -EINVAL; - f = strdupa(from_dir); - t = strdupa(to_path); - - path_simplify(f, true); - path_simplify(t, true); - - /* Skip the common part. */ for (;;) { - size_t a, b; + r = path_find_first_component(&from, true, &f); + if (r < 0) + return r; - f += *f == '/'; - t += *t == '/'; + k = path_find_first_component(&to, true, &t); + if (k < 0) + return k; - if (!*f) { - if (!*t) - /* from_dir equals to_path. */ - r = strdup("."); - else - /* from_dir is a parent directory of to_path. */ - r = strdup(t); - if (!r) - return -ENOMEM; + if (r == 0) { + /* end of 'from' */ + if (k == 0) { + /* from and to are equivalent. */ + result = strdup("."); + if (!result) + return -ENOMEM; + } else { + /* 'to' is inside of 'from'. */ + result = strdup(t); + if (!result) + return -ENOMEM; - *_r = r; + path_simplify(result); + + if (!path_is_valid(result)) + return -EINVAL; + } + + *ret = TAKE_PTR(result); return 0; } - if (!*t) + if (r != k || !strneq(f, t, r)) break; - - a = strcspn(f, "/"); - b = strcspn(t, "/"); - - if (a != b || memcmp(f, t, a) != 0) - break; - - f += a; - t += b; } /* If we're here, then "from_dir" has one or more elements that need to * be replaced with "..". */ - /* Count the number of necessary ".." elements. */ - for (; *f;) { - size_t w; - - w = strcspn(f, "/"); - - /* If this includes ".." we can't do a simple series of "..", refuse */ - if (w == 2 && f[0] == '.' && f[1] == '.') - return -EINVAL; - - /* Count number of elements */ - n_parents++; - - f += w; - f += *f == '/'; + for (n_parents = 1;; n_parents++) { + /* If this includes ".." we can't do a simple series of "..". */ + r = path_find_first_component(&from, false, &f); + if (r < 0) + return r; + if (r == 0) + break; } - r = new(char, n_parents * 3 + strlen(t) + 1); - if (!r) + if (isempty(t) && n_parents * 3 > PATH_MAX) + /* PATH_MAX is counted *with* the trailing NUL byte */ + return -EINVAL; + + result = new(char, n_parents * 3 + !isempty(t) + strlen_ptr(t)); + if (!result) return -ENOMEM; - for (p = r; n_parents > 0; n_parents--) + for (p = result; n_parents > 0; n_parents--) p = mempcpy(p, "../", 3); - if (*t) - strcpy(p, t); - else - /* Remove trailing slash */ - *(--p) = 0; + if (isempty(t)) { + /* Remove trailing slash and terminate string. */ + *(--p) = '\0'; + *ret = TAKE_PTR(result); + return 0; + } - *_r = r; + strcpy(p, t); + + path_simplify(result); + + if (!path_is_valid(result)) + return -EINVAL; + + *ret = TAKE_PTR(result); return 0; } @@ -224,7 +227,7 @@ int path_strv_make_absolute_cwd(char **l) { if (r < 0) return r; - path_simplify(t, false); + path_simplify(t); free_and_replace(*s, t); } @@ -324,63 +327,53 @@ char **path_strv_resolve_uniq(char **l, const char *root) { return strv_uniq(l); } -char *path_simplify(char *path, bool kill_dots) { - char *f, *t; - bool slash = false, ignore_slash = false, absolute; +char *path_simplify(char *path) { + bool add_slash = false; + char *f = path; + int r; assert(path); - /* Removes redundant inner and trailing slashes. Also removes unnecessary dots - * if kill_dots is true. Modifies the passed string in-place. + /* Removes redundant inner and trailing slashes. Also removes unnecessary dots. + * Modifies the passed string in-place. * - * ///foo//./bar/. becomes /foo/./bar/. (if kill_dots is false) - * ///foo//./bar/. becomes /foo/bar (if kill_dots is true) - * .//./foo//./bar/. becomes ././foo/./bar/. (if kill_dots is false) - * .//./foo//./bar/. becomes foo/bar (if kill_dots is true) + * ///foo//./bar/. becomes /foo/bar + * .//./foo//./bar/. becomes foo/bar */ if (isempty(path)) return path; - absolute = path_is_absolute(path); - - f = path; - if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/')) { - ignore_slash = true; + if (path_is_absolute(path)) f++; - } - for (t = path; *f; f++) { + for (const char *p = f;;) { + const char *e; - if (*f == '/') { - slash = true; - continue; + r = path_find_first_component(&p, true, &e); + if (r == 0) + break; + + if (add_slash) + *f++ = '/'; + + if (r < 0) { + /* if path is invalid, then refuse to simplify remaining part. */ + memmove(f, p, strlen(p) + 1); + return path; } - if (slash) { - if (kill_dots && *f == '.' && IN_SET(f[1], 0, '/')) - continue; + memmove(f, e, r); + f += r; - slash = false; - if (ignore_slash) - ignore_slash = false; - else - *(t++) = '/'; - } - - *(t++) = *f; + add_slash = true; } - /* Special rule, if we stripped everything, we either need a "/" (for the root directory) - * or "." for the current directory */ - if (t == path) { - if (absolute) - *(t++) = '/'; - else - *(t++) = '.'; - } + /* Special rule, if we stripped everything, we need a "." for the current directory. */ + if (f == path) + *f++ = '.'; - *t = 0; + *f = '\0'; return path; } @@ -415,7 +408,7 @@ int path_simplify_and_warn( lvalue, fatal ? "" : ", ignoring", path); } - path_simplify(path, true); + path_simplify(path); if (!path_is_valid(path)) return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), @@ -430,7 +423,7 @@ int path_simplify_and_warn( return 0; } -char* path_startswith(const char *path, const char *prefix) { +char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) { assert(path); assert(prefix); @@ -448,33 +441,30 @@ char* path_startswith(const char *path, const char *prefix) { return NULL; for (;;) { - size_t a, b; + const char *p, *q; + int r, k; - path += strspn(path, "/"); - prefix += strspn(prefix, "/"); - - if (*prefix == 0) - return (char*) path; - - if (*path == 0) + r = path_find_first_component(&path, accept_dot_dot, &p); + if (r < 0) return NULL; - a = strcspn(path, "/"); - b = strcspn(prefix, "/"); - - if (a != b) + k = path_find_first_component(&prefix, accept_dot_dot, &q); + if (k < 0) return NULL; - if (memcmp(path, prefix, a) != 0) + if (k == 0) + return (char*) (p ?: path); + + if (r != k) return NULL; - path += a; - prefix += b; + if (!strneq(p, q, r)) + return NULL; } } int path_compare(const char *a, const char *b) { - int d; + int r; assert(a); assert(b); @@ -482,40 +472,45 @@ int path_compare(const char *a, const char *b) { /* A relative path and an absolute path must not compare as equal. * Which one is sorted before the other does not really matter. * Here a relative path is ordered before an absolute path. */ - d = (a[0] == '/') - (b[0] == '/'); - if (d != 0) - return d; + r = CMP(path_is_absolute(a), path_is_absolute(b)); + if (r != 0) + return r; for (;;) { - size_t j, k; + const char *aa, *bb; + int j, k; - a += strspn(a, "/"); - b += strspn(b, "/"); + j = path_find_first_component(&a, true, &aa); + k = path_find_first_component(&b, true, &bb); - if (*a == 0 && *b == 0) - return 0; + if (j < 0 || k < 0) { + /* When one of paths is invalid, order invalid path after valid one. */ + r = CMP(j < 0, k < 0); + if (r != 0) + return r; + + /* fallback to use strcmp() if both paths are invalid. */ + return strcmp(a, b); + } /* Order prefixes first: "/foo" before "/foo/bar" */ - if (*a == 0) + if (j == 0) { + if (k == 0) + return 0; return -1; - if (*b == 0) + } + if (k == 0) return 1; - j = strcspn(a, "/"); - k = strcspn(b, "/"); - /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ - d = memcmp(a, b, MIN(j, k)); - if (d != 0) - return (d > 0) - (d < 0); /* sign of d */ + r = memcmp(aa, bb, MIN(j, k)); + if (r != 0) + return r; /* Sort "/foo/a" before "/foo/aaa" */ - d = (j > k) - (j < k); /* sign of (j - k) */ - if (d != 0) - return d; - - a += j; - b += k; + r = CMP(j, k); + if (r != 0) + return r; } } @@ -597,7 +592,7 @@ char* path_extend_internal(char **x, ...) { *x = nx; if (old_sz > 0) - slash = nx[old_sz] == '/'; + slash = nx[old_sz-1] == '/'; else { nx[old_sz] = 0; slash = true; /* no need to generate a slash anymore */ @@ -706,7 +701,7 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret_file /* Found it! */ if (ret_filename) - *ret_filename = path_simplify(TAKE_PTR(element), false); + *ret_filename = path_simplify(TAKE_PTR(element)); if (ret_fd) *ret_fd = TAKE_FD(fd); @@ -808,6 +803,193 @@ char* dirname_malloc(const char *path) { return dir2; } +static const char *skip_slash_or_dot(const char *p) { + for (; !isempty(p); p++) { + if (*p == '/') + continue; + if (startswith(p, "./")) { + p++; + continue; + } + break; + } + return p; +} + +int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret) { + const char *q, *first, *end_first, *next; + size_t len; + + assert(p); + + /* When a path is input, then returns the pointer to the first component and its length, and + * move the input pointer to the next component or nul. This skips both over any '/' + * immediately *before* and *after* the first component before returning. + * + * Examples + * Input: p: "//.//aaa///bbbbb/cc" + * Output: p: "bbbbb///cc" + * ret: "aaa///bbbbb/cc" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "aaa//" + * Output: p: (pointer to NUL) + * ret: "aaa//" + * return value: 3 (== strlen("aaa")) + * + * Input: p: "/", ".", "" + * Output: p: (pointer to NUL) + * ret: NULL + * return value: 0 + * + * Input: p: NULL + * Output: p: NULL + * ret: NULL + * return value: 0 + * + * Input: p: "(too long component)" + * Output: return value: -EINVAL + * + * (when accept_dot_dot is false) + * Input: p: "//..//aaa///bbbbb/cc" + * Output: return value: -EINVAL + */ + + q = *p; + + first = skip_slash_or_dot(q); + if (isempty(first)) { + *p = first; + if (ret) + *ret = NULL; + return 0; + } + if (streq(first, ".")) { + *p = first + 1; + if (ret) + *ret = NULL; + return 0; + } + + end_first = strchrnul(first, '/'); + len = end_first - first; + + if (len > NAME_MAX) + return -EINVAL; + if (!accept_dot_dot && len == 2 && first[0] == '.' && first[1] == '.') + return -EINVAL; + + next = skip_slash_or_dot(end_first); + + *p = next + streq(next, "."); + if (ret) + *ret = first; + return len; +} + +static const char *skip_slash_or_dot_backward(const char *path, const char *q) { + assert(path); + + for (; q >= path; q--) { + if (*q == '/') + continue; + if (q > path && strneq(q - 1, "/.", 2)) + continue; + break; + } + return q; +} + +int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret) { + const char *q, *last_end, *last_begin; + size_t len; + + /* Similar to path_find_first_component(), but search components from the end. + * + * Examples + * Input: path: "//.//aaa///bbbbb/cc//././" + * next: NULL + * Output: next: "/cc//././" + * ret: "cc//././" + * return value: 2 (== strlen("cc")) + * + * Input: path: "//.//aaa///bbbbb/cc//././" + * next: "/cc//././" + * Output: next: "///bbbbb/cc//././" + * ret: "bbbbb/cc//././" + * return value: 5 (== strlen("bbbbb")) + * + * Input: path: "/", ".", "", or NULL + * Output: next: equivalent to path + * ret: NULL + * return value: 0 + * + * Input: path: "(too long component)" + * Output: return value: -EINVAL + * + * (when accept_dot_dot is false) + * Input: path: "//..//aaa///bbbbb/cc/..//" + * Output: return value: -EINVAL + */ + + if (isempty(path)) { + if (next) + *next = path; + if (ret) + *ret = NULL; + return 0; + } + + if (next && *next) { + if (*next < path || *next > path + strlen(path)) + return -EINVAL; + if (*next == path) { + if (ret) + *ret = NULL; + return 0; + } + if (!IN_SET(**next, '\0', '/')) + return -EINVAL; + q = *next - 1; + } else + q = path + strlen(path) - 1; + + q = skip_slash_or_dot_backward(path, q); + if ((q < path) || /* the root directory */ + (q == path && *q == '.')) { /* path is "." or "./" */ + if (next) + *next = path; + if (ret) + *ret = NULL; + return 0; + } + + last_end = q + 1; + + while (q >= path && *q != '/') + q--; + + last_begin = q + 1; + len = last_end - last_begin; + + if (len > NAME_MAX) + return -EINVAL; + if (!accept_dot_dot && len == 2 && strneq(last_begin, "..", 2)) + return -EINVAL; + + if (next) { + q = skip_slash_or_dot_backward(path, q); + if (q < path) + *next = path; + else + *next = q + 1; + } + + if (ret) + *ret = last_begin; + return len; +} + const char *last_path_component(const char *path) { /* Finds the last component of the path, preserving the optional trailing slash that signifies a directory. @@ -851,82 +1033,81 @@ const char *last_path_component(const char *path) { return path + k; } -int path_extract_filename(const char *p, char **ret) { +int path_extract_filename(const char *path, char **ret) { _cleanup_free_ char *a = NULL; - const char *c; - size_t n; + const char *c, *next = NULL; + int r; /* Extracts the filename part (i.e. right-most component) from a path, i.e. string that passes * filename_is_valid(). A wrapper around last_path_component(), but eats up trailing * slashes. Returns: * - * -EINVAL → if the passed in path is not a valid path - * -EADDRNOTAVAIL → if only a directory was specified, but no filename, i.e. the root dir itself is specified + * -EINVAL → if the path is not valid + * -EADDRNOTAVAIL → if only a directory was specified, but no filename, i.e. the root dir + * itself or "." is specified * -ENOMEM → no memory * - * Returns >= 0 on success. If the input path has a trailing slash, returns O_DIRECTORY, to indicate - * the referenced file must be a directory. + * Returns >= 0 on success. If the input path has a trailing slash, returns O_DIRECTORY, to + * indicate the referenced file must be a directory. * * This function guarantees to return a fully valid filename, i.e. one that passes * filename_is_valid() – this means "." and ".." are not accepted. */ - if (!path_is_valid(p)) + if (!path_is_valid(path)) return -EINVAL; - /* Special case the root dir, because in that case we simply have no filename, but - * last_path_component() won't complain */ - if (path_equal(p, "/")) + r = path_find_last_component(path, false, &next, &c); + if (r < 0) + return r; + if (r == 0) /* root directory */ return -EADDRNOTAVAIL; - c = last_path_component(p); - n = strcspn(c, "/"); - - a = strndup(c, n); + a = strndup(c, r); if (!a) return -ENOMEM; - if (!filename_is_valid(a)) - return -EINVAL; - *ret = TAKE_PTR(a); - return c[n] == '/' ? O_DIRECTORY : 0; + return strlen(c) > (size_t)r ? O_DIRECTORY : 0; } -int path_extract_directory(const char *p, char **ret) { +int path_extract_directory(const char *path, char **ret) { _cleanup_free_ char *a = NULL; - const char *c; + const char *c, *next = NULL; + int r; /* The inverse of path_extract_filename(), i.e. returns the directory path prefix. Returns: * - * -EINVAL → if the passed in path is not a valid path + * -EINVAL → if the path is not valid * -EDESTADDRREQ → if no directory was specified in the passed in path, i.e. only a filename was passed - * -EADDRNOTAVAIL → if the passed in parameter had no filename but did have a directory, i.e. the root dir itself was specified + * -EADDRNOTAVAIL → if the passed in parameter had no filename but did have a directory, i.e. + * the root dir itself or "." was specified * -ENOMEM → no memory (surprise!) * * This function guarantees to return a fully valid path, i.e. one that passes path_is_valid(). */ - if (!path_is_valid(p)) - return -EINVAL; + r = path_find_last_component(path, false, &next, &c); + if (r < 0) + return r; + if (r == 0) /* empty or root */ + return isempty(path) ? -EINVAL : -EADDRNOTAVAIL; + if (next == path) { + if (*path != '/') /* filename only */ + return -EDESTADDRREQ; - /* Special case the root dir, because otherwise for an input of "///" last_path_component() returns - * the pointer to the last slash only, which might be seen as a valid path below. */ - if (path_equal(p, "/")) - return -EADDRNOTAVAIL; + a = strdup("/"); + if (!a) + return -ENOMEM; + *ret = TAKE_PTR(a); + return 0; + } - c = last_path_component(p); - - /* Delete trailing slashes, but keep one */ - while (c > p+1 && c[-1] == '/') - c--; - - if (p == c) /* No path whatsoever? Then return a recognizable error */ - return -EDESTADDRREQ; - - a = strndup(p, c - p); + a = strndup(path, next - path); if (!a) return -ENOMEM; + path_simplify(a); + if (!path_is_valid(a)) return -EINVAL; @@ -953,44 +1134,30 @@ bool filename_is_valid(const char *p) { return true; } -bool path_is_valid(const char *p) { - +bool path_is_valid_full(const char *p, bool accept_dot_dot) { if (isempty(p)) return false; for (const char *e = p;;) { - size_t n; + int r; + + r = path_find_first_component(&e, accept_dot_dot, NULL); + if (r < 0) + return false; - /* Skip over slashes */ - e += strspn(e, "/"); if (e - p >= PATH_MAX) /* Already reached the maximum length for a path? (PATH_MAX is counted * *with* the trailing NUL byte) */ return false; if (*e == 0) /* End of string? Yay! */ return true; - - /* Skip over one component */ - n = strcspn(e, "/"); - if (n > NAME_MAX) /* One component larger than NAME_MAX? (NAME_MAX is counted *without* the - * trailing NUL byte) */ - return false; - - e += n; } } bool path_is_normalized(const char *p) { - - if (!path_is_valid(p)) + if (!path_is_safe(p)) return false; - if (dot_or_dot_dot(p)) - return false; - - if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) - return false; - - if (startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) + if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) return false; if (strstr(p, "//")) @@ -1189,15 +1356,15 @@ bool dot_or_dot_dot(const char *path) { return path[2] == 0; } -bool empty_or_root(const char *root) { +bool empty_or_root(const char *path) { /* For operations relative to some root directory, returns true if the specified root directory is redundant, * i.e. either / or NULL or the empty string or any equivalent. */ - if (!root) + if (isempty(path)) return true; - return root[strspn(root, "/")] == 0; + return path_equal(path, "/"); } bool path_strv_contains(char **l, const char *path) { diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 238a76ea73..c6d4668794 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -56,8 +56,11 @@ int path_split_and_make_absolute(const char *p, char ***ret); char* path_make_absolute(const char *p, const char *prefix); int safe_getcwd(char **ret); int path_make_absolute_cwd(const char *p, char **ret); -int path_make_relative(const char *from_dir, const char *to_path, char **_r); -char* path_startswith(const char *path, const char *prefix) _pure_; +int path_make_relative(const char *from, const char *to, char **ret); +char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_; +static inline char* path_startswith(const char *path, const char *prefix) { + return path_startswith_full(path, prefix, true); +} int path_compare(const char *a, const char *b) _pure_; bool path_equal(const char *a, const char *b) _pure_; bool path_equal_or_files_same(const char *a, const char *b, int flags); @@ -68,7 +71,7 @@ char* path_extend_internal(char **x, ...); #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX) #define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX) -char* path_simplify(char *path, bool kill_dots); +char* path_simplify(char *path); enum { PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ @@ -106,7 +109,7 @@ int fsck_exists(const char *fstype); * directory. Excludes the specified directory itself */ #define PATH_FOREACH_PREFIX(prefix, path) \ for (char *_slash = ({ \ - path_simplify(strcpy(prefix, path), false); \ + path_simplify(strcpy(prefix, path)); \ streq(prefix, "/") ? NULL : strrchr(prefix, '/'); \ }); \ _slash && ((*_slash = 0), true); \ @@ -115,7 +118,7 @@ int fsck_exists(const char *fstype); /* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */ #define PATH_FOREACH_PREFIX_MORE(prefix, path) \ for (char *_slash = ({ \ - path_simplify(strcpy(prefix, path), false); \ + path_simplify(strcpy(prefix, path)); \ if (streq(prefix, "/")) \ prefix[0] = 0; \ strrchr(prefix, 0); \ @@ -149,12 +152,20 @@ int fsck_exists(const char *fstype); }) char* dirname_malloc(const char *path); +int path_find_first_component(const char **p, bool accept_dot_dot, const char **ret); +int path_find_last_component(const char *path, bool accept_dot_dot, const char **next, const char **ret); const char *last_path_component(const char *path); -int path_extract_filename(const char *p, char **ret); -int path_extract_directory(const char *p, char **ret); +int path_extract_filename(const char *path, char **ret); +int path_extract_directory(const char *path, char **ret); bool filename_is_valid(const char *p) _pure_; -bool path_is_valid(const char *p) _pure_; +bool path_is_valid_full(const char *p, bool accept_dot_dot) _pure_; +static inline bool path_is_valid(const char *p) { + return path_is_valid_full(p, true); +} +static inline bool path_is_safe(const char *p) { + return path_is_valid_full(p, false); +} bool path_is_normalized(const char *p) _pure_; char *file_in_same_dir(const char *path, const char *filename); @@ -180,7 +191,7 @@ static inline const char *skip_dev_prefix(const char *p) { return e ?: p; } -bool empty_or_root(const char *root); +bool empty_or_root(const char *path); static inline const char *empty_to_root(const char *path) { return isempty(path) ? "/" : path; } diff --git a/src/basic/sysctl-util.c b/src/basic/sysctl-util.c index c96b5cd77f..8913e6ff85 100644 --- a/src/basic/sysctl-util.c +++ b/src/basic/sysctl-util.c @@ -33,7 +33,7 @@ char *sysctl_normalize(char *s) { n = strpbrk(n + 1, "/."); } while (n); - path_simplify(s, true); + path_simplify(s); /* Kill the leading slash, but keep the first character of the string in the same place. */ if (*s == '/' && *(s+1)) diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index 9eb654c140..91c8ff1725 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -126,7 +126,7 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { if (!path_extend(&d, nf)) return -ENOMEM; - *ret = path_simplify(TAKE_PTR(d), false); + *ret = path_simplify(TAKE_PTR(d)); } else *ret = TAKE_PTR(nf); @@ -168,7 +168,7 @@ int tempfn_random(const char *p, const char *extra, char **ret) { if (!path_extend(&d, nf)) return -ENOMEM; - *ret = path_simplify(TAKE_PTR(d), false); + *ret = path_simplify(TAKE_PTR(d)); } else *ret = TAKE_PTR(nf); @@ -213,7 +213,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { *x = 0; - *ret = path_simplify(t, false); + *ret = path_simplify(t); return 0; } diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index dbc9f5baa2..29ffd81ea9 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -387,7 +387,7 @@ int unit_name_path_escape(const char *f, char **ret) { if (!p) return -ENOMEM; - path_simplify(p, false); + path_simplify(p); if (empty_or_root(p)) s = strdup("-"); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 3756e80c30..385ec514b4 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -571,7 +571,7 @@ int get_home_dir(char **_h) { if (!h) return -ENOMEM; - *_h = path_simplify(h, true); + *_h = path_simplify(h); return 0; } @@ -609,7 +609,7 @@ int get_home_dir(char **_h) { if (!h) return -ENOMEM; - *_h = path_simplify(h, true); + *_h = path_simplify(h); return 0; } @@ -628,7 +628,7 @@ int get_shell(char **_s) { if (!s) return -ENOMEM; - *_s = path_simplify(s, true); + *_s = path_simplify(s); return 0; } @@ -666,7 +666,7 @@ int get_shell(char **_s) { if (!s) return -ENOMEM; - *_s = path_simplify(s, true); + *_s = path_simplify(s); return 0; } diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index 6868049c18..f39aa53da0 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -243,7 +243,7 @@ static int run(int argc, char *argv[]) { if (!j) return log_oom(); - path_simplify(j, false); + path_simplify(j); path = j; } else path = root; diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 5424f2c90f..9cc2f27591 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -472,7 +472,7 @@ static int refresh_one( if (!p) return -ENOMEM; - path_simplify(p, false); + path_simplify(p); r = refresh_one(controller, p, a, b, iteration, depth + 1, &child); if (r < 0) diff --git a/src/core/automount.c b/src/core/automount.c index dc92f9c0e4..8d75346d97 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -199,7 +199,7 @@ static int automount_set_where(Automount *a) { if (r < 0) return r; - path_simplify(a->where, false); + path_simplify(a->where); return 1; } diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 5b00faa6f6..6a46e14556 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -2113,7 +2113,7 @@ static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suf return -EINVAL; pp = strjoina("/", pp, suffix_path); - path_simplify(pp, false); + path_simplify(pp); r = sd_bus_call_method(u->manager->system_bus, "org.freedesktop.systemd1", diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 9c141d73b1..50daef6702 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -1451,7 +1451,7 @@ int bus_set_transient_exec_command( } else c->flags = b ? EXEC_COMMAND_IGNORE_FAILURE : 0; - path_simplify(c->path, false); + path_simplify(c->path); exec_command_append_list(exec_command, c); } @@ -3062,7 +3062,7 @@ int bus_exec_context_set_transient_property( if (!path_is_absolute(i + offset)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); - path_simplify(i + offset, false); + path_simplify(i + offset); } if (!UNIT_WRITE_FLAGS_NOOP(flags)) { diff --git a/src/core/dbus-path.c b/src/core/dbus-path.c index e132cd2b3c..e025c31532 100644 --- a/src/core/dbus-path.c +++ b/src/core/dbus-path.c @@ -103,7 +103,7 @@ static int bus_path_set_transient_property( if (!k) return -ENOMEM; - path_simplify(k, false); + path_simplify(k); s = new0(PathSpec, 1); if (!s) diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 4dea8d5aec..13d555041e 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -475,7 +475,7 @@ static int bus_service_set_transient_property( if (!n) return -ENOMEM; - path_simplify(n, true); + path_simplify(n); if (!path_is_normalized(n)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PIDFile= path '%s' is not valid", n); diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index 2c9da7412a..61c2f1a199 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -390,7 +390,7 @@ static int bus_socket_set_transient_property( if (!p->path) return log_oom(); - path_simplify(p->path, false); + path_simplify(p->path); } else if (streq(t, "Netlink")) { r = socket_address_parse_netlink(&p->address, a); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 8875ba082d..6c04c6e5db 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -2265,7 +2265,7 @@ static int bus_unit_set_transient_property( return r; STRV_FOREACH(p, l) { - path_simplify(*p, true); + path_simplify(*p); if (!path_is_absolute(*p)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path specified in %s is not absolute: %s", name, *p); diff --git a/src/core/execute.c b/src/core/execute.c index f577b790e6..3d750ee06e 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1846,7 +1846,7 @@ static int build_environment( if (!x) return -ENOMEM; - path_simplify(x + 5, true); + path_simplify(x + 5); our_env[n_env++] = x; } @@ -1867,7 +1867,7 @@ static int build_environment( if (!x) return -ENOMEM; - path_simplify(x + 6, true); + path_simplify(x + 6); our_env[n_env++] = x; } diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d31d642017..c6fca7135c 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -810,7 +810,7 @@ int config_parse_exec( n[nlen] = NULL; } - path_simplify(path, false); + path_simplify(path); while (!isempty(p)) { _cleanup_free_ char *word = NULL, *resolved = NULL; diff --git a/src/core/manager.c b/src/core/manager.c index 419bab5b9d..d96280667a 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -4589,7 +4589,7 @@ Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) { assert(path); strcpy(p, path); - path_simplify(p, false); + path_simplify(p); return hashmap_get(m->units_requiring_mounts_for, streq(p, "/") ? "" : p); } diff --git a/src/core/mount.c b/src/core/mount.c index b496268164..07d8e7b985 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -634,7 +634,7 @@ static int mount_add_extras(Mount *m) { return r; } - path_simplify(m->where, false); + path_simplify(m->where); if (!u->description) { r = unit_set_description(u, m->where); diff --git a/src/core/service.c b/src/core/service.c index f11b8f5557..792ec99a00 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -3148,7 +3148,7 @@ static int service_demand_pid_file(Service *s) { return -ENOMEM; } - path_simplify(ps->path, false); + path_simplify(ps->path); /* PATH_CHANGED would not be enough. There are daemons (sendmail) that * keep their PID file open all the time. */ diff --git a/src/core/socket.c b/src/core/socket.c index 8be01aa8e1..10777e4e80 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -1347,7 +1347,7 @@ static int usbffs_dispatch_eps(SocketPort *p) { goto fail; } - path_simplify(ep, false); + path_simplify(ep); r = usbffs_address_create(ep); if (r < 0) diff --git a/src/core/swap.c b/src/core/swap.c index a81b1928b8..74da2428c2 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -342,7 +342,7 @@ static int swap_add_extras(Swap *s) { return -ENOMEM; } - path_simplify(s->what, false); + path_simplify(s->what); if (!UNIT(s)->description) { r = unit_set_description(UNIT(s), s->what); diff --git a/src/core/unit.c b/src/core/unit.c index 9c97a92746..e48e7602c2 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -4601,7 +4601,7 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) if (!p) return -ENOMEM; - path = path_simplify(p, true); + path = path_simplify(p); if (!path_is_normalized(path)) return -EPERM; diff --git a/src/delta/delta.c b/src/delta/delta.c index 8c702459c1..9ebe435a49 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -656,7 +656,7 @@ static int run(int argc, char *argv[]) { int i; for (i = optind; i < argc; i++) { - path_simplify(argv[i], false); + path_simplify(argv[i]); k = process_suffix_chop(argv[i]); if (k < 0) diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 1d8ee93013..a4e3ea5311 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -610,7 +610,7 @@ static int parse_fstab(bool initrd) { return log_oom(); if (is_path(where)) { - path_simplify(where, false); + path_simplify(where); /* Follow symlinks here; see 5261ba901845c084de5a8fd06500ed09bfb0bd80 which makes sense for * mount units, but causes problems since it historically worked to have symlinks in e.g. diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index a5bd7d8d84..de0798f4d8 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -171,7 +171,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { return -ENOMEM; free_and_replace(syspath, new_syspath); - path_simplify(syspath, false); + path_simplify(syspath); } if (path_startswith(syspath, "/sys/devices/")) { diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 2e69a99bc6..1d78ff28c8 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -386,7 +386,7 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_mount_what) return log_oom(); - path_simplify(arg_mount_what, false); + path_simplify(arg_mount_what); if (!path_is_absolute(arg_mount_what)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -403,7 +403,7 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_mount_where) return log_oom(); - path_simplify(arg_mount_where, false); + path_simplify(arg_mount_where); if (!path_is_absolute(arg_mount_where)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -985,7 +985,7 @@ static int action_umount( if (!p) return log_oom(); - path_simplify(p, false); + path_simplify(p); r = stop_mounts(bus, p); if (r < 0) diff --git a/src/portable/portable.c b/src/portable/portable.c index 2eebdc08ae..9e33299ed5 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -688,14 +688,14 @@ static int portable_changes_add( if (!p) return -ENOMEM; - path_simplify(p, false); + path_simplify(p); if (source) { s = strdup(source); if (!s) return -ENOMEM; - path_simplify(s, false); + path_simplify(s); } c[(*n_changes)++] = (PortableChange) { diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 8d04ba04fc..c572e8dae8 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -160,7 +160,7 @@ static int image_new( if (!i->path) return -ENOMEM; - path_simplify(i->path, false); + path_simplify(i->path); *ret = TAKE_PTR(i); diff --git a/src/shared/install.c b/src/shared/install.c index 3723ec031c..48e632f4df 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -289,14 +289,14 @@ int unit_file_changes_add( if (!p) return -ENOMEM; - path_simplify(p, false); + path_simplify(p); if (source) { s = strdup(source); if (!s) return -ENOMEM; - path_simplify(s, false); + path_simplify(s); } c[(*n_changes)++] = (UnitFileChange) { @@ -515,7 +515,7 @@ static int mark_symlink_for_removal( if (!n) return -ENOMEM; - path_simplify(n, false); + path_simplify(n); r = set_consume(*remove_symlinks_to, n); if (r == -EEXIST) @@ -597,7 +597,7 @@ static int remove_marked_symlinks_fd( p = path_make_absolute(de->d_name, path); if (!p) return -ENOMEM; - path_simplify(p, false); + path_simplify(p); q = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &dest, NULL); if (q == -ENOENT) diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index 1e4bbfb1ab..ef3527e9a7 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -454,7 +454,7 @@ static int relabel_extra(void) { if (r == 0) /* EOF */ break; - path_simplify(line, true); + path_simplify(line); if (!path_is_normalized(line)) { log_warning("Path to relabel is not normalized, ignoring: %s", line); diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index 68c55a54c9..145bd119ac 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -73,7 +73,7 @@ int parse_path_argument(const char *path, bool suppress_root, char **arg) { if (r < 0) return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); - path_simplify(p, false); + path_simplify(p); if (suppress_root && empty_or_root(p)) p = mfree(p); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index c9eda29b49..d76556e75d 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -1651,7 +1651,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { if (resolved_id) { if (path_is_absolute(resolved_id)) { i->uid_path = TAKE_PTR(resolved_id); - path_simplify(i->uid_path, false); + path_simplify(i->uid_path); } else { _cleanup_free_ char *uid = NULL, *gid = NULL; if (split_pair(resolved_id, ":", &uid, &gid) == 0) { @@ -1706,7 +1706,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { if (resolved_id) { if (path_is_absolute(resolved_id)) { i->gid_path = TAKE_PTR(resolved_id); - path_simplify(i->gid_path, false); + path_simplify(i->gid_path); } else { r = parse_gid(resolved_id, &i->gid); if (r < 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); diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c index 9ed6bee9da..b9f20fbd94 100644 --- a/src/test/test-hashmap-plain.c +++ b/src/test/test-hashmap-plain.c @@ -1014,8 +1014,11 @@ static void test_path_hashmap(void) { assert_se(hashmap_put(h, "//foo", INT_TO_PTR(3)) == -EEXIST); assert_se(hashmap_put(h, "//foox/", INT_TO_PTR(4)) >= 0); assert_se(hashmap_put(h, "/foox////", INT_TO_PTR(5)) == -EEXIST); + assert_se(hashmap_put(h, "//././/foox//.//.", INT_TO_PTR(5)) == -EEXIST); assert_se(hashmap_put(h, "foo//////bar/quux//", INT_TO_PTR(6)) >= 0); assert_se(hashmap_put(h, "foo/bar//quux/", INT_TO_PTR(8)) == -EEXIST); + assert_se(hashmap_put(h, "foo./ba.r//.quux/", INT_TO_PTR(9)) >= 0); + assert_se(hashmap_put(h, "foo./ba.r//.//.quux///./", INT_TO_PTR(10)) == -EEXIST); assert_se(hashmap_get(h, "foo") == INT_TO_PTR(1)); assert_se(hashmap_get(h, "foo/") == INT_TO_PTR(1)); @@ -1024,12 +1027,14 @@ static void test_path_hashmap(void) { assert_se(hashmap_get(h, "//foo") == INT_TO_PTR(2)); assert_se(hashmap_get(h, "/////foo////") == INT_TO_PTR(2)); assert_se(hashmap_get(h, "/////foox////") == INT_TO_PTR(4)); + assert_se(hashmap_get(h, "/.///./foox//.//") == INT_TO_PTR(4)); assert_se(hashmap_get(h, "/foox/") == INT_TO_PTR(4)); assert_se(hashmap_get(h, "/foox") == INT_TO_PTR(4)); assert_se(!hashmap_get(h, "foox")); assert_se(hashmap_get(h, "foo/bar/quux") == INT_TO_PTR(6)); assert_se(hashmap_get(h, "foo////bar////quux/////") == INT_TO_PTR(6)); assert_se(!hashmap_get(h, "/foo////bar////quux/////")); + assert_se(hashmap_get(h, "foo././//ba.r////.quux///.//.") == INT_TO_PTR(9)); } static void test_string_strv_hashmap(void) { diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 8091a301e3..622e38c1bb 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -21,52 +21,9 @@ static void test_print_paths(void) { log_info("DEFAULT_USER_PATH=%s", DEFAULT_USER_PATH); } -#define test_path_compare(a, b, result) { \ - assert_se(path_compare(a, b) == result); \ - assert_se(path_compare(b, a) == -result); \ - assert_se(path_equal(a, b) == !result); \ - assert_se(path_equal(b, a) == !result); \ - } - -static void test_path_simplify(const char *in, const char *out, const char *out_dot) { - char *p; - - log_info("/* %s */", __func__); - - p = strdupa(in); - assert_se(streq(path_simplify(p, false), out)); - - p = strdupa(in); - assert_se(streq(path_simplify(p, true), out_dot)); -} - static void test_path(void) { log_info("/* %s */", __func__); - test_path_compare("/goo", "/goo", 0); - test_path_compare("/goo", "/goo", 0); - test_path_compare("//goo", "/goo", 0); - test_path_compare("//goo/////", "/goo", 0); - test_path_compare("goo/////", "goo", 0); - - test_path_compare("/goo/boo", "/goo//boo", 0); - test_path_compare("//goo/boo", "/goo/boo//", 0); - - test_path_compare("/", "///", 0); - - test_path_compare("/x", "x/", 1); - test_path_compare("x/", "/", -1); - - test_path_compare("/x/./y", "x/y", 1); - test_path_compare("x/.y", "x/y", -1); - - test_path_compare("foo", "/foo", -1); - test_path_compare("/foo", "/foo/bar", -1); - test_path_compare("/foo/aaa", "/foo/b", -1); - test_path_compare("/foo/aaa", "/foo/b/a", -1); - test_path_compare("/foo/a", "/foo/aaa", -1); - test_path_compare("/foo/a/b", "/foo/aaa", -1); - assert_se(path_is_absolute("/")); assert_se(!path_is_absolute("./")); @@ -79,29 +36,6 @@ static void test_path(void) { assert_se(streq(basename("/aa///file..."), "file...")); assert_se(streq(basename("file.../"), "")); - test_path_simplify("aaa/bbb////ccc", "aaa/bbb/ccc", "aaa/bbb/ccc"); - test_path_simplify("//aaa/.////ccc", "/aaa/./ccc", "/aaa/ccc"); - test_path_simplify("///", "/", "/"); - test_path_simplify("///.//", "/.", "/"); - test_path_simplify("///.//.///", "/./.", "/"); - test_path_simplify("////.././///../.", "/.././../.", "/../.."); - test_path_simplify(".", ".", "."); - test_path_simplify("./", ".", "."); - test_path_simplify(".///.//./.", "./././.", "."); - test_path_simplify(".///.//././/", "./././.", "."); - test_path_simplify("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", - "/./aaa/././.bbb/../c./d.dd/..eeee/.", - "/aaa/.bbb/../c./d.dd/..eeee"); - test_path_simplify("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", - "/./aaa/././.bbb/../c./d.dd/..eeee/..", - "/aaa/.bbb/../c./d.dd/..eeee/.."); - test_path_simplify(".//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", - "././aaa/././.bbb/../c./d.dd/..eeee/..", - "aaa/.bbb/../c./d.dd/..eeee/.."); - test_path_simplify("..//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", - ".././aaa/././.bbb/../c./d.dd/..eeee/..", - "../aaa/.bbb/../c./d.dd/..eeee/.."); - assert_se(PATH_IN_SET("/bin", "/", "/bin", "/foo")); assert_se(PATH_IN_SET("/bin", "/bin")); assert_se(PATH_IN_SET("/bin", "/foo/bar", "/bin")); @@ -120,6 +54,99 @@ static void test_path(void) { assert_se(!path_equal_filename("/b", "/c")); } +static void test_path_simplify_one(const char *in, const char *out) { + char *p; + + p = strdupa(in); + path_simplify(p); + log_debug("/* test_path_simplify(%s) → %s (expected: %s) */", in, p, out); + assert_se(streq(p, out)); +} + +static void test_path_simplify(void) { + _cleanup_free_ char *hoge = NULL, *hoge_out = NULL; + char foo[NAME_MAX * 2]; + + log_info("/* %s */", __func__); + + test_path_simplify_one("", ""); + test_path_simplify_one("aaa/bbb////ccc", "aaa/bbb/ccc"); + test_path_simplify_one("//aaa/.////ccc", "/aaa/ccc"); + test_path_simplify_one("///", "/"); + test_path_simplify_one("///.//", "/"); + test_path_simplify_one("///.//.///", "/"); + test_path_simplify_one("////.././///../.", "/../.."); + test_path_simplify_one(".", "."); + test_path_simplify_one("./", "."); + test_path_simplify_one(".///.//./.", "."); + test_path_simplify_one(".///.//././/", "."); + test_path_simplify_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + "/aaa/.bbb/../c./d.dd/..eeee"); + test_path_simplify_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "/aaa/.bbb/../c./d.dd/..eeee/.."); + test_path_simplify_one(".//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "aaa/.bbb/../c./d.dd/..eeee/.."); + test_path_simplify_one("..//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "../aaa/.bbb/../c./d.dd/..eeee/.."); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_simplify_one(foo, foo); + + hoge = strjoin("/", foo); + assert_se(hoge); + test_path_simplify_one(hoge, hoge); + hoge = mfree(hoge); + + hoge = strjoin("a////.//././//./b///././/./c/////././//./", foo, "//.//////d/e/.//f/"); + assert_se(hoge); + + hoge_out = strjoin("a/b/c/", foo, "//.//////d/e/.//f/"); + assert_se(hoge_out); + + test_path_simplify_one(hoge, hoge_out); +} + +static void test_path_compare_one(const char *a, const char *b, int expected) { + int r; + + assert_se(path_compare(a, a) == 0); + assert_se(path_compare(b, b) == 0); + + r = path_compare(a, b); + assert_se((r > 0) == (expected > 0) && (r < 0) == (expected < 0)); + r = path_compare(b, a); + assert_se((r < 0) == (expected > 0) && (r > 0) == (expected < 0)); + + assert_se(path_equal(a, a) == 1); + assert_se(path_equal(b, b) == 1); + assert_se(path_equal(a, b) == (expected == 0)); + assert_se(path_equal(b, a) == (expected == 0)); +} + +static void test_path_compare(void) { + test_path_compare_one("/goo", "/goo", 0); + test_path_compare_one("/goo", "/goo", 0); + test_path_compare_one("//goo", "/goo", 0); + test_path_compare_one("//goo/////", "/goo", 0); + test_path_compare_one("goo/////", "goo", 0); + test_path_compare_one("/goo/boo", "/goo//boo", 0); + test_path_compare_one("//goo/boo", "/goo/boo//", 0); + test_path_compare_one("//goo/././//./boo//././//", "/goo/boo//.", 0); + test_path_compare_one("/.", "//.///", 0); + test_path_compare_one("/x", "x/", 1); + test_path_compare_one("x/", "/", -1); + test_path_compare_one("/x/./y", "x/y", 1); + test_path_compare_one("x/.y", "x/y", -1); + test_path_compare_one("foo", "/foo", -1); + test_path_compare_one("/foo", "/foo/bar", -1); + test_path_compare_one("/foo/aaa", "/foo/b", -1); + test_path_compare_one("/foo/aaa", "/foo/b/a", -1); + test_path_compare_one("/foo/a", "/foo/aaa", -1); + test_path_compare_one("/foo/a/b", "/foo/aaa", -1); +} + static void test_path_equal_root(void) { /* Nail down the details of how path_equal("/", ...) works. */ @@ -128,7 +155,7 @@ static void test_path_equal_root(void) { assert_se(path_equal("/", "/")); assert_se(path_equal("/", "//")); - assert_se(!path_equal("/", "/./")); + assert_se(path_equal("/", "/./")); assert_se(!path_equal("/", "/../")); assert_se(!path_equal("/", "/.../")); @@ -397,8 +424,14 @@ static void test_path_extend(void) { assert_se(path_extend(&p, "/foo") == p); assert_se(streq(p, "foo/foo")); - assert_se(path_extend(&p, "waaaah/wahhh/") == p); - assert_se(streq(p, "foo/foo/waaaah/wahhh/")); + assert_se(path_extend(&p, "/waaaah/wahhh//") == p); + assert_se(streq(p, "foo/foo/waaaah/wahhh//")); /* path_extend() does not drop redundant slashes */ + assert_se(path_extend(&p, "/aaa/bbb/") == p); + assert_se(streq(p, "foo/foo/waaaah/wahhh///aaa/bbb/")); /* but not add an extra slash */ + + assert_se(free_and_strdup(&p, "/") >= 0); + assert_se(path_extend(&p, "foo") == p); + assert_se(streq(p, "/foo")); } static void test_fsck_exists(void) { @@ -414,29 +447,32 @@ static void test_fsck_exists(void) { assert_se(fsck_exists("/../bin/") == 0); } -static void test_make_relative(void) { - char *result; +static void test_path_make_relative_one(const char *from, const char *to, const char *expected) { + _cleanup_free_ char *z = NULL; + int r; + log_info("/* %s(%s, %s) */", __func__, from, to); + + r = path_make_relative(from, to, &z); + assert_se((r >= 0) == !!expected); + assert_se(streq_ptr(z, expected)); +} + +static void test_make_relative(void) { log_info("/* %s */", __func__); - assert_se(path_make_relative("some/relative/path", "/some/path", &result) < 0); - assert_se(path_make_relative("/some/path", "some/relative/path", &result) < 0); - assert_se(path_make_relative("/some/dotdot/../path", "/some/path", &result) < 0); + test_path_make_relative_one("some/relative/path", "/some/path", NULL); + test_path_make_relative_one("/some/path", "some/relative/path", NULL); + test_path_make_relative_one("/some/dotdot/../path", "/some/path", NULL); -#define test(from_dir, to_path, expected) { \ - _cleanup_free_ char *z = NULL; \ - path_make_relative(from_dir, to_path, &z); \ - assert_se(streq(z, expected)); \ - } - - test("/", "/", "."); - test("/", "/some/path", "some/path"); - test("/some/path", "/some/path", "."); - test("/some/path", "/some/path/in/subdir", "in/subdir"); - test("/some/path", "/", "../.."); - test("/some/path", "/some/other/path", "../other/path"); - test("/some/path/./dot", "/some/further/path", "../../further/path"); - test("//extra.//.//./.slashes//./won't////fo.ol///anybody//", "/././/extra././/.slashes////ar.e/.just/././.fine///", "../../../ar.e/.just/.fine"); + test_path_make_relative_one("/", "/", "."); + test_path_make_relative_one("/", "/some/path", "some/path"); + test_path_make_relative_one("/some/path", "/some/path", "."); + test_path_make_relative_one("/some/path", "/some/path/in/subdir", "in/subdir"); + test_path_make_relative_one("/some/path", "/", "../.."); + test_path_make_relative_one("/some/path", "/some/other/path", "../other/path"); + test_path_make_relative_one("/some/path/./dot", "/some/further/path", "../../further/path"); + test_path_make_relative_one("//extra.//.//./.slashes//./won't////fo.ol///anybody//", "/././/extra././/.slashes////ar.e/.just/././.fine///", "../../../ar.e/.just/.fine"); } static void test_strv_resolve(void) { @@ -467,49 +503,42 @@ static void test_strv_resolve(void) { assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); } -static void test_path_startswith(void) { - const char *p; +static void test_path_startswith_one(const char *path, const char *prefix, const char *skipped, const char *expected) { + const char *p, *q; + log_debug("/* %s(%s, %s) */", __func__, path, prefix); + + p = path_startswith(path, prefix); + assert_se(streq_ptr(p, expected)); + if (p) { + q = strjoina(skipped, p); + assert_se(streq(q, path)); + assert_se(p == path + strlen(skipped)); + } +} + +static void test_path_startswith(void) { log_info("/* %s */", __func__); - p = path_startswith("/foo/bar/barfoo/", "/foo"); - assert_se(streq_ptr(p, "bar/barfoo/")); + test_path_startswith_one("/foo/bar/barfoo/", "/foo", "/foo/", "bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/", "/foo/", "bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "/", "/", "foo/bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "////", "/", "foo/bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "/foo//bar/////barfoo///", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfoo////", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar///barfoo/", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo////bar/barfoo/", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "////foo/bar/barfoo/", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfoo", "/foo/bar/barfoo/", ""); - p = path_startswith("/foo/bar/barfoo/", "/foo/"); - assert_se(streq_ptr(p, "bar/barfoo/")); - - p = path_startswith("/foo/bar/barfoo/", "/"); - assert_se(streq_ptr(p, "foo/bar/barfoo/")); - - p = path_startswith("/foo/bar/barfoo/", "////"); - assert_se(streq_ptr(p, "foo/bar/barfoo/")); - - p = path_startswith("/foo/bar/barfoo/", "/foo//bar/////barfoo///"); - assert_se(streq_ptr(p, "")); - - p = path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo////"); - assert_se(streq_ptr(p, "")); - - p = path_startswith("/foo/bar/barfoo/", "/foo/bar///barfoo/"); - assert_se(streq_ptr(p, "")); - - p = path_startswith("/foo/bar/barfoo/", "/foo////bar/barfoo/"); - assert_se(streq_ptr(p, "")); - - p = path_startswith("/foo/bar/barfoo/", "////foo/bar/barfoo/"); - assert_se(streq_ptr(p, "")); - - p = path_startswith("/foo/bar/barfoo/", "/foo/bar/barfoo"); - assert_se(streq_ptr(p, "")); - - assert_se(!path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa/")); - assert_se(!path_startswith("/foo/bar/barfoo/", "/foo/bar/barfooa")); - assert_se(!path_startswith("/foo/bar/barfoo/", "")); - assert_se(!path_startswith("/foo/bar/barfoo/", "/bar/foo")); - assert_se(!path_startswith("/foo/bar/barfoo/", "/f/b/b/")); - assert_se(!path_startswith("/foo/bar/barfoo/", "/foo/bar/barfo")); - assert_se(!path_startswith("/foo/bar/barfoo/", "/foo/bar/bar")); - assert_se(!path_startswith("/foo/bar/barfoo/", "/fo")); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfooa/", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfooa", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/bar/foo", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/f/b/b/", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfo", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/bar", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL); } static void test_prefix_root_one(const char *r, const char *p, const char *expected) { @@ -571,6 +600,169 @@ static void test_file_in_same_dir(void) { free(t); } +static void test_path_find_first_component_one( + const char *path, + bool accept_dot_dot, + char **expected, + int ret) { + + log_debug("/* %s(\"%s\", accept_dot_dot=%s) */", __func__, strnull(path), yes_no(accept_dot_dot)); + + for (const char *p = path;;) { + const char *e; + int r; + + r = path_find_first_component(&p, accept_dot_dot, &e); + if (r <= 0) { + if (r == 0) { + if (path) + assert_se(p == path + strlen_ptr(path)); + else + assert_se(!p); + assert_se(!e); + } + assert_se(r == ret); + assert_se(strv_isempty(expected)); + return; + } + + assert_se(e); + assert_se(strcspn(e, "/") == (size_t) r); + assert_se(strlen_ptr(*expected) == (size_t) r); + assert_se(strneq(e, *expected++, r)); + } +} + +static void test_path_find_first_component(void) { + _cleanup_free_ char *hoge = NULL; + char foo[NAME_MAX * 2]; + + log_info("/* %s */", __func__); + + test_path_find_first_component_one(NULL, false, NULL, 0); + test_path_find_first_component_one("", false, NULL, 0); + test_path_find_first_component_one("/", false, NULL, 0); + test_path_find_first_component_one(".", false, NULL, 0); + test_path_find_first_component_one("./", false, NULL, 0); + test_path_find_first_component_one("./.", false, NULL, 0); + test_path_find_first_component_one("..", false, NULL, -EINVAL); + test_path_find_first_component_one("/..", false, NULL, -EINVAL); + test_path_find_first_component_one("./..", false, NULL, -EINVAL); + test_path_find_first_component_one("////./././//.", false, NULL, 0); + test_path_find_first_component_one("a/b/c", false, STRV_MAKE("a", "b", "c"), 0); + test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0); + test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL); + test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL); + test_path_find_first_component_one("a/foo./b", false, STRV_MAKE("a", "foo.", "b"), 0); + + test_path_find_first_component_one(NULL, true, NULL, 0); + test_path_find_first_component_one("", true, NULL, 0); + test_path_find_first_component_one("/", true, NULL, 0); + test_path_find_first_component_one(".", true, NULL, 0); + test_path_find_first_component_one("./", true, NULL, 0); + test_path_find_first_component_one("./.", true, NULL, 0); + test_path_find_first_component_one("..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("/..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("./..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("////./././//.", true, NULL, 0); + test_path_find_first_component_one("a/b/c", true, STRV_MAKE("a", "b", "c"), 0); + test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0); + test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0); + test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0); + test_path_find_first_component_one("a/foo./b", true, STRV_MAKE("a", "foo.", "b"), 0); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_find_first_component_one(foo, false, NULL, -EINVAL); + test_path_find_first_component_one(foo, true, NULL, -EINVAL); + + hoge = strjoin("a/b/c/", foo, "//d/e/.//f/"); + assert_se(hoge); + + test_path_find_first_component_one(hoge, false, STRV_MAKE("a", "b", "c"), -EINVAL); + test_path_find_first_component_one(hoge, true, STRV_MAKE("a", "b", "c"), -EINVAL); +} + +static void test_path_find_last_component_one( + const char *path, + bool accept_dot_dot, + char **expected, + int ret) { + + log_debug("/* %s(\"%s\", accept_dot_dot=%s) */", __func__, strnull(path), yes_no(accept_dot_dot)); + + for (const char *next = NULL;;) { + const char *e; + int r; + + r = path_find_last_component(path, accept_dot_dot, &next, &e); + if (r <= 0) { + if (r == 0) { + assert_se(next == path); + assert_se(!e); + } + assert_se(r == ret); + assert_se(strv_isempty(expected)); + return; + } + + assert_se(e); + assert_se(strcspn(e, "/") == (size_t) r); + assert_se(strlen_ptr(*expected) == (size_t) r); + assert_se(strneq(e, *expected++, r)); + } +} + +static void test_path_find_last_component(void) { + _cleanup_free_ char *hoge = NULL; + char foo[NAME_MAX * 2]; + + log_info("/* %s */", __func__); + + test_path_find_last_component_one(NULL, false, NULL, 0); + test_path_find_last_component_one("", false, NULL, 0); + test_path_find_last_component_one("/", false, NULL, 0); + test_path_find_last_component_one(".", false, NULL, 0); + test_path_find_last_component_one("./", false, NULL, 0); + test_path_find_last_component_one("./.", false, NULL, 0); + test_path_find_last_component_one("..", false, NULL, -EINVAL); + test_path_find_last_component_one("/..", false, NULL, -EINVAL); + test_path_find_last_component_one("./..", false, NULL, -EINVAL); + test_path_find_last_component_one("////./././//.", false, NULL, 0); + test_path_find_last_component_one("a/b/c", false, STRV_MAKE("c", "b", "a"), 0); + test_path_find_last_component_one("././//.///aa./.bbb//./ccc/././/", false, STRV_MAKE("ccc", ".bbb", "aa."), 0); + test_path_find_last_component_one("././//.///aa/../.../bbb//./ccc/.", false, STRV_MAKE("ccc", "bbb", "..."), -EINVAL); + test_path_find_last_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("..eeee", "d.dd", "c."), -EINVAL); + + test_path_find_last_component_one(NULL, true, NULL, 0); + test_path_find_last_component_one("", true, NULL, 0); + test_path_find_last_component_one("/", true, NULL, 0); + test_path_find_last_component_one(".", true, NULL, 0); + test_path_find_last_component_one("./", true, NULL, 0); + test_path_find_last_component_one("./.", true, NULL, 0); + test_path_find_last_component_one("..", true, STRV_MAKE(".."), 0); + test_path_find_last_component_one("/..", true, STRV_MAKE(".."), 0); + test_path_find_last_component_one("./..", true, STRV_MAKE(".."), 0); + test_path_find_last_component_one("////./././//.", true, NULL, 0); + test_path_find_last_component_one("a/b/c", true, STRV_MAKE("c", "b", "a"), 0); + test_path_find_last_component_one("././//.///aa./.bbb//./ccc/././/", true, STRV_MAKE("ccc", ".bbb", "aa."), 0); + test_path_find_last_component_one("././//.///aa/../.../bbb//./ccc/.", true, STRV_MAKE("ccc", "bbb", "...", "..", "aa"), 0); + test_path_find_last_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("..eeee", "d.dd", "c.", "..", ".bbb", "aaa"), 0); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_find_last_component_one(foo, false, NULL, -EINVAL); + test_path_find_last_component_one(foo, true, NULL, -EINVAL); + + hoge = strjoin(foo, "/a/b/c/"); + assert_se(hoge); + + test_path_find_last_component_one(hoge, false, STRV_MAKE("c", "b", "a"), -EINVAL); + test_path_find_last_component_one(hoge, true, STRV_MAKE("c", "b", "a"), -EINVAL); +} + static void test_last_path_component(void) { assert_se(last_path_component(NULL) == NULL); assert_se(streq(last_path_component("a/b/c"), "c")); @@ -613,26 +805,28 @@ static void test_path_extract_filename(void) { test_path_extract_filename_one("/", NULL, -EADDRNOTAVAIL); test_path_extract_filename_one("//", NULL, -EADDRNOTAVAIL); test_path_extract_filename_one("///", NULL, -EADDRNOTAVAIL); - test_path_extract_filename_one(".", NULL, -EINVAL); - test_path_extract_filename_one("./.", NULL, -EINVAL); - test_path_extract_filename_one("././", NULL, -EINVAL); - test_path_extract_filename_one("././/", NULL, -EINVAL); + test_path_extract_filename_one("/.", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one(".", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("./", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("./.", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("././", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("././/", NULL, -EADDRNOTAVAIL); test_path_extract_filename_one("/foo/a", "a", 0); test_path_extract_filename_one("/foo/a/", "a", O_DIRECTORY); test_path_extract_filename_one("", NULL, -EINVAL); test_path_extract_filename_one("a", "a", 0); test_path_extract_filename_one("a/", "a", O_DIRECTORY); + test_path_extract_filename_one("a/././//.", "a", O_DIRECTORY); test_path_extract_filename_one("/a", "a", 0); test_path_extract_filename_one("/a/", "a", O_DIRECTORY); + test_path_extract_filename_one("/a//./.", "a", O_DIRECTORY); test_path_extract_filename_one("/////////////a/////////////", "a", O_DIRECTORY); - test_path_extract_filename_one("xx/.", NULL, -EINVAL); + test_path_extract_filename_one("//./a/.///b./././.c//./d//.", "d", O_DIRECTORY); + test_path_extract_filename_one("xx/.", "xx", O_DIRECTORY); test_path_extract_filename_one("xx/..", NULL, -EINVAL); test_path_extract_filename_one("..", NULL, -EINVAL); test_path_extract_filename_one("/..", NULL, -EINVAL); test_path_extract_filename_one("../", NULL, -EINVAL); - test_path_extract_filename_one(".", NULL, -EINVAL); - test_path_extract_filename_one("/.", NULL, -EINVAL); - test_path_extract_filename_one("./", NULL, -EINVAL); } static void test_path_extract_directory_one(const char *input, const char *output, int ret) { @@ -671,26 +865,28 @@ static void test_path_extract_directory(void) { test_path_extract_directory_one("/", NULL, -EADDRNOTAVAIL); test_path_extract_directory_one("//", NULL, -EADDRNOTAVAIL); test_path_extract_directory_one("///", NULL, -EADDRNOTAVAIL); - test_path_extract_directory_one(".", NULL, -EDESTADDRREQ); - test_path_extract_directory_one("./.", ".", 0); - test_path_extract_directory_one("././", ".", 0); - test_path_extract_directory_one("././/", ".", 0); + test_path_extract_directory_one("/.", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one(".", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("./", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("./.", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("././", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("././/", NULL, -EADDRNOTAVAIL); test_path_extract_directory_one("/foo/a", "/foo", 0); test_path_extract_directory_one("/foo/a/", "/foo", 0); test_path_extract_directory_one("", NULL, -EINVAL); test_path_extract_directory_one("a", NULL, -EDESTADDRREQ); test_path_extract_directory_one("a/", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("a/././//.", NULL, -EDESTADDRREQ); test_path_extract_directory_one("/a", "/", 0); test_path_extract_directory_one("/a/", "/", 0); + test_path_extract_directory_one("/a//./.", "/", 0); test_path_extract_directory_one("/////////////a/////////////", "/", 0); - test_path_extract_directory_one("xx/.", "xx", 0); - test_path_extract_directory_one("xx/..", "xx", 0); - test_path_extract_directory_one("..", NULL, -EDESTADDRREQ); - test_path_extract_directory_one("/..", "/", 0); - test_path_extract_directory_one("../", NULL, -EDESTADDRREQ); - test_path_extract_directory_one(".", NULL, -EDESTADDRREQ); - test_path_extract_directory_one("/.", "/", 0); - test_path_extract_directory_one("./", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("//./a/.///b./././.c//./d//.", "/a/b./.c", 0); + test_path_extract_directory_one("xx/.", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("xx/..", NULL, -EINVAL); + test_path_extract_directory_one("..", NULL, -EINVAL); + test_path_extract_directory_one("/..", NULL, -EINVAL); + test_path_extract_directory_one("../", NULL, -EINVAL); } static void test_filename_is_valid(void) { @@ -716,36 +912,48 @@ static void test_filename_is_valid(void) { assert_se(filename_is_valid("o.o")); } -static void test_path_is_valid(void) { +static void test_path_is_valid_and_safe_one(const char *p, bool ret) { + log_debug("/* %s(\"%s\")*/", __func__, strnull(p)); + + assert_se(path_is_valid(p) == ret); + if (ret) + ret = !streq(p, "..") && + !startswith(p, "../") && + !endswith(p, "/..") && + !strstr(p, "/../"); + assert_se(path_is_safe(p) == ret); +} + +static void test_path_is_valid_and_safe(void) { char foo[PATH_MAX+2]; const char *c; log_info("/* %s */", __func__); - assert_se(!path_is_valid("")); - assert_se(path_is_valid("/bar/foo")); - assert_se(path_is_valid("/bar/foo/")); - assert_se(path_is_valid("/bar/foo/")); - assert_se(path_is_valid("//bar//foo//")); - assert_se(path_is_valid("/")); - assert_se(path_is_valid("/////")); - assert_se(path_is_valid("/////.///.////...///..//.")); - assert_se(path_is_valid(".")); - assert_se(path_is_valid("..")); - assert_se(path_is_valid("bar/foo")); - assert_se(path_is_valid("bar/foo/")); - assert_se(path_is_valid("bar//")); + test_path_is_valid_and_safe_one("", false); + test_path_is_valid_and_safe_one("/bar/foo", true); + test_path_is_valid_and_safe_one("/bar/foo/", true); + test_path_is_valid_and_safe_one("/bar/foo/", true); + test_path_is_valid_and_safe_one("//bar//foo//", true); + test_path_is_valid_and_safe_one("/", true); + test_path_is_valid_and_safe_one("/////", true); + test_path_is_valid_and_safe_one("/////.///.////...///..//.", true); + test_path_is_valid_and_safe_one(".", true); + test_path_is_valid_and_safe_one("..", true); + test_path_is_valid_and_safe_one("bar/foo", true); + test_path_is_valid_and_safe_one("bar/foo/", true); + test_path_is_valid_and_safe_one("bar//", true); memset(foo, 'a', sizeof(foo) -1); char_array_0(foo); - assert_se(!path_is_valid(foo)); + test_path_is_valid_and_safe_one(foo, false); c = strjoina("/xxx/", foo, "/yyy"); - assert_se(!path_is_valid(c)); + test_path_is_valid_and_safe_one(c, false); - assert_se(path_is_valid("foo_bar-333")); - assert_se(path_is_valid("o.o")); + test_path_is_valid_and_safe_one("foo_bar-333", true); + test_path_is_valid_and_safe_one("o.o", true); } static void test_hidden_or_backup_file(void) { @@ -875,6 +1083,8 @@ int main(int argc, char **argv) { test_print_paths(); test_path(); + test_path_simplify(); + test_path_compare(); test_path_equal_root(); test_find_executable_full(); test_find_executable(argv[0]); @@ -888,11 +1098,13 @@ int main(int argc, char **argv) { test_path_startswith(); test_prefix_root(); test_file_in_same_dir(); + test_path_find_first_component(); + test_path_find_last_component(); test_last_path_component(); test_path_extract_filename(); test_path_extract_directory(); test_filename_is_valid(); - test_path_is_valid(); + test_path_is_valid_and_safe(); test_hidden_or_backup_file(); test_skip_dev_prefix(); test_empty_or_root(); diff --git a/src/test/test-tmpfile-util.c b/src/test/test-tmpfile-util.c index 83bac15d00..9f2e615867 100644 --- a/src/test/test-tmpfile-util.c +++ b/src/test/test-tmpfile-util.c @@ -26,8 +26,10 @@ static void test_tempfn_random_one(const char *p, const char *extra, const char } static void test_tempfn_random(void) { + log_info("/* %s */", __func__); + test_tempfn_random_one("", NULL, NULL, -EINVAL); - test_tempfn_random_one(".", NULL, NULL, -EINVAL); + test_tempfn_random_one(".", NULL, NULL, -EADDRNOTAVAIL); test_tempfn_random_one("..", NULL, NULL, -EINVAL); test_tempfn_random_one("/", NULL, NULL, -EADDRNOTAVAIL); @@ -35,8 +37,8 @@ static void test_tempfn_random(void) { test_tempfn_random_one("foo", "bar", ".#barfoo", 0); test_tempfn_random_one("/tmp/foo", NULL, "/tmp/.#foo", 0); test_tempfn_random_one("/tmp/foo", "bar", "/tmp/.#barfoo", 0); - test_tempfn_random_one("./foo", NULL, "./.#foo", 0); - test_tempfn_random_one("./foo", "bar", "./.#barfoo", 0); + test_tempfn_random_one("./foo", NULL, ".#foo", 0); + test_tempfn_random_one("./foo", "bar", ".#barfoo", 0); test_tempfn_random_one("../foo", NULL, "../.#foo", 0); test_tempfn_random_one("../foo", "bar", "../.#barfoo", 0); @@ -44,8 +46,8 @@ static void test_tempfn_random(void) { test_tempfn_random_one("foo/", "bar", ".#barfoo", 0); test_tempfn_random_one("/tmp/foo/", NULL, "/tmp/.#foo", 0); test_tempfn_random_one("/tmp/foo/", "bar", "/tmp/.#barfoo", 0); - test_tempfn_random_one("./foo/", NULL, "./.#foo", 0); - test_tempfn_random_one("./foo/", "bar", "./.#barfoo", 0); + test_tempfn_random_one("./foo/", NULL, ".#foo", 0); + test_tempfn_random_one("./foo/", "bar", ".#barfoo", 0); test_tempfn_random_one("../foo/", NULL, "../.#foo", 0); test_tempfn_random_one("../foo/", "bar", "../.#barfoo", 0); } @@ -68,8 +70,10 @@ static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char } static void test_tempfn_xxxxxx(void) { + log_info("/* %s */", __func__); + test_tempfn_xxxxxx_one("", NULL, NULL, -EINVAL); - test_tempfn_xxxxxx_one(".", NULL, NULL, -EINVAL); + test_tempfn_xxxxxx_one(".", NULL, NULL, -EADDRNOTAVAIL); test_tempfn_xxxxxx_one("..", NULL, NULL, -EINVAL); test_tempfn_xxxxxx_one("/", NULL, NULL, -EADDRNOTAVAIL); @@ -77,8 +81,8 @@ static void test_tempfn_xxxxxx(void) { test_tempfn_xxxxxx_one("foo", "bar", ".#barfoo", 0); test_tempfn_xxxxxx_one("/tmp/foo", NULL, "/tmp/.#foo", 0); test_tempfn_xxxxxx_one("/tmp/foo", "bar", "/tmp/.#barfoo", 0); - test_tempfn_xxxxxx_one("./foo", NULL, "./.#foo", 0); - test_tempfn_xxxxxx_one("./foo", "bar", "./.#barfoo", 0); + test_tempfn_xxxxxx_one("./foo", NULL, ".#foo", 0); + test_tempfn_xxxxxx_one("./foo", "bar", ".#barfoo", 0); test_tempfn_xxxxxx_one("../foo", NULL, "../.#foo", 0); test_tempfn_xxxxxx_one("../foo", "bar", "../.#barfoo", 0); @@ -86,8 +90,8 @@ static void test_tempfn_xxxxxx(void) { test_tempfn_xxxxxx_one("foo/", "bar", ".#barfoo", 0); test_tempfn_xxxxxx_one("/tmp/foo/", NULL, "/tmp/.#foo", 0); test_tempfn_xxxxxx_one("/tmp/foo/", "bar", "/tmp/.#barfoo", 0); - test_tempfn_xxxxxx_one("./foo/", NULL, "./.#foo", 0); - test_tempfn_xxxxxx_one("./foo/", "bar", "./.#barfoo", 0); + test_tempfn_xxxxxx_one("./foo/", NULL, ".#foo", 0); + test_tempfn_xxxxxx_one("./foo/", "bar", ".#barfoo", 0); test_tempfn_xxxxxx_one("../foo/", NULL, "../.#foo", 0); test_tempfn_xxxxxx_one("../foo/", "bar", "../.#barfoo", 0); } diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index dc4dc7529d..0bce571fed 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -129,7 +129,7 @@ static void test_unit_name_from_path(void) { test_unit_name_from_path_one("/", ".mount", "-.mount", 0); test_unit_name_from_path_one("///", ".mount", "-.mount", 0); test_unit_name_from_path_one("/foo/../bar", ".mount", NULL, -EINVAL); - test_unit_name_from_path_one("/foo/./bar", ".mount", NULL, -EINVAL); + test_unit_name_from_path_one("/foo/./bar", ".mount", "foo-bar.mount", 0); test_unit_name_from_path_one("/waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ".mount", NULL, -EINVAL); } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 3eac712b0b..071070e9f6 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -445,7 +445,7 @@ static int load_unix_sockets(void) { if (!s) return log_oom(); - path_simplify(s, false); + path_simplify(s); r = set_consume(sockets, s); if (r == -EEXIST) @@ -2779,7 +2779,7 @@ static int parse_line( free_and_replace(i.argument, p); } - path_simplify(i.argument, false); + path_simplify(i.argument); break; case CREATE_CHAR_DEVICE: @@ -2847,7 +2847,7 @@ static int parse_line( "Path '%s' not absolute.", i.path); } - path_simplify(i.path, false); + path_simplify(i.path); if (!should_include_path(i.path)) return 0; diff --git a/src/udev/test-udev-event.c b/src/udev/test-udev-event.c index 584e5c27ed..fd1752c65f 100644 --- a/src/udev/test-udev-event.c +++ b/src/udev/test-udev-event.c @@ -96,7 +96,7 @@ int main(int argc, char *argv[]) { test_event_spawn_cat(false); assert_se(path_make_absolute_cwd(argv[0], &self) >= 0); - path_simplify(self, true); + path_simplify(self); test_event_spawn_self(self, "test1", true); test_event_spawn_self(self, "test1", false);