]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: add new CHASE_STEP flag to chase_symlinks()
authorLennart Poettering <lennart@poettering.net>
Wed, 4 Apr 2018 15:03:45 +0000 (17:03 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 18 Apr 2018 12:15:48 +0000 (14:15 +0200)
If the flag is set only a single step of the normalization is executed,
and the resulting path is returned.

This allows callers to normalize piecemeal, taking into account every
single intermediary path of the normalization.

src/basic/fs-util.c
src/basic/fs-util.h
src/test/test-fs-util.c

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