summaryrefslogtreecommitdiff
path: root/src/basic
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2023-04-20 09:19:22 +0200
committerGitHub <noreply@github.com>2023-04-20 09:19:22 +0200
commit59e4eeed7819c05c57d52e268ab459a00a28ea27 (patch)
treebd909ec00a3b6798078ce7071465d502e819291f /src/basic
parent47041a2b91034b5cce19cb87a5c9a43b25691b23 (diff)
parentc19f1cc9a5ef20f37e890df65fb9b8b95a0b18fa (diff)
downloadsystemd-59e4eeed7819c05c57d52e268ab459a00a28ea27.tar.gz
Merge pull request #27299 from yuwata/chase-absolute
chase: return absolute path when dir_fd points to the root directory
Diffstat (limited to 'src/basic')
-rw-r--r--src/basic/chase.c114
-rw-r--r--src/basic/chase.h2
-rw-r--r--src/basic/fd-util.c22
-rw-r--r--src/basic/mountpoint-util.c6
-rw-r--r--src/basic/os-util.c4
-rw-r--r--src/basic/path-util.c28
-rw-r--r--src/basic/path-util.h1
7 files changed, 109 insertions, 68 deletions
diff --git a/src/basic/chase.c b/src/basic/chase.c
index eb4bda07a6..373252b645 100644
--- a/src/basic/chase.c
+++ b/src/basic/chase.c
@@ -111,12 +111,26 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
* given directory file descriptor, even if it is absolute. If the given directory file descriptor is
* AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host.
*
- * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this
- * functions returns a relative path in "ret_path" because openat() like functions generally ignore
- * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is
- * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because
- * otherwise, if the caller passes the returned relative path to another openat() like function, it
- * would be resolved relative to the current working directory instead of to "/".
+ * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function
+ * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat()
+ * like functions generally ignore the directory fd if they are provided with an absolute path. When
+ * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file
+ * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When
+ * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path"
+ * because otherwise, if the caller passes the returned relative path to another openat() like
+ * function, it would be resolved relative to the current working directory instead of to "/".
+ *
+ * Summary about the result path:
+ * - "dir_fd" points to the root directory
+ * → result will be absolute
+ * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set
+ * → relative
+ * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set
+ * → relative when all resolved symlinks are relative, otherwise absolute
+ * - "dir_fd" is AT_FDCWD, and "path" is absolute
+ * → absolute
+ * - "dir_fd" is AT_FDCWD, and "path" is relative
+ * → relative when all resolved symlinks are relative, otherwise absolute
*
* Algorithmically this operates on two path buffers: "done" are the components of the path we
* already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we
@@ -190,8 +204,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
return -ENOMEM;
/* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because
- * a relative path would be interpreted relative to the current working directory. */
- bool need_absolute = dir_fd == AT_FDCWD && path_is_absolute(path);
+ * a relative path would be interpreted relative to the current working directory. Also, let's make
+ * the result absolute when the file descriptor of the root directory is specified. */
+ bool need_absolute = (dir_fd == AT_FDCWD && path_is_absolute(path)) || dir_fd_is_root(dir_fd) > 0;
if (need_absolute) {
done = strdup("/");
if (!done)
@@ -373,6 +388,11 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
unsafe_transition(&st_child, &st))
return log_unsafe_transition(child, fd, path, flags);
+ /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be
+ * outside of the specified dir_fd. Let's make the result absolute. */
+ if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
+ need_absolute = true;
+
r = free_and_strdup(&done, need_absolute ? "/" : NULL);
if (r < 0)
return r;
@@ -474,8 +494,8 @@ chased_one:
return 0;
}
-int chase(const char *path, const char *original_root, ChaseFlags flags, char **ret_path, int *ret_fd) {
- _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL;
+int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
+ _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
_cleanup_close_ int fd = -EBADF, pfd = -EBADF;
int r;
@@ -484,18 +504,17 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char **
if (isempty(path))
return -EINVAL;
- /* A root directory of "/" or "" is identical to none */
- if (empty_or_root(original_root))
- original_root = NULL;
-
- if (original_root) {
- r = path_make_absolute_cwd(original_root, &root);
+ /* A root directory of "/" or "" is identical to "/". */
+ if (empty_or_root(root))
+ root = "/";
+ else {
+ r = path_make_absolute_cwd(root, &root_abs);
if (r < 0)
return r;
/* Simplify the root directory, so that it has no duplicate slashes and nothing at the
* end. While we won't resolve the root path we still simplify it. */
- path_simplify(root);
+ root = path_simplify(root_abs);
assert(path_is_absolute(root));
assert(!empty_or_root(root));
@@ -515,14 +534,14 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char **
return r;
}
- path = path_startswith(absolute, empty_to_root(root));
+ path = path_startswith(absolute, root);
if (!path)
return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG,
SYNTHETIC_ERRNO(ECHRNG),
"Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
- absolute, empty_to_root(root));
+ absolute, root);
- fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
+ fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
@@ -532,19 +551,27 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char **
if (ret_path) {
if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
- _cleanup_free_ char *q = NULL;
- q = path_join(empty_to_root(root), p);
- if (!q)
- return -ENOMEM;
+ /* When "root" points to the root directory, the result of chaseat() is always
+ * absolute, hence it is not necessary to prefix with the root. When "root" points to
+ * a non-root directory, the result path is always normalized and relative, hence
+ * we can simply call path_join() and not necessary to call path_simplify().
+ * Note that the result of chaseat() may start with "." (more specifically, it may be
+ * "." or "./"), and we need to drop "." in that case. */
+
+ if (empty_or_root(root))
+ assert(path_is_absolute(p));
+ else {
+ char *q;
- path_simplify(q);
+ assert(!path_is_absolute(p));
- if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/."))
- if (!strextend(&q, "/"))
+ q = path_join(root, p + (*p == '.'));
+ if (!q)
return -ENOMEM;
- free_and_replace(p, q);
+ free_and_replace(p, q);
+ }
}
*ret_path = TAKE_PTR(p);
@@ -556,6 +583,37 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char **
return r;
}
+int chaseat_prefix_root(const char *path, const char *root, char **ret) {
+ char *q;
+ int r;
+
+ assert(path);
+ assert(ret);
+
+ /* This is mostly for prefixing the result of chaseat(). */
+
+ if (!path_is_absolute(path)) {
+ _cleanup_free_ char *root_abs = NULL;
+
+ /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
+ assert(!empty_or_root(root));
+
+ r = path_make_absolute_cwd(root, &root_abs);
+ if (r < 0)
+ return r;
+
+ root = path_simplify(root_abs);
+
+ q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0')));
+ } else
+ q = strdup(path);
+ if (!q)
+ return -ENOMEM;
+
+ *ret = q;
+ return 0;
+}
+
int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL, *fname = NULL;
diff --git a/src/basic/chase.h b/src/basic/chase.h
index 40121f7d70..f37e836822 100644
--- a/src/basic/chase.h
+++ b/src/basic/chase.h
@@ -42,6 +42,8 @@ bool unsafe_transition(const struct stat *a, const struct stat *b);
int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd);
+int chaseat_prefix_root(const char *path, const char *root, char **ret);
+
int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path);
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index 7125e28e1b..974a7aac65 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -911,10 +911,23 @@ int dir_fd_is_root(int dir_fd) {
if (!statx_inode_same(&st.sx, &pst.sx))
return false;
+ /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+ * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+ * following could be used to trick us:
+ *
+ * $ mkdir /tmp/x /tmp/x/y
+ * $ mount --bind /tmp/x /tmp/x/y
+ *
+ * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old
+ * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious
+ * mount points in an early boot stage, and silently skip the following check. */
+
if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
int mntid;
r = path_get_mnt_id_at(dir_fd, "", &mntid);
+ if (r == -ENOSYS)
+ return true; /* skip the mount ID check */
if (r < 0)
return r;
assert(mntid >= 0);
@@ -927,6 +940,8 @@ int dir_fd_is_root(int dir_fd) {
int mntid;
r = path_get_mnt_id_at(dir_fd, "..", &mntid);
+ if (r == -ENOSYS)
+ return true; /* skip the mount ID check */
if (r < 0)
return r;
assert(mntid >= 0);
@@ -935,13 +950,6 @@ int dir_fd_is_root(int dir_fd) {
pst.nsx.stx_mask |= STATX_MNT_ID;
}
- /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
- * and we also need to check that the mount ids are the same. Otherwise, a construct like the
- * following could be used to trick us:
- *
- * $ mkdir /tmp/x /tmp/x/y
- * $ mount --bind /tmp/x /tmp/x/y
- */
return statx_mount_same(&st.nsx, &pst.nsx);
}
diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c
index db36b2f2f6..e28ca825b3 100644
--- a/src/basic/mountpoint-util.c
+++ b/src/basic/mountpoint-util.c
@@ -123,7 +123,7 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mn
r = read_full_virtual_file(path, &fdinfo, NULL);
if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
- return -EOPNOTSUPP;
+ return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS;
if (r < 0)
return r;
@@ -280,7 +280,7 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
fallback_fdinfo:
r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
- if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM))
+ if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM, -ENOSYS))
goto fallback_fstat;
if (r < 0)
return r;
@@ -549,6 +549,8 @@ int dev_is_devtmpfs(void) {
return r;
r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
+ if (r == -ENOENT)
+ return proc_mounted() > 0 ? -ENOENT : -ENOSYS;
if (r < 0)
return r;
diff --git a/src/basic/os-util.c b/src/basic/os-util.c
index dd8faf2376..5d06e20871 100644
--- a/src/basic/os-util.c
+++ b/src/basic/os-util.c
@@ -155,7 +155,7 @@ int open_os_release(const char *root, char **ret_path, int *ret_fd) {
return r;
if (ret_path) {
- r = path_prefix_root_cwd(p, root, ret_path);
+ r = chaseat_prefix_root(p, root, ret_path);
if (r < 0)
return r;
}
@@ -292,7 +292,7 @@ int open_extension_release(
return r;
if (ret_path) {
- r = path_prefix_root_cwd(p, root, ret_path);
+ r = chaseat_prefix_root(p, root, ret_path);
if (r < 0)
return r;
}
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index fa2e26789f..0b0f0da760 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -100,34 +100,6 @@ int path_make_absolute_cwd(const char *p, char **ret) {
return 0;
}
-int path_prefix_root_cwd(const char *p, const char *root, char **ret) {
- _cleanup_free_ char *root_abs = NULL;
- char *c;
- int r;
-
- assert(p);
- assert(ret);
-
- /* Unlike path_make_absolute(), this always prefixes root path if specified.
- * The root path is always simplified, but the provided path will not.
- * This is useful for prefixing the result of chaseat(). */
-
- if (empty_or_root(root))
- return path_make_absolute_cwd(p, ret);
-
- r = path_make_absolute_cwd(root, &root_abs);
- if (r < 0)
- return r;
-
- path_simplify(root_abs);
- c = path_join(root_abs, p);
- if (!c)
- return -ENOMEM;
-
- *ret = c;
- return 0;
-}
-
int path_make_relative(const char *from, const char *to, char **ret) {
_cleanup_free_ char *result = NULL;
unsigned n_parents;
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index a0af9de674..7843599816 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -60,7 +60,6 @@ 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_prefix_root_cwd(const char *p, const char *root, char **ret);
int path_make_relative(const char *from, const char *to, char **ret);
int path_make_relative_parent(const char *from_child, const char *to, char **ret);
char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_;