fs-util: add new CHASE_STEP flag to chase_symlinks()

If the flag is set only a single step of the normalization is executed,
and the resulting path is returned.

This allows callers to normalize piecemeal, taking into account every
single intermediary path of the normalization.
This commit is contained in:
Lennart Poettering 2018-04-04 17:03:45 +02:00
parent 12777909c9
commit 49eb36596b
3 changed files with 106 additions and 3 deletions

View 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(

View 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);

View 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);
}