diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index f929db9c23..b2ac648838 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -732,7 +732,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races - * at a minimum. + * to a minimum. * * 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 @@ -742,9 +742,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * 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. + * 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 wasn'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 @@ -760,13 +760,13 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, * * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from * unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If - * CHASE_WARN is also set a warning describing the unsafe transition is emitted. + * CHASE_WARN is also set, a warning describing the unsafe transition is emitted. * - * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, the path normalization is - * aborted and -EREMOTE is returned. If CHASE_WARN is also set a warning showing the path of the mount point - * is emitted. + * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization + * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of + * the mount point is emitted. * - * */ + */ /* A root directory of "/" or "" is identical to none */ if (empty_or_root(original_root)) diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index c7c5899815..1f0bdd95b3 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -74,14 +74,16 @@ union inotify_event_buffer { int inotify_add_watch_fd(int fd, int what, uint32_t mask); enum { - CHASE_PREFIX_ROOT = 1 << 0, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */ - CHASE_NONEXISTENT = 1 << 1, /* If set, it's OK if the path doesn't actually exist. */ - CHASE_NO_AUTOFS = 1 << 2, /* If set, return -EREMOTE if autofs mount point found */ - CHASE_SAFE = 1 << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */ - CHASE_OPEN = 1 << 4, /* If set, return an O_PATH object to the final component */ - CHASE_TRAIL_SLASH = 1 << 5, /* If set, any trailing slash will be preserved */ - CHASE_STEP = 1 << 6, /* If set, just execute a single step of the normalization */ - CHASE_NOFOLLOW = 1 << 7, /* Only valid with CHASE_OPEN: when the path's right-most component refers to symlink return O_PATH fd of the symlink, rather than following it. */ + CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */ + CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */ + CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */ + CHASE_SAFE = 1 << 3, /* Return EPERM if we ever traverse from unprivileged to privileged files or directories */ + CHASE_OPEN = 1 << 4, /* Return an O_PATH object to the final component */ + CHASE_TRAIL_SLASH = 1 << 5, /* Any trailing slash will be preserved */ + CHASE_STEP = 1 << 6, /* Just execute a single step of the normalization */ + CHASE_NOFOLLOW = 1 << 7, /* Do not follow the path's right-most compontent. With CHASE_OPEN, when + * the path's right-most component refers to symlink, return O_PATH fd of + * the symlink. */ CHASE_WARN = 1 << 8, /* Emit an appropriate warning when an error is encountered */ }; diff --git a/src/test/meson.build b/src/test/meson.build index e337e50146..0595cfe37a 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -627,6 +627,11 @@ tests += [ [], []], + [['src/test/test-chase-symlinks.c'], + [], + [], + '', 'manual'], + [['src/test/test-path.c', 'src/test/test-helper.c'], [libcore, diff --git a/src/test/test-chase-symlinks.c b/src/test/test-chase-symlinks.c new file mode 100644 index 0000000000..3fac6b6bc5 --- /dev/null +++ b/src/test/test-chase-symlinks.c @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#include + +#include "log.h" +#include "fs-util.h" +#include "main-func.h" + +static char *arg_root = NULL; +static int arg_flags = 0; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_ROOT = 0x1000, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "root", required_argument, NULL, ARG_ROOT }, + + { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT }, + { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT }, + { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS }, + { "safe", no_argument, NULL, CHASE_SAFE }, + { "open", no_argument, NULL, CHASE_OPEN }, + { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH }, + { "step", no_argument, NULL, CHASE_STEP }, + { "nofollow", no_argument, NULL, CHASE_NOFOLLOW }, + { "warn", no_argument, NULL, CHASE_WARN }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) + switch (c) { + + case 'h': + printf("Syntax:\n" + " %s [OPTION...] path...\n" + "Options:\n" + , argv[0]); + for (size_t i = 0; i < ELEMENTSOF(options) - 1; i++) + printf(" --%s\n", options[i].name); + return 0; + + case ARG_ROOT: + arg_root = optarg; + break; + + case CHASE_PREFIX_ROOT: + case CHASE_NONEXISTENT: + case CHASE_NO_AUTOFS: + case CHASE_SAFE: + case CHASE_OPEN: + case CHASE_TRAIL_SLASH: + case CHASE_STEP: + case CHASE_NOFOLLOW: + case CHASE_WARN: + arg_flags |= c; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind == argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); + + return 1; +} + +static int run(int argc, char **argv) { + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + for (int i = optind; i < argc; i++) { + _cleanup_free_ char *p = NULL; + + printf("%s ", argv[i]); + fflush(stdout); + + r = chase_symlinks(argv[i], arg_root, arg_flags, &p); + if (r < 0) + log_error_errno(r, "failed: %m"); + else + log_info("→ %s", p); + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run);