summaryrefslogtreecommitdiff
path: root/src/basic
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic')
-rw-r--r--src/basic/architecture.h4
-rw-r--r--src/basic/chase-symlinks.h44
-rw-r--r--src/basic/chase.c (renamed from src/basic/chase-symlinks.c)578
-rw-r--r--src/basic/chase.h63
-rw-r--r--src/basic/compress.c22
-rw-r--r--src/basic/compress.h11
-rw-r--r--src/basic/conf-files.c149
-rw-r--r--src/basic/conf-files.h3
-rw-r--r--src/basic/coverage.h32
-rw-r--r--src/basic/devnum-util.c6
-rw-r--r--src/basic/devnum-util.h4
-rw-r--r--src/basic/env-file.c45
-rw-r--r--src/basic/env-file.h2
-rw-r--r--src/basic/env-util.c42
-rw-r--r--src/basic/env-util.h2
-rw-r--r--src/basic/fd-util.c81
-rw-r--r--src/basic/fd-util.h14
-rw-r--r--src/basic/fileio.c232
-rw-r--r--src/basic/fileio.h35
-rw-r--r--src/basic/fs-util.c88
-rw-r--r--src/basic/fs-util.h8
-rw-r--r--src/basic/getopt-defs.h75
-rw-r--r--src/basic/list.h9
-rw-r--r--src/basic/lock-util.c57
-rw-r--r--src/basic/lock-util.h18
-rw-r--r--src/basic/log.c165
-rw-r--r--src/basic/log.h48
-rw-r--r--src/basic/logarithm.h53
-rw-r--r--src/basic/macro.h2
-rw-r--r--src/basic/meson.build2
-rw-r--r--src/basic/missing_fcntl.h9
-rw-r--r--src/basic/mkdir.c13
-rw-r--r--src/basic/mountpoint-util.c57
-rw-r--r--src/basic/nulstr-util.c6
-rw-r--r--src/basic/nulstr-util.h5
-rw-r--r--src/basic/origin-id.h36
-rw-r--r--src/basic/os-util.c383
-rw-r--r--src/basic/os-util.h52
-rw-r--r--src/basic/parse-util.c4
-rw-r--r--src/basic/path-util.c61
-rw-r--r--src/basic/path-util.h9
-rw-r--r--src/basic/proc-cmdline.c326
-rw-r--r--src/basic/proc-cmdline.h4
-rw-r--r--src/basic/process-util.c70
-rw-r--r--src/basic/process-util.h2
-rw-r--r--src/basic/ratelimit.c29
-rw-r--r--src/basic/ratelimit.h2
-rw-r--r--src/basic/socket-util.c20
-rw-r--r--src/basic/socket-util.h14
-rw-r--r--src/basic/stat-util.c68
-rw-r--r--src/basic/stat-util.h6
-rw-r--r--src/basic/string-table.h1
-rw-r--r--src/basic/string-util.c35
-rw-r--r--src/basic/string-util.h19
-rw-r--r--src/basic/strv.c11
-rw-r--r--src/basic/strv.h15
-rw-r--r--src/basic/terminal-util.c2
-rw-r--r--src/basic/tmpfile-util.c21
-rw-r--r--src/basic/tmpfile-util.h11
-rw-r--r--src/basic/unit-def.c45
-rw-r--r--src/basic/unit-def.h3
-rw-r--r--src/basic/unit-file.c23
-rw-r--r--src/basic/user-util.c22
-rw-r--r--src/basic/user-util.h4
-rw-r--r--src/basic/virt.c6
65 files changed, 2240 insertions, 1048 deletions
diff --git a/src/basic/architecture.h b/src/basic/architecture.h
index 096526a857..788f3abc37 100644
--- a/src/basic/architecture.h
+++ b/src/basic/architecture.h
@@ -197,10 +197,10 @@ Architecture uname_architecture(void);
# elif defined(__SH4A__)
# define LIB_ARCH_TUPLE "sh4a-linux-gnu"
# endif
-#elif defined(__loongarch64)
+#elif defined(__loongarch_lp64)
# define native_architecture() ARCHITECTURE_LOONGARCH64
# if defined(__loongarch_double_float)
-# define LIB_ARCH_TUPLE "loongarch64-linux-gnuf64"
+# define LIB_ARCH_TUPLE "loongarch64-linux-gnu"
# elif defined(__loongarch_single_float)
# define LIB_ARCH_TUPLE "loongarch64-linux-gnuf32"
# elif defined(__loongarch_soft_float)
diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h
deleted file mode 100644
index 448471fa6d..0000000000
--- a/src/basic/chase-symlinks.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <dirent.h>
-#include <stdio.h>
-
-#include "stat-util.h"
-
-typedef enum ChaseSymlinksFlags {
- 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_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */
- CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */
- CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's
- * right-most component refers to symlink, return O_PATH fd of the symlink. */
- CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered.
- * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
- CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
- * relative to the given directory fd instead of root. */
- CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
- CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the
- * full path is still stored in ret_path and only the returned
- * file descriptor will point to the parent directory. */
- CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. */
-} ChaseSymlinksFlags;
-
-bool unsafe_transition(const struct stat *a, const struct stat *b);
-
-/* How many iterations to execute before returning -ELOOP */
-#define CHASE_SYMLINKS_MAX 32
-
-int chase_symlinks(const char *path_with_prefix, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, int *ret_fd);
-
-int chase_symlinks_and_open(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
-int chase_symlinks_and_opendir(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, DIR **ret_dir);
-int chase_symlinks_and_stat(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat);
-int chase_symlinks_and_access(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path);
-int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseSymlinksFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
-int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path);
-
-int chase_symlinks_at(int dir_fd, const char *path, ChaseSymlinksFlags flags, char **ret_path, int *ret_fd);
-int chase_symlinks_at_and_open(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
diff --git a/src/basic/chase-symlinks.c b/src/basic/chase.c
index 5c2b56ea20..373252b645 100644
--- a/src/basic/chase-symlinks.c
+++ b/src/basic/chase.c
@@ -3,7 +3,7 @@
#include <linux/magic.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -24,7 +24,7 @@ bool unsafe_transition(const struct stat *a, const struct stat *b) {
return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
}
-static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFlags flags) {
+static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
_cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
struct stat st;
@@ -44,7 +44,7 @@ static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFl
strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
}
-static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags flags) {
+static int log_autofs_mount_point(int fd, const char *path, ChaseFlags flags) {
_cleanup_free_ char *n1 = NULL;
if (!FLAGS_SET(flags, CHASE_WARN))
@@ -57,7 +57,7 @@ static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags f
strna(n1), path);
}
-static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
+static int log_prohibited_symlink(int fd, ChaseFlags flags) {
_cleanup_free_ char *n1 = NULL;
assert(fd >= 0);
@@ -72,37 +72,34 @@ static int log_prohibited_symlink(int fd, ChaseSymlinksFlags flags) {
strna(n1));
}
-int chase_symlinks_at(
- int dir_fd,
- const char *path,
- ChaseSymlinksFlags flags,
- char **ret_path,
- int *ret_fd) {
-
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
_cleanup_free_ char *buffer = NULL, *done = NULL;
_cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
- unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
+ unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
bool exists = true, append_trail_slash = false;
- struct stat previous_stat;
+ struct stat st; /* stat obtained from fd */
const char *todo;
int r;
assert(path);
assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
+ assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
+ assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME));
+ assert(!FLAGS_SET(flags, CHASE_MKDIR_0755) || (flags & (CHASE_NONEXISTENT | CHASE_PARENT)) != 0);
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
/* Either the file may be missing, or we return an fd to the final object, but both make no sense */
- if ((flags & CHASE_NONEXISTENT))
+ if (FLAGS_SET(flags, CHASE_NONEXISTENT))
assert(!ret_fd);
- if ((flags & CHASE_STEP))
+ if (FLAGS_SET(flags, CHASE_STEP))
assert(!ret_fd);
if (isempty(path))
path = ".";
/* This function resolves symlinks of the path relative to the given directory file descriptor. If
- * CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
+ * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
* are resolved relative to the given directory file descriptor. Otherwise, they are resolved
* relative to the root directory of the host.
*
@@ -114,12 +111,26 @@ int chase_symlinks_at(
* 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
@@ -162,6 +173,17 @@ int chase_symlinks_at(
* the mount point is emitted. CHASE_WARN cannot be used in PID 1.
*/
+ if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
+ /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to
+ * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */
+
+ r = dir_fd_is_root_or_cwd(dir_fd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
+ }
+
if (!(flags &
(CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP|
CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) &&
@@ -169,7 +191,7 @@ int chase_symlinks_at(
/* Shortcut the ret_fd 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 = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
+ r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
if (r < 0)
return -errno;
@@ -177,15 +199,14 @@ int chase_symlinks_at(
return 0;
}
- if (!buffer) {
- buffer = strdup(path);
- if (!buffer)
- return -ENOMEM;
- }
+ buffer = strdup(path);
+ if (!buffer)
+ 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)
@@ -193,7 +214,7 @@ int chase_symlinks_at(
}
/* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
- * directory file descriptor is provided will we look at CHASE_AT_RESOLVE_IN_ROOT to determine
+ * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
* whether to resolve symlinks in it or not. */
if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
@@ -213,16 +234,16 @@ int chase_symlinks_at(
if (fd < 0)
return -errno;
- if (fstat(fd, &previous_stat) < 0)
+ if (fstat(fd, &st) < 0)
return -errno;
- if (flags & CHASE_TRAIL_SLASH)
+ if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
append_trail_slash = ENDSWITH_SET(buffer, "/", "/.");
for (todo = buffer;;) {
_cleanup_free_ char *first = NULL;
_cleanup_close_ int child = -EBADF;
- struct stat st;
+ struct stat st_child;
const char *e;
r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
@@ -243,6 +264,7 @@ int chase_symlinks_at(
if (path_equal(first, "..")) {
_cleanup_free_ char *parent = NULL;
_cleanup_close_ int fd_parent = -EBADF;
+ struct stat st_parent;
/* If we already are at the top, then going up will not change anything. This is
* in-line with how the kernel handles this. */
@@ -253,13 +275,19 @@ int chase_symlinks_at(
if (fd_parent < 0)
return -errno;
- if (fstat(fd_parent, &st) < 0)
+ if (fstat(fd_parent, &st_parent) < 0)
return -errno;
- /* If we opened the same directory, that means we're at the host root directory, so
+ /* If we opened the same directory, that _may_ indicate that we're at the host root
+ * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
* going up won't change anything. */
- if (st.st_dev == previous_stat.st_dev && st.st_ino == previous_stat.st_ino)
- continue;
+ if (stat_inode_same(&st_parent, &st)) {
+ r = dir_fd_is_root(fd);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+ }
r = path_extract_directory(done, &parent);
if (r >= 0 || r == -EDESTADDRREQ)
@@ -271,21 +299,19 @@ int chase_symlinks_at(
} else
return r;
- if (flags & CHASE_STEP)
+ if (FLAGS_SET(flags, CHASE_STEP))
goto chased_one;
- if (flags & CHASE_SAFE) {
- if (unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(fd, fd_parent, path, flags);
-
- previous_stat = st;
- }
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st, &st_parent))
+ return log_unsafe_transition(fd, fd_parent, path, flags);
if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
break;
+ /* update fd and stat */
+ st = st_parent;
close_and_replace(fd, fd_parent);
-
continue;
}
@@ -299,7 +325,7 @@ int chase_symlinks_at(
return r;
if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
- child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755);
+ child = xopenat(fd, first, O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, 0755);
if (child < 0)
return child;
} else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
@@ -307,7 +333,7 @@ int chase_symlinks_at(
return -ENOMEM;
break;
- } else if (flags & CHASE_NONEXISTENT) {
+ } else if (FLAGS_SET(flags, CHASE_NONEXISTENT)) {
if (!path_extend(&done, first, todo))
return -ENOMEM;
@@ -317,22 +343,21 @@ int chase_symlinks_at(
return r;
}
- if (fstat(child, &st) < 0)
+ if (fstat(child, &st_child) < 0)
return -errno;
- if ((flags & CHASE_SAFE) &&
- unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(fd, child, path, flags);
- previous_stat = st;
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st, &st_child))
+ return log_unsafe_transition(fd, child, path, flags);
- if ((flags & CHASE_NO_AUTOFS) &&
+ if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
return log_autofs_mount_point(child, path, flags);
- if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
+ if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
_cleanup_free_ char *destination = NULL;
- if (flags & CHASE_PROHIBIT_SYMLINKS)
+ if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
return log_prohibited_symlink(child, flags);
/* This is a symlink, in this case read the destination. But let's make sure we
@@ -356,15 +381,17 @@ int chase_symlinks_at(
if (fd < 0)
return fd;
- if (flags & CHASE_SAFE) {
- if (fstat(fd, &st) < 0)
- return -errno;
+ if (fstat(fd, &st) < 0)
+ return -errno;
- if (unsafe_transition(&previous_stat, &st))
- return log_unsafe_transition(child, fd, path, flags);
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st_child, &st))
+ return log_unsafe_transition(child, fd, path, flags);
- previous_stat = st;
- }
+ /* 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)
@@ -379,7 +406,7 @@ int chase_symlinks_at(
free_and_replace(buffer, destination);
todo = buffer;
- if (flags & CHASE_STEP)
+ if (FLAGS_SET(flags, CHASE_STEP))
goto chased_one;
continue;
@@ -393,16 +420,28 @@ int chase_symlinks_at(
break;
/* And iterate again, but go one directory further down. */
+ st = st_child;
close_and_replace(fd, child);
}
- if (flags & CHASE_PARENT) {
- r = fd_verify_directory(fd);
+ if (FLAGS_SET(flags, CHASE_PARENT)) {
+ r = stat_verify_directory(&st);
if (r < 0)
return r;
}
if (ret_path) {
+ if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) {
+ _cleanup_free_ char *f = NULL;
+
+ r = path_extract_filename(done, &f);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+
+ /* If we get EADDRNOTAVAIL we clear done and it will get reinitialized by the next block. */
+ free_and_replace(done, f);
+ }
+
if (!done) {
done = strdup(append_trail_slash ? "./" : ".");
if (!done)
@@ -420,7 +459,7 @@ int chase_symlinks_at(
*ret_fd = TAKE_FD(fd);
}
- if (flags & CHASE_STEP)
+ if (FLAGS_SET(flags, CHASE_STEP))
return 1;
return exists;
@@ -455,14 +494,8 @@ chased_one:
return 0;
}
-int chase_symlinks(
- const char *path,
- const char *original_root,
- ChaseSymlinksFlags 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;
@@ -471,28 +504,28 @@ int chase_symlinks(
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. Note that dropping the
- * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
- * 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);
-
- if (flags & CHASE_PREFIX_ROOT) {
+ * end. While we won't resolve the root path we still simplify it. */
+ root = path_simplify(root_abs);
+
+ assert(path_is_absolute(root));
+ assert(!empty_or_root(root));
+
+ if (FLAGS_SET(flags, CHASE_PREFIX_ROOT)) {
absolute = path_join(root, path);
if (!absolute)
return -ENOMEM;
}
+
+ flags |= CHASE_AT_RESOLVE_IN_ROOT;
}
if (!absolute) {
@@ -501,38 +534,47 @@ int chase_symlinks(
return r;
}
- path = path_startswith(absolute, empty_to_root(root));
+ path = path_startswith(absolute, root);
if (!path)
- 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.",
- absolute, empty_to_root(root));
+ 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, 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;
- flags |= CHASE_AT_RESOLVE_IN_ROOT;
- flags &= ~CHASE_PREFIX_ROOT;
-
- r = chase_symlinks_at(fd, path, flags, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
+ r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL);
if (r < 0)
return r;
if (ret_path) {
- _cleanup_free_ char *q = NULL;
+ if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) {
- 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. */
- path_simplify(q);
+ if (empty_or_root(root))
+ assert(path_is_absolute(p));
+ else {
+ char *q;
- if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/."))
- if (!strextend(&q, "/"))
- return -ENOMEM;
+ assert(!path_is_absolute(p));
+
+ q = path_join(root, p + (*p == '.'));
+ if (!q)
+ return -ENOMEM;
+
+ free_and_replace(p, q);
+ }
+ }
- *ret_path = TAKE_PTR(q);
+ *ret_path = TAKE_PTR(p);
}
if (ret_fd)
@@ -541,13 +583,38 @@ int chase_symlinks(
return r;
}
-int chase_symlinks_and_open(
- const char *path,
- const char *root,
- ChaseSymlinksFlags chase_flags,
- int open_flags,
- char **ret_path) {
+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;
mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
@@ -563,7 +630,7 @@ int chase_symlinks_and_open(
open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
mode));
- r = chase_symlinks(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
+ r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
@@ -572,14 +639,13 @@ int chase_symlinks_and_open(
if (isempty(q))
q = ".";
- r = path_extract_filename(q, &fname);
- if (r < 0 && r != -EADDRNOTAVAIL)
- return r;
+ if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
+ r = path_extract_filename(q, &fname);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+ }
- if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EADDRNOTAVAIL)
- r = fd_reopen(path_fd, open_flags);
- else
- r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
+ r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
if (r < 0)
return r;
@@ -589,13 +655,7 @@ int chase_symlinks_and_open(
return r;
}
-int chase_symlinks_and_opendir(
- const char *path,
- const char *root,
- ChaseSymlinksFlags chase_flags,
- char **ret_path,
- DIR **ret_dir) {
-
+int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
DIR *d;
@@ -615,7 +675,7 @@ int chase_symlinks_and_opendir(
return 0;
}
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
@@ -631,13 +691,7 @@ int chase_symlinks_and_opendir(
return 0;
}
-int chase_symlinks_and_stat(
- const char *path,
- const char *root,
- ChaseSymlinksFlags chase_flags,
- char **ret_path,
- struct stat *ret_stat) {
-
+int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
@@ -647,16 +701,12 @@ int chase_symlinks_and_stat(
assert(ret_stat);
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
/* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
- if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
- return -errno;
-
- return 1;
- }
-
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
@@ -667,16 +717,10 @@ int chase_symlinks_and_stat(
if (ret_path)
*ret_path = TAKE_PTR(p);
- return 1;
+ return 0;
}
-int chase_symlinks_and_access(
- const char *path,
- const char *root,
- ChaseSymlinksFlags chase_flags,
- int access_mode,
- char **ret_path) {
-
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
@@ -685,16 +729,12 @@ int chase_symlinks_and_access(
assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
/* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
- if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
- return -errno;
-
- return 1;
- }
-
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
@@ -706,13 +746,13 @@ int chase_symlinks_and_access(
if (ret_path)
*ret_path = TAKE_PTR(p);
- return 1;
+ return 0;
}
-int chase_symlinks_and_fopen_unlocked(
+int chase_and_fopen_unlocked(
const char *path,
const char *root,
- ChaseSymlinksFlags chase_flags,
+ ChaseFlags chase_flags,
const char *open_flags,
char **ret_path,
FILE **ret_file) {
@@ -730,7 +770,7 @@ int chase_symlinks_and_fopen_unlocked(
if (mode_flags < 0)
return mode_flags;
- fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
+ fd = chase_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
if (fd < 0)
return fd;
@@ -744,13 +784,7 @@ int chase_symlinks_and_fopen_unlocked(
return 0;
}
-int chase_symlinks_and_unlink(
- const char *path,
- const char *root,
- ChaseSymlinksFlags chase_flags,
- int unlink_flags,
- char **ret_path) {
-
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
_cleanup_free_ char *p = NULL, *fname = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
@@ -758,7 +792,7 @@ int chase_symlinks_and_unlink(
assert(path);
assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
- fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
+ fd = chase_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
if (fd < 0)
return fd;
@@ -775,13 +809,19 @@ int chase_symlinks_and_unlink(
return 0;
}
-int chase_symlinks_at_and_open(
- int dir_fd,
- const char *path,
- ChaseSymlinksFlags chase_flags,
- int open_flags,
- char **ret_path) {
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename) {
+ int pfd, r;
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+ r = chase(path, root, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
+ if (r < 0)
+ return r;
+
+ return pfd;
+}
+
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL, *fname = NULL;
mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
@@ -796,18 +836,17 @@ int chase_symlinks_at_and_open(
open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
mode));
- r = chase_symlinks_at(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
+ r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
if (r < 0)
return r;
- r = path_extract_filename(p, &fname);
- if (r < 0 && r != -EDESTADDRREQ)
- return r;
+ if (!FLAGS_SET(chase_flags, CHASE_PARENT)) {
+ r = path_extract_filename(p, &fname);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+ }
- if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EDESTADDRREQ)
- r = fd_reopen(path_fd, open_flags);
- else
- r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
+ r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, mode);
if (r < 0)
return r;
@@ -817,3 +856,168 @@ int chase_symlinks_at_and_open(
return r;
}
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) {
+ _cleanup_close_ int path_fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ DIR *d;
+ int r;
+
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+ assert(ret_dir);
+
+ if (dir_fd == AT_FDCWD && !ret_path &&
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ /* Shortcut this call if none of the special features of this call are requested */
+ d = opendir(path);
+ if (!d)
+ return -errno;
+
+ *ret_dir = d;
+ return 0;
+ }
+
+ r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
+
+ d = xopendirat(path_fd, ".", O_NOFOLLOW);
+ if (!d)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ *ret_dir = d;
+ return 0;
+}
+
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) {
+ _cleanup_close_ int path_fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+ assert(ret_stat);
+
+ if (dir_fd == AT_FDCWD && !ret_path &&
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
+ /* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
+
+ r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
+
+ if (fstat(path_fd, ret_stat) < 0)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ return 0;
+}
+
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) {
+ _cleanup_close_ int path_fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+ if (dir_fd == AT_FDCWD && !ret_path &&
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
+ /* Shortcut this call if none of the special features of this call are requested */
+ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode,
+ FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0));
+
+ r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
+
+ r = access_fd(path_fd, access_mode);
+ if (r < 0)
+ return r;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ return 0;
+}
+
+int chase_and_fopenat_unlocked(
+ int dir_fd,
+ const char *path,
+ ChaseFlags chase_flags,
+ const char *open_flags,
+ char **ret_path,
+ FILE **ret_file) {
+
+ _cleanup_free_ char *final_path = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int mode_flags, r;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
+ assert(open_flags);
+ assert(ret_file);
+
+ mode_flags = fopen_mode_to_flags(open_flags);
+ if (mode_flags < 0)
+ return mode_flags;
+
+ fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL);
+ if (fd < 0)
+ return fd;
+
+ r = take_fdopen_unlocked(&fd, open_flags, ret_file);
+ if (r < 0)
+ return r;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(final_path);
+
+ return 0;
+}
+
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) {
+ _cleanup_free_ char *p = NULL, *fname = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
+
+ fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
+ if (fd < 0)
+ return fd;
+
+ r = path_extract_filename(p, &fname);
+ if (r < 0)
+ return r;
+
+ if (unlinkat(fd, fname, unlink_flags) < 0)
+ return -errno;
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(p);
+
+ return 0;
+}
+
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) {
+ int pfd, r;
+
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+
+ r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd);
+ if (r < 0)
+ return r;
+
+ return pfd;
+}
diff --git a/src/basic/chase.h b/src/basic/chase.h
new file mode 100644
index 0000000000..f37e836822
--- /dev/null
+++ b/src/basic/chase.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <dirent.h>
+#include <stdio.h>
+
+#include "stat-util.h"
+
+typedef enum ChaseFlags {
+ 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_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */
+ CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */
+ CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's
+ * right-most component refers to symlink, return O_PATH fd of the symlink. */
+ CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered.
+ * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */
+ CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
+ * relative to the given directory fd instead of root. */
+ CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
+ CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the
+ * full path is still stored in ret_path and only the returned
+ * file descriptor will point to the parent directory. Note that
+ * the result path is the root or '.', then the file descriptor
+ * also points to the result path even if this flag is set.
+ * When this specified, chase() will succeed with 1 even if the
+ * file points to the last path component does not exist. */
+ CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. This
+ * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT.
+ * Note, chase_and_open() or friends always add CHASE_PARENT flag
+ * when internally call chase(), hence CHASE_MKDIR_0755 can be
+ * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */
+ CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */
+} ChaseFlags;
+
+bool unsafe_transition(const struct stat *a, const struct stat *b);
+
+/* How many iterations to execute before returning -ELOOP */
+#define CHASE_MAX 32
+
+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);
+int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename);
+
+int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd);
+
+int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path);
+int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
+int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path);
+int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
+int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path);
+int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename);
diff --git a/src/basic/compress.c b/src/basic/compress.c
index 0a330ecb55..10e7aaec59 100644
--- a/src/basic/compress.c
+++ b/src/basic/compress.c
@@ -65,6 +65,16 @@ static const char* const compression_table[_COMPRESSION_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(compression, Compression);
+bool compression_supported(Compression c) {
+ static const unsigned supported =
+ (1U << COMPRESSION_NONE) |
+ (1U << COMPRESSION_XZ) * HAVE_XZ |
+ (1U << COMPRESSION_LZ4) * HAVE_LZ4 |
+ (1U << COMPRESSION_ZSTD) * HAVE_ZSTD;
+
+ return c >= 0 && c < _COMPRESSION_MAX && FLAGS_SET(supported, 1U << c);
+}
+
int compress_blob_xz(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size) {
#if HAVE_XZ
@@ -97,7 +107,7 @@ int compress_blob_xz(const void *src, uint64_t src_size,
return -ENOBUFS;
*dst_size = out_pos;
- return COMPRESSION_XZ;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
@@ -127,7 +137,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size,
unaligned_write_le64(dst, src_size);
*dst_size = r + 8;
- return COMPRESSION_LZ4;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
@@ -150,7 +160,7 @@ int compress_blob_zstd(
return zstd_ret_to_errno(k);
*dst_size = k;
- return COMPRESSION_ZSTD;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
@@ -616,7 +626,7 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncom
s.total_in, s.total_out,
(double) s.total_out / s.total_in * 100);
- return COMPRESSION_XZ;
+ return 0;
}
}
}
@@ -707,7 +717,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco
total_in, total_out,
(double) total_out / total_in * 100);
- return COMPRESSION_LZ4;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
@@ -951,7 +961,7 @@ int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unc
log_debug("ZSTD compression finished (%" PRIu64 " -> %" PRIu64 " bytes)",
in_bytes, max_bytes - left);
- return COMPRESSION_ZSTD;
+ return 0;
#else
return -EPROTONOSUPPORT;
#endif
diff --git a/src/basic/compress.h b/src/basic/compress.h
index 583b105c66..1b5c645e32 100644
--- a/src/basic/compress.h
+++ b/src/basic/compress.h
@@ -2,6 +2,7 @@
#pragma once
#include <errno.h>
+#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
@@ -17,6 +18,8 @@ typedef enum Compression {
const char* compression_to_string(Compression compression);
Compression compression_from_string(const char *compression);
+bool compression_supported(Compression c);
+
int compress_blob_xz(const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size);
int compress_blob_lz4(const void *src, uint64_t src_size,
@@ -60,7 +63,7 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_size);
int decompress_stream_lz4(int fdf, int fdt, uint64_t max_size);
int decompress_stream_zstd(int fdf, int fdt, uint64_t max_size);
-static inline int compress_blob_explicit(
+static inline int compress_blob(
Compression compression,
const void *src, uint64_t src_size,
void *dst, size_t dst_alloc_size, size_t *dst_size) {
@@ -77,12 +80,6 @@ static inline int compress_blob_explicit(
}
}
-#define compress_blob(src, src_size, dst, dst_alloc_size, dst_size) \
- compress_blob_explicit( \
- DEFAULT_COMPRESSION, \
- src, src_size, \
- dst, dst_alloc_size, dst_size)
-
static inline int compress_stream(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) {
switch (DEFAULT_COMPRESSION) {
case COMPRESSION_ZSTD:
diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c
index d64277b806..c31fe79ebd 100644
--- a/src/basic/conf-files.c
+++ b/src/basic/conf-files.c
@@ -5,7 +5,7 @@
#include <stdio.h>
#include <stdlib.h>
-#include "chase-symlinks.h"
+#include "chase.h"
#include "conf-files.h"
#include "constants.h"
#include "dirent-util.h"
@@ -23,26 +23,19 @@
#include "terminal-util.h"
static int files_add(
- Hashmap **h,
+ DIR *dir,
+ const char *dirpath,
+ Hashmap **files,
Set **masked,
const char *suffix,
- const char *root,
- unsigned flags,
- const char *path) {
+ unsigned flags) {
- _cleanup_free_ char *dirpath = NULL;
- _cleanup_closedir_ DIR *dir = NULL;
int r;
- assert(h);
+ assert(dir);
+ assert(dirpath);
+ assert(files);
assert(masked);
- assert(path);
-
- r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &dirpath, &dir);
- if (r == -ENOENT)
- return 0;
- if (r < 0)
- return log_debug_errno(r, "Failed to open directory '%s/%s': %m", empty_or_root(root) ? "" : root, dirpath);
FOREACH_DIRENT(de, dir, return -errno) {
_cleanup_free_ char *n = NULL, *p = NULL;
@@ -53,7 +46,7 @@ static int files_add(
continue;
/* Has this file already been found in an earlier directory? */
- if (hashmap_contains(*h, de->d_name)) {
+ if (hashmap_contains(*files, de->d_name)) {
log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
continue;
}
@@ -107,13 +100,13 @@ static int files_add(
return -ENOMEM;
if ((flags & CONF_FILES_BASENAME))
- r = hashmap_ensure_put(h, &string_hash_ops_free, n, n);
+ r = hashmap_ensure_put(files, &string_hash_ops_free, n, n);
else {
p = path_join(dirpath, de->d_name);
if (!p)
return -ENOMEM;
- r = hashmap_ensure_put(h, &string_hash_ops_free_free, n, p);
+ r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p);
}
if (r < 0)
return r;
@@ -127,49 +120,99 @@ static int files_add(
}
static int base_cmp(char * const *a, char * const *b) {
- return strcmp(basename(*a), basename(*b));
+ assert(a);
+ assert(b);
+ return path_compare_filename(*a, *b);
}
-static int conf_files_list_strv_internal(
+static int copy_and_sort_files_from_hashmap(Hashmap *fh, char ***ret) {
+ _cleanup_free_ char **sv = NULL;
+ char **files;
+
+ assert(ret);
+
+ sv = hashmap_get_strv(fh);
+ if (!sv)
+ return -ENOMEM;
+
+ /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
+ files = strv_copy(sv);
+ if (!files)
+ return -ENOMEM;
+
+ typesafe_qsort(files, strv_length(files), base_cmp);
+
+ *ret = files;
+ return 0;
+}
+
+int conf_files_list_strv(
char ***ret,
const char *suffix,
const char *root,
unsigned flags,
- char **dirs) {
+ const char * const *dirs) {
_cleanup_hashmap_free_ Hashmap *fh = NULL;
_cleanup_set_free_ Set *masked = NULL;
- _cleanup_strv_free_ char **files = NULL;
- _cleanup_free_ char **sv = NULL;
int r;
assert(ret);
- /* This alters the dirs string array */
- if (!path_strv_resolve_uniq(dirs, root))
- return -ENOMEM;
-
STRV_FOREACH(p, dirs) {
- r = files_add(&fh, &masked, suffix, root, flags, *p);
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ r = chase_and_opendir(*p, root, CHASE_PREFIX_ROOT, &path, &dir);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+ continue;
+ }
+
+ r = files_add(dir, path, &fh, &masked, suffix, flags);
if (r == -ENOMEM)
return r;
if (r < 0)
- log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p);
+ log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
}
- sv = hashmap_get_strv(fh);
- if (!sv)
- return -ENOMEM;
+ return copy_and_sort_files_from_hashmap(fh, ret);
+}
- /* The entries in the array given by hashmap_get_strv() are still owned by the hashmap. */
- files = strv_copy(sv);
- if (!files)
- return -ENOMEM;
+int conf_files_list_strv_at(
+ char ***ret,
+ const char *suffix,
+ int rfd,
+ unsigned flags,
+ const char * const *dirs) {
- typesafe_qsort(files, strv_length(files), base_cmp);
- *ret = TAKE_PTR(files);
+ _cleanup_hashmap_free_ Hashmap *fh = NULL;
+ _cleanup_set_free_ Set *masked = NULL;
+ int r;
- return 0;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(ret);
+
+ STRV_FOREACH(p, dirs) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+
+ r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
+ continue;
+ }
+
+ r = files_add(dir, path, &fh, &masked, suffix, flags);
+ if (r == -ENOMEM)
+ return r;
+ if (r < 0)
+ log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
+ }
+
+ return copy_and_sort_files_from_hashmap(fh, ret);
}
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) {
@@ -240,31 +283,27 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p
return r;
}
-int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs) {
- _cleanup_strv_free_ char **copy = NULL;
-
- assert(ret);
-
- copy = strv_copy((char**) dirs);
- if (!copy)
- return -ENOMEM;
+int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
+ return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
+}
- return conf_files_list_strv_internal(ret, suffix, root, flags, copy);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir) {
+ return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
}
-int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
- _cleanup_strv_free_ char **dirs = NULL;
+int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+ _cleanup_strv_free_ char **d = NULL;
assert(ret);
- dirs = strv_new(dir);
- if (!dirs)
+ d = strv_split_nulstr(dirs);
+ if (!d)
return -ENOMEM;
- return conf_files_list_strv_internal(ret, suffix, root, flags, dirs);
+ return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
}
-int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs) {
_cleanup_strv_free_ char **d = NULL;
assert(ret);
@@ -273,7 +312,7 @@ int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, un
if (!d)
return -ENOMEM;
- return conf_files_list_strv_internal(ret, suffix, root, flags, d);
+ return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
}
int conf_files_list_with_replacement(
diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h
index 7774ed7054..547c2fc137 100644
--- a/src/basic/conf-files.h
+++ b/src/basic/conf-files.h
@@ -12,8 +12,11 @@ enum {
};
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir);
+int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir);
int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs);
+int conf_files_list_strv_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char * const *dirs);
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs);
+int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs);
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path);
int conf_files_list_with_replacement(
const char *root,
diff --git a/src/basic/coverage.h b/src/basic/coverage.h
index 640bddc485..3e674a8dba 100644
--- a/src/basic/coverage.h
+++ b/src/basic/coverage.h
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+extern void __gcov_dump(void);
+extern void __gcov_reset(void);
+
/* When built with --coverage (gcov) we need to explicitly call __gcov_dump()
* in places where we use _exit(), since _exit() skips at-exit hooks resulting
* in lost coverage.
@@ -8,12 +11,37 @@
* To make sure we don't miss any _exit() calls, this header file is included
* explicitly on the compiler command line via the -include directive (only
* when built with -Db_coverage=true)
- * */
+ */
extern void _exit(int);
-extern void __gcov_dump(void);
static inline _Noreturn void _coverage__exit(int status) {
__gcov_dump();
_exit(status);
}
#define _exit(x) _coverage__exit(x)
+
+/* gcov provides wrappers for the exec*() calls but there's none for execveat(),
+ * which means we lose all coverage prior to the call. To mitigate this, let's
+ * add a simple execveat() wrapper in gcov's style[0], which dumps and resets
+ * the coverage data when needed.
+ *
+ * This applies only when we're built with -Dfexecve=true.
+ *
+ * [0] https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/libgcov-interface.c;h=b2ee930864183b78c8826255183ca86e15e21ded;hb=HEAD
+ */
+
+extern int execveat(int, const char *, char * const [], char * const [], int);
+
+static inline int _coverage_execveat(
+ int dirfd,
+ const char *pathname,
+ char * const argv[],
+ char * const envp[],
+ int flags) {
+ __gcov_dump();
+ int r = execveat(dirfd, pathname, argv, envp, flags);
+ __gcov_reset();
+
+ return r;
+}
+#define execveat(d,p,a,e,f) _coverage_execveat(d, p, a, e, f)
diff --git a/src/basic/devnum-util.c b/src/basic/devnum-util.c
index bd1b4d6c39..f82e13bdb0 100644
--- a/src/basic/devnum-util.c
+++ b/src/basic/devnum-util.c
@@ -3,7 +3,7 @@
#include <string.h>
#include <sys/stat.h>
-#include "chase-symlinks.h"
+#include "chase.h"
#include "devnum-util.h"
#include "parse-util.h"
#include "path-util.h"
@@ -83,7 +83,7 @@ int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
assert(ret);
- if (major(devnum) == 0 && minor(devnum) == 0)
+ if (devnum_is_zero(devnum))
/* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
* /dev/block/ and /dev/char/, hence we handle them specially here. */
return device_path_make_inaccessible(mode, ret);
@@ -92,7 +92,7 @@ int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
if (r < 0)
return r;
- return chase_symlinks(p, NULL, 0, ret, NULL);
+ return chase(p, NULL, 0, ret, NULL);
}
int device_path_parse_major_minor(const char *path, mode_t *ret_mode, dev_t *ret_devnum) {
diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h
index 38aa4efa87..e109de9913 100644
--- a/src/basic/devnum-util.h
+++ b/src/basic/devnum-util.h
@@ -50,3 +50,7 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) {
}
#define FORMAT_DEVNUM(d) format_devnum((d), (char[DEVNUM_STR_MAX]) {})
+
+static inline bool devnum_is_zero(dev_t d) {
+ return major(d) == 0 && minor(d) == 0;
+}
diff --git a/src/basic/env-file.c b/src/basic/env-file.c
index 16de727c09..7b3e209ddc 100644
--- a/src/basic/env-file.c
+++ b/src/basic/env-file.c
@@ -359,6 +359,23 @@ int parse_env_filev(
return r;
}
+int parse_env_file_fdv(int fd, const char *fname, va_list ap) {
+ _cleanup_fclose_ FILE *f = NULL;
+ va_list aq;
+ int r;
+
+ assert(fd >= 0);
+
+ r = fdopen_independent(fd, "re", &f);
+ if (r < 0)
+ return r;
+
+ va_copy(aq, ap);
+ r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
+ va_end(aq);
+ return r;
+}
+
int parse_env_file_sentinel(
FILE *f,
const char *fname,
@@ -381,25 +398,13 @@ int parse_env_file_fd_sentinel(
const char *fname, /* only used for logging */
...) {
- _cleanup_close_ int fd_ro = -EBADF;
- _cleanup_fclose_ FILE *f = NULL;
va_list ap;
int r;
assert(fd >= 0);
- fd_ro = fd_reopen(fd, O_CLOEXEC | O_RDONLY);
- if (fd_ro < 0)
- return fd_ro;
-
- f = fdopen(fd_ro, "re");
- if (!f)
- return -errno;
-
- TAKE_FD(fd_ro);
-
va_start(ap, fname);
- r = parse_env_filev(f, fname, ap);
+ r = parse_env_file_fdv(fd, fname, ap);
va_end(ap);
return r;
@@ -485,6 +490,7 @@ int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
int r;
assert(f || fname);
+ assert(ret);
r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
if (r < 0)
@@ -494,6 +500,19 @@ int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
return 0;
}
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(fd >= 0);
+
+ r = fdopen_independent(fd, "re", &f);
+ if (r < 0)
+ return r;
+
+ return load_env_file_pairs(f, fname, ret);
+}
+
static int merge_env_file_push(
const char *filename, unsigned line,
const char *key, char *value,
diff --git a/src/basic/env-file.h b/src/basic/env-file.h
index dc38b7a5c9..2465eeddf4 100644
--- a/src/basic/env-file.h
+++ b/src/basic/env-file.h
@@ -8,12 +8,14 @@
#include "macro.h"
int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
#define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
#define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL)
int load_env_file(FILE *f, const char *fname, char ***ret);
int load_env_file_pairs(FILE *f, const char *fname, char ***ret);
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret);
int merge_env_file(char ***env, FILE *f, const char *fname);
diff --git a/src/basic/env-util.c b/src/basic/env-util.c
index 55ac11a512..41fad1d1b9 100644
--- a/src/basic/env-util.c
+++ b/src/basic/env-util.c
@@ -459,6 +459,48 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
return strv_env_replace_consume(l, p);
}
+int _strv_env_assign_many(char ***l, ...) {
+ va_list ap;
+ int r;
+
+ assert(l);
+
+ va_start(ap, l);
+ for (;;) {
+ const char *key, *value;
+
+ key = va_arg(ap, const char *);
+ if (!key)
+ break;
+
+ if (!env_name_is_valid(key)) {
+ va_end(ap);
+ return -EINVAL;
+ }
+
+ value = va_arg(ap, const char *);
+ if (!value) {
+ strv_env_unset(*l, key);
+ continue;
+ }
+
+ char *p = strjoin(key, "=", value);
+ if (!p) {
+ va_end(ap);
+ return -ENOMEM;
+ }
+
+ r = strv_env_replace_consume(l, p);
+ if (r < 0) {
+ va_end(ap);
+ return r;
+ }
+ }
+ va_end(ap);
+
+ return 0;
+}
+
char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
assert(name);
diff --git a/src/basic/env-util.h b/src/basic/env-util.h
index b927ac7a48..b0ff5a11d1 100644
--- a/src/basic/env-util.h
+++ b/src/basic/env-util.h
@@ -49,6 +49,8 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */
int strv_env_replace_strdup(char ***l, const char *assignment);
int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
int strv_env_assign(char ***l, const char *key, const char *value);
+int _strv_env_assign_many(char ***l, ...) _sentinel_;
+#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
char *strv_env_get(char **x, const char *n) _pure_;
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index d729643265..974a7aac65 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -534,6 +534,11 @@ bool fdname_is_valid(const char *s) {
int fd_get_path(int fd, char **ret) {
int r;
+ assert(fd >= 0 || fd == AT_FDCWD);
+
+ if (fd == AT_FDCWD)
+ return safe_getcwd(ret);
+
r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret);
if (r == -ENOENT) {
/* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
@@ -744,23 +749,42 @@ finish:
int fd_reopen(int fd, int flags) {
int new_fd, r;
+ assert(fd >= 0 || fd == AT_FDCWD);
+
/* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
* turn O_RDWR fds into O_RDONLY fds.
*
* This doesn't work on sockets (since they cannot be open()ed, ever).
*
- * This implicitly resets the file read index to 0. */
-
- if (FLAGS_SET(flags, O_DIRECTORY)) {
+ * This implicitly resets the file read index to 0.
+ *
+ * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd.
+ *
+ * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used
+ * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without
+ * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you
+ * can reopen it only if you pass O_PATH again. */
+
+ if (FLAGS_SET(flags, O_NOFOLLOW))
+ /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is primarily implemented
+ * via a symlink-based interface in /proc/self/fd. Let's refuse this here early. Note that
+ * the kernel would generate ELOOP here too, hence this manual check is mostly redundant –
+ * the only reason we add it here is so that the O_DIRECTORY special case (see below) behaves
+ * the same way as the non-O_DIRECTORY case. */
+ return -ELOOP;
+
+ if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) {
/* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole
* magic /proc/ directory, and make ourselves independent of that being mounted. */
- new_fd = openat(fd, ".", flags);
+ new_fd = openat(fd, ".", flags | O_DIRECTORY);
if (new_fd < 0)
return -errno;
return new_fd;
}
+ assert(fd >= 0);
+
new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
if (new_fd < 0) {
if (errno != ENOENT)
@@ -879,10 +903,31 @@ int dir_fd_is_root(int dir_fd) {
if (r < 0)
return r;
+ r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
+ if (r < 0)
+ return r;
+
+ /* First, compare inode. If these are different, the fd does not point to the root directory "/". */
+ 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);
@@ -891,14 +936,12 @@ int dir_fd_is_root(int dir_fd) {
st.nsx.stx_mask |= STATX_MNT_ID;
}
- r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
- if (r < 0)
- return r;
-
if (!FLAGS_SET(pst.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);
@@ -907,12 +950,18 @@ int dir_fd_is_root(int dir_fd) {
pst.nsx.stx_mask |= STATX_MNT_ID;
}
- /* If the parent directory is the same inode, the fd points to the root directory "/". We also 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_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
+ return statx_mount_same(&st.nsx, &pst.nsx);
+}
+
+const char *accmode_to_string(int flags) {
+ switch (flags & O_ACCMODE) {
+ case O_RDONLY:
+ return "ro";
+ case O_WRONLY:
+ return "wo";
+ case O_RDWR:
+ return "rw";
+ default:
+ return NULL;
+ }
}
diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h
index a6353cf48e..2f59e334c5 100644
--- a/src/basic/fd-util.h
+++ b/src/basic/fd-util.h
@@ -2,6 +2,7 @@
#pragma once
#include <dirent.h>
+#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>
@@ -101,6 +102,9 @@ int read_nr_open(void);
int fd_get_diskseq(int fd, uint64_t *ret);
int dir_fd_is_root(int dir_fd);
+static inline int dir_fd_is_root_or_cwd(int dir_fd) {
+ return dir_fd == AT_FDCWD ? true : dir_fd_is_root(dir_fd);
+}
/* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
#define PROC_FD_PATH_MAX \
@@ -115,3 +119,13 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f
#define FORMAT_PROC_FD_PATH(fd) \
format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
+
+const char *accmode_to_string(int flags);
+
+/* Like ASSERT_PTR, but for fds */
+#define ASSERT_FD(fd) \
+ ({ \
+ int _fd_ = (fd); \
+ assert(_fd_ >= 0); \
+ _fd_; \
+ })
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index c802db749b..48ffb4e5e6 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -13,7 +13,7 @@
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -44,20 +44,6 @@
* can detect EOFs. */
#define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret) {
- int r;
-
- assert(ret);
-
- r = xfopenat(dir_fd, path, options, flags, ret);
- if (r < 0)
- return r;
-
- (void) __fsetlocking(*ret, FSETLOCKING_BYCALLER);
-
- return 0;
-}
-
int fdopen_unlocked(int fd, const char *options, FILE **ret) {
assert(ret);
@@ -267,7 +253,8 @@ int write_string_file_ts_at(
const struct timespec *ts) {
_cleanup_fclose_ FILE *f = NULL;
- int q, r, fd;
+ _cleanup_close_ int fd = -EBADF;
+ int q, r;
assert(fn);
assert(line);
@@ -304,11 +291,9 @@ int write_string_file_ts_at(
goto fail;
}
- r = fdopen_unlocked(fd, "w", &f);
- if (r < 0) {
- safe_close(fd);
+ r = take_fdopen_unlocked(&fd, "w", &f);
+ if (r < 0)
goto fail;
- }
if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
setvbuf(f, NULL, _IONBF, 0);
@@ -354,18 +339,19 @@ int write_string_filef(
return write_string_file(fn, p, flags);
}
-int read_one_line_file(const char *fn, char **line) {
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
int r;
- assert(fn);
- assert(line);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(filename);
+ assert(ret);
- r = fopen_unlocked(fn, "re", &f);
+ r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f);
if (r < 0)
return r;
- return read_line(f, LONG_LINE_MAX, line);
+ return read_line(f, LONG_LINE_MAX, ret);
}
int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) {
@@ -760,62 +746,19 @@ int read_full_file_full(
size_t *ret_size) {
_cleanup_fclose_ FILE *f = NULL;
+ XfopenFlags xflags = XFOPEN_UNLOCKED;
int r;
assert(filename);
assert(ret_contents);
- r = xfopenat(dir_fd, filename, "re", 0, &f);
- if (r < 0) {
- _cleanup_close_ int sk = -EBADF;
-
- /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
- if (r != -ENXIO)
- return r;
-
- /* If this is enabled, let's try to connect to it */
- if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
- return -ENXIO;
-
- /* Seeking is not supported on AF_UNIX sockets */
- if (offset != UINT64_MAX)
- return -ENXIO;
-
- sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
- if (sk < 0)
- return -errno;
-
- if (bind_name) {
- /* If the caller specified a socket name to bind to, do so before connecting. This is
- * useful to communicate some minor, short meta-information token from the client to
- * the server. */
- union sockaddr_union bsa;
-
- r = sockaddr_un_set_path(&bsa.un, bind_name);
- if (r < 0)
- return r;
-
- if (bind(sk, &bsa.sa, r) < 0)
- return -errno;
- }
-
- r = connect_unix_path(sk, dir_fd, filename);
- if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
- return -ENXIO;
- if (r < 0)
- return r;
-
- if (shutdown(sk, SHUT_WR) < 0)
- return -errno;
-
- f = fdopen(sk, "r");
- if (!f)
- return -errno;
-
- TAKE_FD(sk);
- }
+ if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+ offset == UINT64_MAX) /* Seeking is not supported on AF_UNIX sockets */
+ xflags |= XFOPEN_SOCKET;
- (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+ if (r < 0)
+ return r;
return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
}
@@ -922,8 +865,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
}
DIR *xopendirat(int fd, const char *name, int flags) {
- int nfd;
- DIR *d;
+ _cleanup_close_ int nfd = -EBADF;
assert(!(flags & O_CREAT));
@@ -934,13 +876,7 @@ DIR *xopendirat(int fd, const char *name, int flags) {
if (nfd < 0)
return NULL;
- d = fdopendir(nfd);
- if (!d) {
- safe_close(nfd);
- return NULL;
- }
-
- return d;
+ return take_fdopendir(&nfd);
}
int fopen_mode_to_flags(const char *mode) {
@@ -989,37 +925,143 @@ int fopen_mode_to_flags(const char *mode) {
return flags;
}
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
FILE *f;
/* A combination of fopen() with openat() */
- if (dir_fd == AT_FDCWD && flags == 0) {
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(mode);
+ assert(ret);
+
+ if (dir_fd == AT_FDCWD && open_flags == 0)
f = fopen(path, mode);
- if (!f)
- return -errno;
- } else {
- int fd, mode_flags;
+ else {
+ _cleanup_close_ int fd = -EBADF;
+ int mode_flags;
mode_flags = fopen_mode_to_flags(mode);
if (mode_flags < 0)
return mode_flags;
- fd = openat(dir_fd, path, mode_flags | flags);
+ fd = openat(dir_fd, path, mode_flags | open_flags);
if (fd < 0)
return -errno;
- f = fdopen(fd, mode);
- if (!f) {
- safe_close(fd);
+ f = take_fdopen(&fd, mode);
+ }
+ if (!f)
+ return -errno;
+
+ *ret = f;
+ return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+ _cleanup_close_ int sk = -EBADF;
+ FILE *f;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (sk < 0)
+ return -errno;
+
+ if (bind_name) {
+ /* If the caller specified a socket name to bind to, do so before connecting. This is
+ * useful to communicate some minor, short meta-information token from the client to
+ * the server. */
+ union sockaddr_union bsa;
+
+ r = sockaddr_un_set_path(&bsa.un, bind_name);
+ if (r < 0)
+ return r;
+
+ if (bind(sk, &bsa.sa, r) < 0)
return -errno;
- }
}
+ r = connect_unix_path(sk, dir_fd, path);
+ if (r < 0)
+ return r;
+
+ if (shutdown(sk, SHUT_WR) < 0)
+ return -errno;
+
+ f = take_fdopen(&sk, "r");
+ if (!f)
+ return -errno;
+
+ *ret = f;
+ return 0;
+}
+
+int xfopenat_full(
+ int dir_fd,
+ const char *path,
+ const char *mode,
+ int open_flags,
+ XfopenFlags flags,
+ const char *bind_name,
+ FILE **ret) {
+
+ FILE *f = NULL; /* avoid false maybe-uninitialized warning */
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(mode);
+ assert(ret);
+
+ r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+ if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+ /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+ r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+ if (IN_SET(r, -ENOTSOCK, -EINVAL))
+ return -ENXIO; /* propagate original error if this is not a socket after all */
+ }
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
*ret = f;
return 0;
}
+int fdopen_independent(int fd, const char *mode, FILE **ret) {
+ _cleanup_close_ int copy_fd = -EBADF;
+ _cleanup_fclose_ FILE *f = NULL;
+ int mode_flags;
+
+ assert(fd >= 0);
+ assert(mode);
+ assert(ret);
+
+ /* A combination of fdopen() + fd_reopen(). i.e. reopens the inode the specified fd points to and
+ * returns a FILE* for it */
+
+ mode_flags = fopen_mode_to_flags(mode);
+ if (mode_flags < 0)
+ return mode_flags;
+
+ copy_fd = fd_reopen(fd, mode_flags);
+ if (copy_fd < 0)
+ return copy_fd;
+
+ f = take_fdopen(&copy_fd, mode);
+ if (!f)
+ return -errno;
+
+ *ret = TAKE_PTR(f);
+ return 0;
+}
+
static int search_and_fopen_internal(
const char *path,
const char *mode,
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index 7da3ee33e0..769bf394fd 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -43,10 +43,6 @@ typedef enum {
READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */
} ReadFullFileFlags;
-int fopen_unlocked_at(int dir_fd, const char *path, const char *options, int flags, FILE **ret);
-static inline int fopen_unlocked(const char *path, const char *options, FILE **ret) {
- return fopen_unlocked_at(AT_FDCWD, path, options, 0, ret);
-}
int fdopen_unlocked(int fd, const char *options, FILE **ret);
int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
FILE* take_fdopen(int *fd, const char *options);
@@ -71,7 +67,10 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin
int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
-int read_one_line_file(const char *filename, char **line);
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret);
+static inline int read_one_line_file(const char *filename, char **ret) {
+ return read_one_line_file_at(AT_FDCWD, filename, ret);
+}
int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) {
return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
@@ -104,7 +103,31 @@ int executable_is_script(const char *path, char **interpreter);
int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
DIR *xopendirat(int dirfd, const char *name, int flags);
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
+
+typedef enum XfopenFlags {
+ XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */
+ XFOPEN_SOCKET = 1 << 1, /* also try to open unix socket */
+} XfopenFlags;
+
+int xfopenat_full(
+ int dir_fd,
+ const char *path,
+ const char *mode,
+ int open_flags,
+ XfopenFlags flags,
+ const char *bind_name,
+ FILE **ret);
+static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+ return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret);
+}
+static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+ return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret);
+}
+static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) {
+ return fopen_unlocked_at(AT_FDCWD, path, mode, 0, ret);
+}
+
+int fdopen_independent(int fd, const char *mode, FILE **ret);
int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path);
int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path);
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index b942abd477..1e1413dc80 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -3,6 +3,7 @@
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
+#include <sys/file.h>
#include <linux/falloc.h>
#include <linux/magic.h>
#include <unistd.h>
@@ -13,6 +14,7 @@
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
+#include "lock-util.h"
#include "log.h"
#include "macro.h"
#include "missing_fcntl.h"
@@ -781,12 +783,23 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
return 0;
}
-int open_parent(const char *path, int flags, mode_t mode) {
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
_cleanup_free_ char *parent = NULL;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
r = path_extract_directory(path, &parent);
- if (r < 0)
+ if (r == -EDESTADDRREQ) {
+ parent = strdup(".");
+ if (!parent)
+ return -ENOMEM;
+ } else if (r == -EADDRNOTAVAIL) {
+ parent = strdup(path);
+ if (!parent)
+ return -ENOMEM;
+ } else if (r < 0)
return r;
/* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
@@ -797,7 +810,7 @@ int open_parent(const char *path, int flags, mode_t mode) {
else if (!FLAGS_SET(flags, O_TMPFILE))
flags |= O_DIRECTORY|O_RDONLY;
- return RET_NERRNO(open(parent, flags, mode));
+ return RET_NERRNO(openat(dir_fd, parent, flags, mode));
}
int conservative_renameat(
@@ -811,7 +824,7 @@ int conservative_renameat(
* have the exact same contents and basic file attributes already. In that case remove the new file
* instead. This call is useful for reducing inotify wakeups on files that are updated but don't
* actually change. This function is written in a style that we rather rename too often than suppress
- * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+ * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
* events is an optimization only, not more. */
old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
@@ -987,7 +1000,7 @@ int parse_cifs_service(
int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
_cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
- _cleanup_free_ char *fname = NULL;
+ _cleanup_free_ char *fname = NULL, *parent = NULL;
int r;
/* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
@@ -1002,19 +1015,13 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
/* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
* flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
- if (isempty(path))
- return -EINVAL;
-
- if (!filename_is_valid(path)) {
- _cleanup_free_ char *parent = NULL;
-
- /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
- * that we can pin it, and operate below it. */
-
- r = path_extract_directory(path, &parent);
- if (r < 0)
+ /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
+ * that we can pin it, and operate below it. */
+ r = path_extract_directory(path, &parent);
+ if (r < 0) {
+ if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
return r;
-
+ } else {
r = path_extract_filename(path, &fname);
if (r < 0)
return r;
@@ -1088,6 +1095,14 @@ int xopenat(int dir_fd, const char *path, int flags, mode_t mode) {
bool made = false;
int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ if (isempty(path)) {
+ assert(!FLAGS_SET(flags, O_CREAT|O_EXCL));
+ return fd_reopen(dir_fd, flags & ~O_NOFOLLOW);
+ }
+
if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) {
r = RET_NERRNO(mkdirat(dir_fd, path, mode));
if (r == -EEXIST) {
@@ -1124,3 +1139,42 @@ int xopenat(int dir_fd, const char *path, int flags, mode_t mode) {
return TAKE_FD(fd);
}
+
+int xopenat_lock(int dir_fd, const char *path, int flags, mode_t mode, LockType locktype, int operation) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
+
+ /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
+ * the same error here). */
+ if (FLAGS_SET(flags, O_DIRECTORY) && locktype != LOCK_BSD)
+ return -EBADF;
+
+ for (;;) {
+ struct stat st;
+
+ fd = xopenat(dir_fd, path, flags, mode);
+ if (fd < 0)
+ return fd;
+
+ r = lock_generic(fd, locktype, operation);
+ if (r < 0)
+ return r;
+
+ /* If we acquired the lock, let's check if the file/directory still exists in the file
+ * system. If not, then the previous exclusive owner removed it and then closed it. In such a
+ * case our acquired lock is worthless, hence try again. */
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+ if (st.st_nlink > 0)
+ break;
+
+ fd = safe_close(fd);
+ }
+
+ return TAKE_FD(fd);
+}
diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h
index b66dda01b4..cf381dfc26 100644
--- a/src/basic/fs-util.h
+++ b/src/basic/fs-util.h
@@ -12,6 +12,7 @@
#include "alloc-util.h"
#include "errno-util.h"
+#include "lock-util.h"
#include "time-util.h"
#include "user-util.h"
@@ -113,7 +114,10 @@ typedef enum UnlinkDeallocateFlags {
int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
-int open_parent(const char *path, int flags, mode_t mode);
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode);
+static inline int open_parent(const char *path, int flags, mode_t mode) {
+ return open_parent_at(AT_FDCWD, path, flags, mode);
+}
int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
static inline int conservative_rename(const char *oldpath, const char *newpath) {
@@ -129,3 +133,5 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode);
int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created);
int xopenat(int dir_fd, const char *path, int flags, mode_t mode);
+
+int xopenat_lock(int dir_fd, const char *path, int flags, mode_t mode, LockType locktype, int operation);
diff --git a/src/basic/getopt-defs.h b/src/basic/getopt-defs.h
new file mode 100644
index 0000000000..3efeb6df80
--- /dev/null
+++ b/src/basic/getopt-defs.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <getopt.h>
+
+#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:"
+
+#define COMMON_GETOPT_ARGS \
+ ARG_LOG_LEVEL = 0x100, \
+ ARG_LOG_TARGET, \
+ ARG_LOG_COLOR, \
+ ARG_LOG_LOCATION, \
+ ARG_LOG_TIME
+
+#define SYSTEMD_GETOPT_ARGS \
+ ARG_UNIT, \
+ ARG_SYSTEM, \
+ ARG_USER, \
+ ARG_TEST, \
+ ARG_NO_PAGER, \
+ ARG_VERSION, \
+ ARG_DUMP_CONFIGURATION_ITEMS, \
+ ARG_DUMP_BUS_PROPERTIES, \
+ ARG_BUS_INTROSPECT, \
+ ARG_DUMP_CORE, \
+ ARG_CRASH_CHVT, \
+ ARG_CRASH_SHELL, \
+ ARG_CRASH_REBOOT, \
+ ARG_CONFIRM_SPAWN, \
+ ARG_SHOW_STATUS, \
+ ARG_DESERIALIZE, \
+ ARG_SWITCHED_ROOT, \
+ ARG_DEFAULT_STD_OUTPUT, \
+ ARG_DEFAULT_STD_ERROR, \
+ ARG_MACHINE_ID, \
+ ARG_SERVICE_WATCHDOGS
+
+#define SHUTDOWN_GETOPT_ARGS \
+ ARG_EXIT_CODE, \
+ ARG_TIMEOUT
+
+#define COMMON_GETOPT_OPTIONS \
+ { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, \
+ { "log-target", required_argument, NULL, ARG_LOG_TARGET }, \
+ { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, \
+ { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, \
+ { "log-time", optional_argument, NULL, ARG_LOG_TIME }
+
+#define SYSTEMD_GETOPT_OPTIONS \
+ { "unit", required_argument, NULL, ARG_UNIT }, \
+ { "system", no_argument, NULL, ARG_SYSTEM }, \
+ { "user", no_argument, NULL, ARG_USER }, \
+ { "test", no_argument, NULL, ARG_TEST }, \
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER }, \
+ { "help", no_argument, NULL, 'h' }, \
+ { "version", no_argument, NULL, ARG_VERSION }, \
+ { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \
+ { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES }, \
+ { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, \
+ { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, \
+ { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \
+ { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \
+ { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \
+ { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \
+ { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \
+ { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \
+ { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, \
+ { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, \
+ { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, \
+ { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, \
+ { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS }
+
+#define SHUTDOWN_GETOPT_OPTIONS \
+ { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, \
+ { "timeout", required_argument, NULL, ARG_TIMEOUT }
diff --git a/src/basic/list.h b/src/basic/list.h
index ffc8bd8304..e4e5dff3ea 100644
--- a/src/basic/list.h
+++ b/src/basic/list.h
@@ -46,7 +46,7 @@
/* Remove an item from the list */
#define LIST_REMOVE(name,head,item) \
- ({ \
+ ({ \
typeof(*(head)) **_head = &(head), *_item = (item); \
assert(_item); \
if (_item->name##_next) \
@@ -127,8 +127,11 @@
_b; \
})
-#define LIST_JUST_US(name,item) \
- (!(item)->name##_prev && !(item)->name##_next)
+#define LIST_JUST_US(name, item) \
+ ({ \
+ typeof(*(item)) *_item = (item); \
+ !(_item)->name##_prev && !(_item)->name##_next; \
+ })
/* The type of the iterator 'i' is automatically determined by the type of 'head', and declared in the
* loop. Hence, do not declare the same variable in the outer scope. Sometimes, we set 'head' through
diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c
index 13e4c12237..8ca30a50f0 100644
--- a/src/basic/lock-util.c
+++ b/src/basic/lock-util.c
@@ -15,45 +15,34 @@
#include "missing_fcntl.h"
#include "path-util.h"
-int make_lock_file(const char *p, int operation, LockFile *ret) {
- _cleanup_close_ int fd = -EBADF;
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret) {
+ _cleanup_close_ int fd = -EBADF, dfd = -EBADF;
_cleanup_free_ char *t = NULL;
- int r;
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(p);
assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
assert(ret);
+ if (isempty(p))
+ return -EINVAL;
+
/* We use UNPOSIX locks as they have nice semantics, and are mostly compatible with NFS. */
+ dfd = fd_reopen(dir_fd, O_CLOEXEC|O_PATH|O_DIRECTORY);
+ if (dfd < 0)
+ return dfd;
+
t = strdup(p);
if (!t)
return -ENOMEM;
- for (;;) {
- struct stat st;
-
- fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
- if (fd < 0)
- return -errno;
-
- r = unposix_lock(fd, operation);
- if (r < 0)
- return r == -EAGAIN ? -EBUSY : r;
-
- /* If we acquired the lock, let's check if the file still exists in the file system. If not,
- * then the previous exclusive owner removed it and then closed it. In such a case our
- * acquired lock is worthless, hence try again. */
-
- if (fstat(fd, &st) < 0)
- return -errno;
- if (st.st_nlink > 0)
- break;
-
- fd = safe_close(fd);
- }
+ fd = xopenat_lock(dfd, p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600, LOCK_UNPOSIX, operation);
+ if (fd < 0)
+ return fd == -EAGAIN ? -EBUSY : fd;
*ret = (LockFile) {
+ .dir_fd = TAKE_FD(dfd),
.path = TAKE_PTR(t),
.fd = TAKE_FD(fd),
.operation = operation,
@@ -100,11 +89,12 @@ void release_lock_file(LockFile *f) {
f->operation = LOCK_EX|LOCK_NB;
if ((f->operation & ~LOCK_NB) == LOCK_EX)
- (void) unlink(f->path);
+ (void) unlinkat(f->dir_fd, f->path, 0);
f->path = mfree(f->path);
}
+ f->dir_fd = safe_close(f->dir_fd);
f->fd = safe_close(f->fd);
f->operation = 0;
}
@@ -173,3 +163,18 @@ void unposix_unlockpp(int **fd) {
(void) fcntl_lock(**fd, LOCK_UN, /*ofd=*/ true);
*fd = NULL;
}
+
+int lock_generic(int fd, LockType type, int operation) {
+ assert(fd >= 0);
+
+ switch (type) {
+ case LOCK_BSD:
+ return RET_NERRNO(flock(fd, operation));
+ case LOCK_POSIX:
+ return posix_lock(fd, operation);
+ case LOCK_UNPOSIX:
+ return unposix_lock(fd, operation);
+ default:
+ assert_not_reached();
+ }
+}
diff --git a/src/basic/lock-util.h b/src/basic/lock-util.h
index 6eebd09143..e7744476bb 100644
--- a/src/basic/lock-util.h
+++ b/src/basic/lock-util.h
@@ -1,17 +1,23 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <fcntl.h>
+
typedef struct LockFile {
+ int dir_fd;
char *path;
int fd;
int operation;
} LockFile;
-int make_lock_file(const char *p, int operation, LockFile *ret);
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret);
+static inline int make_lock_file(const char *p, int operation, LockFile *ret) {
+ return make_lock_file_at(AT_FDCWD, p, operation, ret);
+}
int make_lock_file_for(const char *p, int operation, LockFile *ret);
void release_lock_file(LockFile *f);
-#define LOCK_FILE_INIT { .fd = -EBADF, .path = NULL }
+#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF }
/* POSIX locks with the same interface as flock(). */
int posix_lock(int fd, int operation);
@@ -26,3 +32,11 @@ void unposix_unlockpp(int **fd);
#define CLEANUP_UNPOSIX_UNLOCK(fd) \
_cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd)
+
+typedef enum LockType {
+ LOCK_BSD,
+ LOCK_POSIX,
+ LOCK_UNPOSIX,
+} LockType;
+
+int lock_generic(int fd, LockType type, int operation);
diff --git a/src/basic/log.c b/src/basic/log.c
index 8b973f9e0a..4cd2d5a4ab 100644
--- a/src/basic/log.c
+++ b/src/basic/log.c
@@ -42,7 +42,7 @@
#include "utf8.h"
#define SNDBUF_SIZE (8*1024*1024)
-#define IOVEC_MAX 128U
+#define IOVEC_MAX 256U
static log_syntax_callback_t log_syntax_callback = NULL;
static void *log_syntax_callback_userdata = NULL;
@@ -50,6 +50,7 @@ static void *log_syntax_callback_userdata = NULL;
static LogTarget log_target = LOG_TARGET_CONSOLE;
static int log_max_level = LOG_INFO;
static int log_facility = LOG_DAEMON;
+static bool ratelimit_kmsg = true;
static int console_fd = STDERR_FILENO;
static int syslog_fd = -EBADF;
@@ -73,11 +74,14 @@ static bool prohibit_ipc = false;
static char *log_abort_msg = NULL;
typedef struct LogContext {
+ unsigned n_ref;
/* Depending on which destructor is used (log_context_free() or log_context_detach()) the memory
* referenced by this is freed or not */
char **fields;
struct iovec *input_iovec;
size_t n_input_iovec;
+ char *key;
+ char *value;
bool owned;
LIST_FIELDS(struct LogContext, ll);
} LogContext;
@@ -85,6 +89,8 @@ typedef struct LogContext {
static thread_local LIST_HEAD(LogContext, _log_context) = NULL;
static thread_local size_t _log_context_num_fields = 0;
+static thread_local const char *log_prefix = NULL;
+
#if LOG_MESSAGE_VERIFICATION || defined(__COVERITY__)
bool _log_message_dummy = false; /* Always false */
#endif
@@ -392,7 +398,7 @@ static int write_to_console(
header_time[FORMAT_TIMESTAMP_MAX],
prefix[1 + DECIMAL_STR_MAX(int) + 2],
tid_string[3 + DECIMAL_STR_MAX(pid_t) + 1];
- struct iovec iovec[9];
+ struct iovec iovec[11];
const char *on = NULL, *off = NULL;
size_t n = 0;
@@ -431,6 +437,10 @@ static int write_to_console(
if (on)
iovec[n++] = IOVEC_MAKE_STRING(on);
+ if (log_prefix) {
+ iovec[n++] = IOVEC_MAKE_STRING(log_prefix);
+ iovec[n++] = IOVEC_MAKE_STRING(": ");
+ }
iovec[n++] = IOVEC_MAKE_STRING(buffer);
if (off)
iovec[n++] = IOVEC_MAKE_STRING(off);
@@ -490,6 +500,8 @@ static int write_to_syslog(
IOVEC_MAKE_STRING(header_time),
IOVEC_MAKE_STRING(program_invocation_short_name),
IOVEC_MAKE_STRING(header_pid),
+ IOVEC_MAKE_STRING(strempty(log_prefix)),
+ IOVEC_MAKE_STRING(log_prefix ? ": " : ""),
IOVEC_MAKE_STRING(buffer),
};
const struct msghdr msghdr = {
@@ -541,8 +553,12 @@ static int write_to_kmsg(
if (kmsg_fd < 0)
return 0;
- if (!ratelimit_below(&ratelimit))
- return 0;
+ if (ratelimit_kmsg && !ratelimit_below(&ratelimit)) {
+ if (ratelimit_num_dropped(&ratelimit) > 1)
+ return 0;
+
+ buffer = "Too many messages being logged to kmsg, ignoring";
+ }
xsprintf(header_priority, "<%i>", level);
xsprintf(header_pid, "["PID_FMT"]: ", getpid_cached());
@@ -551,6 +567,8 @@ static int write_to_kmsg(
IOVEC_MAKE_STRING(header_priority),
IOVEC_MAKE_STRING(program_invocation_short_name),
IOVEC_MAKE_STRING(header_pid),
+ IOVEC_MAKE_STRING(strempty(log_prefix)),
+ IOVEC_MAKE_STRING(log_prefix ? ": " : ""),
IOVEC_MAKE_STRING(buffer),
IOVEC_MAKE_STRING("\n"),
};
@@ -631,6 +649,15 @@ static void log_do_context(struct iovec *iovec, size_t iovec_len, size_t *n) {
iovec[(*n)++] = c->input_iovec[i];
iovec[(*n)++] = IOVEC_MAKE_STRING("\n");
}
+
+ if (c->key && c->value) {
+ if (*n + 3 >= iovec_len)
+ return;
+
+ iovec[(*n)++] = IOVEC_MAKE_STRING(c->key);
+ iovec[(*n)++] = IOVEC_MAKE_STRING(c->value);
+ iovec[(*n)++] = IOVEC_MAKE_STRING("\n");
+ }
}
}
@@ -653,13 +680,17 @@ static int write_to_journal(
if (journal_fd < 0)
return 0;
- iovec_len = MIN(4 + _log_context_num_fields * 2, IOVEC_MAX);
+ iovec_len = MIN(6 + _log_context_num_fields * 2, IOVEC_MAX);
iovec = newa(struct iovec, iovec_len);
log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object, extra_field, extra);
iovec[n++] = IOVEC_MAKE_STRING(header);
iovec[n++] = IOVEC_MAKE_STRING("MESSAGE=");
+ if (log_prefix) {
+ iovec[n++] = IOVEC_MAKE_STRING(log_prefix);
+ iovec[n++] = IOVEC_MAKE_STRING(": ");
+ }
iovec[n++] = IOVEC_MAKE_STRING(buffer);
iovec[n++] = IOVEC_MAKE_STRING("\n");
@@ -835,16 +866,9 @@ int log_object_internalv(
/* Make sure that %m maps to the specified error (or "Success"). */
LOCAL_ERRNO(ERRNO_VALUE(error));
- /* Prepend the object name before the message */
- if (object) {
- size_t n;
-
- n = strlen(object);
- buffer = newa(char, n + 2 + LINE_MAX);
- b = stpcpy(stpcpy(buffer, object), ": ");
- } else
- b = buffer = newa(char, LINE_MAX);
+ LOG_SET_PREFIX(object);
+ b = buffer = newa(char, LINE_MAX);
(void) vsnprintf(b, LINE_MAX, format, ap);
return log_dispatch_internal(level, error, file, line, func,
@@ -1159,6 +1183,17 @@ int log_set_max_level_from_string(const char *e) {
return 0;
}
+static int log_set_ratelimit_kmsg_from_string(const char *e) {
+ int r;
+
+ r = parse_boolean(e);
+ if (r < 0)
+ return r;
+
+ ratelimit_kmsg = r;
+ return 0;
+}
+
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
/*
@@ -1209,6 +1244,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
if (log_show_time_from_string(value ?: "1") < 0)
log_warning("Failed to parse log time setting '%s'. Ignoring.", value);
+ } else if (proc_cmdline_key_streq(key, "systemd.log_ratelimit_kmsg")) {
+
+ if (log_set_ratelimit_kmsg_from_string(value ?: "1") < 0)
+ log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", value);
}
return 0;
@@ -1249,6 +1288,10 @@ void log_parse_environment_variables(void) {
e = getenv("SYSTEMD_LOG_TID");
if (e && log_show_tid_from_string(e) < 0)
log_warning("Failed to parse log tid '%s'. Ignoring.", e);
+
+ e = getenv("SYSTEMD_LOG_RATELIMIT_KMSG");
+ if (e && log_set_ratelimit_kmsg_from_string(e) < 0)
+ log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", e);
}
void log_parse_environment(void) {
@@ -1264,6 +1307,24 @@ LogTarget log_get_target(void) {
return log_target;
}
+void log_settle_target(void) {
+
+ /* If we're using LOG_TARGET_AUTO and opening the log again on every single log call, we'll check if
+ * stderr is attached to the journal every single log call. However, if we then close all file
+ * descriptors later, that will stop working because stderr will be closed as well. To avoid that
+ * problem, this function is used to permanently change the log target depending on whether stderr is
+ * connected to the journal or not. */
+
+ LogTarget t = log_get_target();
+
+ if (t != LOG_TARGET_AUTO)
+ return;
+
+ t = getpid_cached() == 1 || stderr_is_journal() ? (prohibit_ipc ? LOG_TARGET_KMSG : LOG_TARGET_JOURNAL_OR_KMSG)
+ : LOG_TARGET_CONSOLE;
+ log_set_target(t);
+}
+
int log_get_max_level(void) {
return log_max_level;
}
@@ -1542,6 +1603,15 @@ void log_setup(void) {
log_show_color(true);
}
+const char *_log_set_prefix(const char *prefix, bool force) {
+ const char *old = log_prefix;
+
+ if (prefix || force)
+ log_prefix = prefix;
+
+ return old;
+}
+
static int saved_log_context_enabled = -1;
bool log_context_enabled(void) {
@@ -1562,33 +1632,67 @@ bool log_context_enabled(void) {
return saved_log_context_enabled;
}
-LogContext* log_context_attach(LogContext *c) {
+static LogContext* log_context_attach(LogContext *c) {
assert(c);
_log_context_num_fields += strv_length(c->fields);
_log_context_num_fields += c->n_input_iovec;
+ _log_context_num_fields += !!c->key;
return LIST_PREPEND(ll, _log_context, c);
}
-LogContext* log_context_detach(LogContext *c) {
+static LogContext* log_context_detach(LogContext *c) {
if (!c)
return NULL;
- assert(_log_context_num_fields >= strv_length(c->fields) + c->n_input_iovec);
+ assert(_log_context_num_fields >= strv_length(c->fields) + c->n_input_iovec +!!c->key);
_log_context_num_fields -= strv_length(c->fields);
_log_context_num_fields -= c->n_input_iovec;
+ _log_context_num_fields -= !!c->key;
LIST_REMOVE(ll, _log_context, c);
return NULL;
}
-LogContext* log_context_new(char **fields, bool owned) {
+LogContext* log_context_new(const char *key, const char *value) {
+ assert(key);
+ assert(endswith(key, "="));
+ assert(value);
+
+ LIST_FOREACH(ll, i, _log_context)
+ if (i->key == key && i->value == value)
+ return log_context_ref(i);
+
LogContext *c = new(LogContext, 1);
if (!c)
return NULL;
*c = (LogContext) {
+ .n_ref = 1,
+ .key = (char *) key,
+ .value = (char *) value,
+ };
+
+ return log_context_attach(c);
+}
+
+LogContext* log_context_new_strv(char **fields, bool owned) {
+ if (!fields)
+ return NULL;
+
+ LIST_FOREACH(ll, i, _log_context)
+ if (i->fields == fields) {
+ assert(!owned);
+ return log_context_ref(i);
+ }
+
+ LogContext *c = new(LogContext, 1);
+ if (!c)
+ return NULL;
+
+ *c = (LogContext) {
+ .n_ref = 1,
.fields = fields,
.owned = owned,
};
@@ -1596,15 +1700,22 @@ LogContext* log_context_new(char **fields, bool owned) {
return log_context_attach(c);
}
-LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bool owned) {
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned) {
if (!input_iovec || n_input_iovec == 0)
- return NULL; /* Nothing to do */
+ return NULL;
+
+ LIST_FOREACH(ll, i, _log_context)
+ if (i->input_iovec == input_iovec && i->n_input_iovec == n_input_iovec) {
+ assert(!owned);
+ return log_context_ref(i);
+ }
LogContext *c = new(LogContext, 1);
if (!c)
return NULL;
*c = (LogContext) {
+ .n_ref = 1,
.input_iovec = input_iovec,
.n_input_iovec = n_input_iovec,
.owned = owned,
@@ -1613,7 +1724,7 @@ LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bo
return log_context_attach(c);
}
-LogContext* log_context_free(LogContext *c) {
+static LogContext* log_context_free(LogContext *c) {
if (!c)
return NULL;
@@ -1622,21 +1733,25 @@ LogContext* log_context_free(LogContext *c) {
if (c->owned) {
strv_free(c->fields);
iovec_array_free(c->input_iovec, c->n_input_iovec);
+ free(c->key);
+ free(c->value);
}
return mfree(c);
}
-LogContext* log_context_new_consume(char **fields) {
- LogContext *c = log_context_new(fields, /*owned=*/ true);
+DEFINE_TRIVIAL_REF_UNREF_FUNC(LogContext, log_context, log_context_free);
+
+LogContext* log_context_new_strv_consume(char **fields) {
+ LogContext *c = log_context_new_strv(fields, /*owned=*/ true);
if (!c)
strv_free(fields);
return c;
}
-LogContext* log_context_new_consumev(struct iovec *input_iovec, size_t n_input_iovec) {
- LogContext *c = log_context_newv(input_iovec, n_input_iovec, /*owned=*/ true);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec) {
+ LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ true);
if (!c)
iovec_array_free(input_iovec, n_input_iovec);
diff --git a/src/basic/log.h b/src/basic/log.h
index c4ac73e27b..9008d47390 100644
--- a/src/basic/log.h
+++ b/src/basic/log.h
@@ -54,6 +54,7 @@ void log_set_target(LogTarget target);
void log_set_target_and_open(LogTarget target);
int log_set_target_from_string(const char *e);
LogTarget log_get_target(void) _pure_;
+void log_settle_target(void);
void log_set_max_level(int level);
int log_set_max_level_from_string(const char *e);
@@ -425,6 +426,16 @@ typedef struct LogRateLimit {
#define log_ratelimit_error_errno(error, ...) log_ratelimit_full_errno(LOG_ERR, error, __VA_ARGS__)
#define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__)
+const char *_log_set_prefix(const char *prefix, bool force);
+static inline const char *_log_unset_prefixp(const char **p) {
+ assert(p);
+ _log_set_prefix(*p, true);
+ return NULL;
+}
+
+#define LOG_SET_PREFIX(prefix) \
+ _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false);
+
/*
* The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep
* track of a thread local log context onto which we can push extra metadata fields that should be logged.
@@ -457,39 +468,44 @@ typedef struct LogContext LogContext;
bool log_context_enabled(void);
-LogContext* log_context_attach(LogContext *c);
-LogContext* log_context_detach(LogContext *c);
-
-LogContext* log_context_new(char **fields, bool owned);
-LogContext* log_context_newv(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
-LogContext* log_context_free(LogContext *c);
+LogContext* log_context_new(const char *key, const char *value);
+LogContext* log_context_new_strv(char **fields, bool owned);
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
/* Same as log_context_new(), but frees the given fields strv/iovec on failure. */
-LogContext* log_context_new_consume(char **fields);
-LogContext* log_context_new_consumev(struct iovec *input_iovec, size_t n_input_iovec);
+LogContext* log_context_new_strv_consume(char **fields);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec);
+
+LogContext *log_context_ref(LogContext *c);
+LogContext *log_context_unref(LogContext *c);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref);
/* Returns the number of attached log context objects. */
size_t log_context_num_contexts(void);
/* Returns the number of fields in all attached log contexts. */
size_t log_context_num_fields(void);
-DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_detach);
-DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_free);
-
#define LOG_CONTEXT_PUSH(...) \
LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__))
#define LOG_CONTEXT_PUSHF(...) \
LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__))
+#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value);
+
+#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \
+ _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ))
+
#define _LOG_CONTEXT_PUSH_STRV(strv, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new(strv, /*owned=*/ false);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false);
#define LOG_CONTEXT_PUSH_STRV(strv) \
_LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ))
#define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_newv(input_iovec, n_input_iovec, /*owned=*/ false);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false);
#define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \
_LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
@@ -503,19 +519,19 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_free);
_unused_ _cleanup_strv_free_ strv = strv_new(s); \
if (!strv) \
free(s); \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(TAKE_PTR(strv))
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv))
#define LOG_CONTEXT_CONSUME_STR(s) \
_LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ))
#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consume(strv);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv);
#define LOG_CONTEXT_CONSUME_STRV(strv) \
_LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ))
#define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \
- _unused_ _cleanup_(log_context_freep) LogContext *c = log_context_new_consumev(input_iovec, n_input_iovec);
+ _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec);
#define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \
_LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
diff --git a/src/basic/logarithm.h b/src/basic/logarithm.h
deleted file mode 100644
index 646f2d3613..0000000000
--- a/src/basic/logarithm.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-#include <stdint.h>
-
-#include "macro.h"
-
-/* Note: log2(0) == log2(1) == 0 here and below. */
-
-#define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
-#define NONCONST_LOG2ULL(x) ({ \
- unsigned long long _x = (x); \
- _x > 1 ? (unsigned) __builtin_clzll(_x) ^ 63U : 0; \
- })
-#define LOG2ULL(x) __builtin_choose_expr(__builtin_constant_p(x), CONST_LOG2ULL(x), NONCONST_LOG2ULL(x))
-
-static inline unsigned log2u64(uint64_t x) {
-#if __SIZEOF_LONG_LONG__ == 8
- return LOG2ULL(x);
-#else
-# error "Wut?"
-#endif
-}
-
-static inline unsigned u32ctz(uint32_t n) {
-#if __SIZEOF_INT__ == 4
- return n != 0 ? __builtin_ctz(n) : 32;
-#else
-# error "Wut?"
-#endif
-}
-
-#define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0)
-#define NONCONST_LOG2U(x) ({ \
- unsigned _x = (x); \
- _x > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(_x) - 1 : 0; \
- })
-#define LOG2U(x) __builtin_choose_expr(__builtin_constant_p(x), CONST_LOG2U(x), NONCONST_LOG2U(x))
-
-static inline unsigned log2i(int x) {
- return LOG2U(x);
-}
-
-static inline unsigned log2u(unsigned x) {
- return LOG2U(x);
-}
-
-static inline unsigned log2u_round_up(unsigned x) {
- if (x <= 1)
- return 0;
-
- return log2u(x - 1) + 1;
-}
diff --git a/src/basic/macro.h b/src/basic/macro.h
index 2770d01aa9..99262e9206 100644
--- a/src/basic/macro.h
+++ b/src/basic/macro.h
@@ -431,7 +431,7 @@ assert_cc(sizeof(dummy_t) == 0);
})
/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
- * convertable. The iteration variable 'entry' must already be defined. */
+ * convertible. The iteration variable 'entry' must already be defined. */
#define VA_ARGS_FOREACH(entry, ...) \
_VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \
diff --git a/src/basic/meson.build b/src/basic/meson.build
index 5f616c1893..ee94337140 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -14,7 +14,7 @@ basic_sources = files(
'cap-list.c',
'capability-util.c',
'cgroup-util.c',
- 'chase-symlinks.c',
+ 'chase.c',
'chattr-util.c',
'conf-files.c',
'devnum-util.c',
diff --git a/src/basic/missing_fcntl.h b/src/basic/missing_fcntl.h
index 00937d2af0..79e95a8f6f 100644
--- a/src/basic/missing_fcntl.h
+++ b/src/basic/missing_fcntl.h
@@ -58,3 +58,12 @@
#ifndef O_TMPFILE
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
#endif
+
+/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
+ * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
+ * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */
+#if O_LARGEFILE != 0
+#define RAW_O_LARGEFILE O_LARGEFILE
+#else
+#define RAW_O_LARGEFILE 0100000
+#endif
diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c
index 2257a1452f..41af1482bc 100644
--- a/src/basic/mkdir.c
+++ b/src/basic/mkdir.c
@@ -5,7 +5,7 @@
#include <string.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
@@ -33,11 +33,8 @@ int mkdirat_safe_internal(
assert(_mkdirat && _mkdirat != mkdirat);
r = _mkdirat(dir_fd, path, mode);
- if (r >= 0) {
- r = chmod_and_chown_at(dir_fd, path, mode, uid, gid);
- if (r < 0)
- return r;
- }
+ if (r >= 0)
+ return chmod_and_chown_at(dir_fd, path, mode, uid, gid);
if (r != -EEXIST)
return r;
@@ -47,7 +44,7 @@ int mkdirat_safe_internal(
if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
_cleanup_free_ char *p = NULL;
- r = chase_symlinks_at(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
+ r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
if (r < 0)
return r;
if (r == 0)
@@ -234,7 +231,7 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m
if (r < 0)
return r;
- dfd = chase_symlinks_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
+ dfd = chase_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
if (dfd < 0)
return dfd;
}
diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c
index 24e38a34e8..3584f31787 100644
--- a/src/basic/mountpoint-util.c
+++ b/src/basic/mountpoint-util.c
@@ -8,7 +8,7 @@
#endif
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "fd-util.h"
#include "fileio.h"
#include "filesystems.h"
@@ -63,7 +63,7 @@ int name_to_handle_at_loop(
h->handle_bytes = n;
- if (name_to_handle_at(fd, path, h, &mnt_id, flags) >= 0) {
+ if (name_to_handle_at(fd, strempty(path), h, &mnt_id, flags) >= 0) {
if (ret_handle)
*ret_handle = TAKE_PTR(h);
@@ -94,7 +94,9 @@ int name_to_handle_at_loop(
/* The buffer was too small. Size the new buffer by what name_to_handle_at() returned. */
n = h->handle_bytes;
- if (offsetof(struct file_handle, f_handle) + n < n) /* check for addition overflow */
+
+ /* paranoia: check for overflow (note that .handle_bytes is unsigned only) */
+ if (n > UINT_MAX - offsetof(struct file_handle, f_handle))
return -EOVERFLOW;
}
}
@@ -121,18 +123,13 @@ 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;
- p = startswith(fdinfo, "mnt_id:");
- if (!p) {
- p = strstr(fdinfo, "\nmnt_id:");
- if (!p) /* The mnt_id field is a relatively new addition */
- return -EOPNOTSUPP;
-
- p += 8;
- }
+ p = find_line_startswith(fdinfo, "mnt_id:");
+ if (!p) /* The mnt_id field is a relatively new addition */
+ return -EOPNOTSUPP;
p += strspn(p, WHITESPACE);
p[strcspn(p, WHITESPACE)] = 0;
@@ -218,9 +215,14 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
* reported. Also, btrfs subvolumes have different st_dev, even though they aren't real mounts of
* their own. */
- if (statx(fd, filename, (FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW) |
- (flags & AT_EMPTY_PATH) |
- AT_NO_AUTOMOUNT, STATX_TYPE, &sx) < 0) {
+ if (statx(fd,
+ filename,
+ (FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW) |
+ (flags & AT_EMPTY_PATH) |
+ AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */
+ AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */
+ STATX_TYPE,
+ &sx) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
return -errno;
@@ -269,16 +271,16 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
/* If the file handle for the directory we are interested in and its parent are identical,
* we assume this is the root directory, which is a mount point. */
- if (h->handle_bytes == h_parent->handle_bytes &&
- h->handle_type == h_parent->handle_type &&
- memcmp(h->f_handle, h_parent->f_handle, h->handle_bytes) == 0)
+ if (h->handle_type == h_parent->handle_type &&
+ memcmp_nn(h->f_handle, h->handle_bytes,
+ h_parent->f_handle, h_parent->handle_bytes) == 0)
return 1;
return mount_id != mount_id_parent;
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;
@@ -341,7 +343,7 @@ int path_is_mount_point(const char *t, const char *root, int flags) {
* /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
* look at needs to be /usr, not /. */
if (flags & AT_SYMLINK_FOLLOW) {
- r = chase_symlinks(t, root, CHASE_TRAIL_SLASH, &canonical, NULL);
+ r = chase(t, root, CHASE_TRAIL_SLASH, &canonical, NULL);
if (r < 0)
return r;
@@ -360,12 +362,13 @@ int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) {
int r;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
- assert(path);
assert(ret);
if (statx(dir_fd,
- path,
- AT_NO_AUTOMOUNT|(isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW),
+ strempty(path),
+ (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) |
+ AT_NO_AUTOMOUNT | /* don't trigger automounts, mnt_id is a local concept */
+ AT_STATX_DONT_SYNC, /* don't go to the network, mnt_id is a local concept */
STATX_MNT_ID,
&buf.sx) < 0) {
if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
@@ -545,6 +548,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;
@@ -564,12 +569,12 @@ int dev_is_devtmpfs(void) {
if (mid != mount_id)
continue;
- e = strstr(line, " - ");
+ e = strstrafter(line, " - ");
if (!e)
continue;
/* accept any name that starts with the currently expected type */
- if (startswith(e + 3, "devtmpfs"))
+ if (startswith(e, "devtmpfs"))
return true;
}
@@ -718,7 +723,7 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu
_cleanup_close_ int fd = -EBADF;
int r;
- /* Checks if the specified file system supports a mount option. Returns > 0 if it suppors it, == 0 if
+ /* Checks if the specified file system supports a mount option. Returns > 0 if it supports it, == 0 if
* it does not. Return -EAGAIN if we can't determine it. And any other error otherwise. */
assert(fstype);
diff --git a/src/basic/nulstr-util.c b/src/basic/nulstr-util.c
index 98d68e0b01..2acc61886b 100644
--- a/src/basic/nulstr-util.c
+++ b/src/basic/nulstr-util.c
@@ -4,7 +4,7 @@
#include "string-util.h"
#include "strv.h"
-char** strv_parse_nulstr(const char *s, size_t l) {
+char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) {
/* l is the length of the input data, which will be split at NULs into elements of the resulting
* strv. Hence, the number of items in the resulting strv will be equal to one plus the number of NUL
* bytes in the l bytes starting at s, unless s[l-1] is NUL, in which case the final empty string is
@@ -18,6 +18,10 @@ char** strv_parse_nulstr(const char *s, size_t l) {
assert(s || l <= 0);
+ if (drop_trailing_nuls)
+ while (l > 0 && s[l-1] == '\0')
+ l--;
+
if (l <= 0)
return new0(char*, 1);
diff --git a/src/basic/nulstr-util.h b/src/basic/nulstr-util.h
index fd0ed44528..d7bc5fd1ce 100644
--- a/src/basic/nulstr-util.h
+++ b/src/basic/nulstr-util.h
@@ -20,7 +20,10 @@ static inline bool nulstr_contains(const char *nulstr, const char *needle) {
return nulstr_get(nulstr, needle);
}
-char** strv_parse_nulstr(const char *s, size_t l);
+char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls);
+static inline char** strv_parse_nulstr(const char *s, size_t l) {
+ return strv_parse_nulstr_full(s, l, false);
+}
char** strv_split_nulstr(const char *s);
int strv_make_nulstr(char * const *l, char **p, size_t *n);
int set_make_nulstr(Set *s, char **ret, size_t *ret_size);
diff --git a/src/basic/origin-id.h b/src/basic/origin-id.h
new file mode 100644
index 0000000000..c55b0a368a
--- /dev/null
+++ b/src/basic/origin-id.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <pthread.h>
+
+#include "random-util.h"
+
+/* This pattern needs to be repeated exactly in multiple modules, so macro it.
+ * To ensure an object is not passed into a different module (e.g.: when two shared objects statically
+ * linked to libsystemd get loaded in the same process, and the object created by one is passed to the
+ * other, see https://github.com/systemd/systemd/issues/27216), create a random static global random
+ * (mixed with PID, so that we can also check for reuse after fork) that is stored in the object and
+ * checked by public API on use. */
+#define _DEFINE_ORIGIN_ID_HELPERS(type, name, scope) \
+static uint64_t origin_id; \
+ \
+static void origin_id_initialize(void) { \
+ origin_id = random_u64(); \
+} \
+ \
+static uint64_t origin_id_query(void) { \
+ static pthread_once_t once = PTHREAD_ONCE_INIT; \
+ assert_se(pthread_once(&once, origin_id_initialize) == 0); \
+ return origin_id ^ getpid_cached(); \
+} \
+ \
+scope bool name##_origin_changed(type *p) { \
+ assert(p); \
+ return p->origin_id != origin_id_query(); \
+}
+
+#define DEFINE_ORIGIN_ID_HELPERS(type, name) \
+ _DEFINE_ORIGIN_ID_HELPERS(type, name,);
+
+#define DEFINE_PRIVATE_ORIGIN_ID_HELPERS(type, name) \
+ _DEFINE_ORIGIN_ID_HELPERS(type, name, static);
diff --git a/src/basic/os-util.c b/src/basic/os-util.c
index bf844e5b7f..5d06e20871 100644
--- a/src/basic/os-util.c
+++ b/src/basic/os-util.c
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "env-file.h"
#include "env-util.h"
@@ -14,11 +14,36 @@
#include "parse-util.h"
#include "path-util.h"
#include "stat-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "utf8.h"
#include "xattr-util.h"
+static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
+ [IMAGE_MACHINE] = "machine",
+ [IMAGE_PORTABLE] = "portable",
+ [IMAGE_SYSEXT] = "extension",
+ [IMAGE_CONFEXT] = "confext",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
+
+/* Helper struct for naming simplicity and reusability */
+static const struct {
+ const char *release_file_directory;
+ const char *release_file_path_prefix;
+} image_class_release_info[_IMAGE_CLASS_MAX] = {
+ [IMAGE_SYSEXT] = {
+ .release_file_directory = "/usr/lib/extension-release.d/",
+ .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
+ },
+ [IMAGE_CONFEXT] = {
+ .release_file_directory = "/etc/extension-release.d/",
+ .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
+ }
+};
+
bool image_name_is_valid(const char *s) {
if (!filename_is_valid(s))
return false;
@@ -36,7 +61,7 @@ bool image_name_is_valid(const char *s) {
return true;
}
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check) {
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
int r;
assert(path);
@@ -48,8 +73,9 @@ int path_is_extension_tree(const char *path, const char *extension, bool relax_e
return -errno;
/* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
+ * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
* and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
- r = open_extension_release(path, extension, relax_extension_release_check, NULL, NULL);
+ r = open_extension_release(path, image_class, extension, relax_extension_release_check, NULL, NULL);
if (r == -ENOENT) /* We got nothing */
return 0;
if (r < 0)
@@ -96,205 +122,246 @@ static int extension_release_strict_xattr_value(int extension_release_fd, const
return false;
}
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd) {
- _cleanup_free_ char *q = NULL;
- int r, fd;
-
- if (extension) {
- const char *extension_full_path;
-
- if (!image_name_is_valid(extension))
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "The extension name %s is invalid.", extension);
-
- extension_full_path = strjoina("/usr/lib/extension-release.d/extension-release.", extension);
- r = chase_symlinks(extension_full_path, root, CHASE_PREFIX_ROOT,
- ret_path ? &q : NULL,
- ret_fd ? &fd : NULL);
- log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", extension_full_path);
-
- /* Cannot find the expected extension-release file? The image filename might have been
- * mangled on deployment, so fallback to checking for any file in the extension-release.d
- * directory, and return the first one with a user.extension-release xattr instead.
- * The user.extension-release.strict xattr is checked to ensure the author of the image
- * considers it OK if names do not match. */
- if (r == -ENOENT) {
- _cleanup_free_ char *extension_release_dir_path = NULL;
- _cleanup_closedir_ DIR *extension_release_dir = NULL;
-
- r = chase_symlinks_and_opendir("/usr/lib/extension-release.d/", root, CHASE_PREFIX_ROOT,
- &extension_release_dir_path, &extension_release_dir);
- if (r < 0)
- return log_debug_errno(r, "Cannot open %s/usr/lib/extension-release.d/, ignoring: %m", root);
-
- r = -ENOENT;
- FOREACH_DIRENT(de, extension_release_dir, return -errno) {
- int k;
-
- if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
- continue;
-
- const char *image_name = startswith(de->d_name, "extension-release.");
- if (!image_name)
- continue;
-
- if (!image_name_is_valid(image_name)) {
- log_debug("%s/%s is not a valid extension-release file name, ignoring.",
- extension_release_dir_path, de->d_name);
- continue;
- }
-
- /* We already chased the directory, and checked that
- * this is a real file, so we shouldn't fail to open it. */
- _cleanup_close_ int extension_release_fd = openat(dirfd(extension_release_dir),
- de->d_name,
- O_PATH|O_CLOEXEC|O_NOFOLLOW);
- if (extension_release_fd < 0)
- return log_debug_errno(errno,
- "Failed to open extension-release file %s/%s: %m",
- extension_release_dir_path,
- de->d_name);
-
- /* Really ensure it is a regular file after we open it. */
- if (fd_verify_regular(extension_release_fd) < 0) {
- log_debug("%s/%s is not a regular file, ignoring.", extension_release_dir_path, de->d_name);
- continue;
- }
-
- if (!relax_extension_release_check) {
- k = extension_release_strict_xattr_value(extension_release_fd,
- extension_release_dir_path,
- de->d_name);
- if (k != 0)
- continue;
- }
-
- /* We already found what we were looking for, but there's another candidate?
- * We treat this as an error, as we want to enforce that there are no ambiguities
- * in case we are in the fallback path. */
- if (r == 0) {
- r = -ENOTUNIQ;
- break;
- }
-
- r = 0; /* Found it! */
-
- if (ret_fd)
- fd = TAKE_FD(extension_release_fd);
-
- if (ret_path) {
- q = path_join(extension_release_dir_path, de->d_name);
- if (!q)
- return -ENOMEM;
- }
- }
- }
- } else {
- const char *var = secure_getenv("SYSTEMD_OS_RELEASE");
- if (var)
- r = chase_symlinks(var, root, 0,
- ret_path ? &q : NULL,
- ret_fd ? &fd : NULL);
- else
- FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
- r = chase_symlinks(path, root, CHASE_PREFIX_ROOT,
- ret_path ? &q : NULL,
- ret_fd ? &fd : NULL);
- if (r != -ENOENT)
- break;
- }
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
+ const char *e;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ e = secure_getenv("SYSTEMD_OS_RELEASE");
+ if (e)
+ return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+
+ FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
+ r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+ if (r != -ENOENT)
+ return r;
}
+
+ return -ENOENT;
+}
+
+int open_os_release(const char *root, char **ret_path, int *ret_fd) {
+ _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+
+ r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
if (r < 0)
return r;
- if (ret_fd) {
- int real_fd;
+ if (ret_path) {
+ r = chaseat_prefix_root(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd);
+
+ return 0;
+}
+
+int open_extension_release_at(
+ int rfd,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ char **ret_path,
+ int *ret_fd) {
+
+ _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
+ _cleanup_close_ int fd_found = -EBADF;
+ _cleanup_closedir_ DIR *dir = NULL;
+ bool found = false;
+ const char *p;
+ int r;
+
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+ assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
+
+ if (!extension)
+ return open_os_release_at(rfd, ret_path, ret_fd);
+
+ if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
+ return -EINVAL;
+
+ if (!image_name_is_valid(extension))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
+
+ p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
+ r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
+ log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
+ if (r != -ENOENT)
+ return r;
+
+ /* Cannot find the expected extension-release file? The image filename might have been mangled on
+ * deployment, so fallback to checking for any file in the extension-release.d directory, and return
+ * the first one with a user.extension-release xattr instead. The user.extension-release.strict
+ * xattr is checked to ensure the author of the image considers it OK if names do not match. */
+
+ p = image_class_release_info[image_class].release_file_directory;
+ r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
+ if (r < 0)
+ return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
+
+ FOREACH_DIRENT(de, dir, return -errno) {
+ _cleanup_close_ int fd = -EBADF;
+ const char *image_name;
- /* Convert the O_PATH fd into a proper, readable one */
- real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY);
- safe_close(fd);
- if (real_fd < 0)
- return real_fd;
+ if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
+ continue;
+
+ image_name = startswith(de->d_name, "extension-release.");
+ if (!image_name)
+ continue;
- *ret_fd = real_fd;
+ if (!image_name_is_valid(image_name)) {
+ log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
+ continue;
+ }
+
+ /* We already chased the directory, and checked that this is a real file, so we shouldn't
+ * fail to open it. */
+ fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+ if (fd < 0)
+ return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
+
+ /* Really ensure it is a regular file after we open it. */
+ r = fd_verify_regular(fd);
+ if (r < 0) {
+ log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
+ continue;
+ }
+
+ if (!relax_extension_release_check &&
+ extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0)
+ continue;
+
+ /* We already found what we were looking for, but there's another candidate? We treat this as
+ * an error, as we want to enforce that there are no ambiguities in case we are in the
+ * fallback path. */
+ if (found)
+ return -ENOTUNIQ;
+
+ found = true;
+
+ if (ret_fd)
+ fd_found = TAKE_FD(fd);
+
+ if (ret_path) {
+ path_found = path_join(dir_path, de->d_name);
+ if (!path_found)
+ return -ENOMEM;
+ }
}
+ if (!found)
+ return -ENOENT;
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd_found);
if (ret_path)
- *ret_path = TAKE_PTR(q);
+ *ret_path = TAKE_PTR(path_found);
return 0;
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file) {
+int open_extension_release(
+ const char *root,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ char **ret_path,
+ int *ret_fd) {
+
+ _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
_cleanup_free_ char *p = NULL;
- _cleanup_close_ int fd = -EBADF;
- FILE *f;
int r;
- if (!ret_file)
- return open_extension_release(root, extension, relax_extension_release_check, ret_path, NULL);
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
- r = open_extension_release(root, extension, relax_extension_release_check, ret_path ? &p : NULL, &fd);
+ r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
+ ret_path ? &p : NULL, ret_fd ? &fd : NULL);
if (r < 0)
return r;
- f = take_fdopen(&fd, "r");
- if (!f)
- return -errno;
+ if (ret_path) {
+ r = chaseat_prefix_root(p, root, ret_path);
+ if (r < 0)
+ return r;
+ }
- if (ret_path)
- *ret_path = TAKE_PTR(p);
- *ret_file = f;
+ if (ret_fd)
+ *ret_fd = TAKE_FD(fd);
return 0;
}
-static int parse_release_internal(const char *root, bool relax_extension_release_check, const char *extension, va_list ap) {
- _cleanup_fclose_ FILE *f = NULL;
+static int parse_extension_release_atv(
+ int rfd,
+ ImageClass image_class,
+ const char *extension,
+ bool relax_extension_release_check,
+ va_list ap) {
+
+ _cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
if (r < 0)
return r;
- return parse_env_filev(f, p, ap);
+ return parse_env_file_fdv(fd, p, ap);
}
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) {
+int parse_extension_release_at_sentinel(
+ int rfd,
+ ImageClass image_class,
+ bool relax_extension_release_check,
+ const char *extension,
+ ...) {
+
va_list ap;
int r;
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
va_start(ap, extension);
- r = parse_release_internal(root, relax_extension_release_check, extension, ap);
+ r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
va_end(ap);
-
return r;
}
-int _parse_os_release(const char *root, ...) {
+int parse_extension_release_sentinel(
+ const char *root,
+ ImageClass image_class,
+ bool relax_extension_release_check,
+ const char *extension,
+ ...) {
+
+ _cleanup_close_ int rfd = -EBADF;
va_list ap;
int r;
- va_start(ap, root);
- r = parse_release_internal(root, /* relax_extension_release_check= */ false, NULL, ap);
- va_end(ap);
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return -errno;
+ va_start(ap, extension);
+ r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
+ va_end(ap);
return r;
}
-int load_os_release_pairs(const char *root, char ***ret) {
- _cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *p = NULL;
- int r;
-
- r = fopen_os_release(root, &p, &f);
- if (r < 0)
- return r;
-
- return load_env_file_pairs(f, p, ret);
-}
-
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
_cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
int r;
@@ -324,16 +391,16 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char
return 0;
}
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret) {
- _cleanup_fclose_ FILE *f = NULL;
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
+ _cleanup_close_ int fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
- r = fopen_extension_release(root, extension, relax_extension_release_check, &p, &f);
+ r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
if (r < 0)
return r;
- return load_env_file_pairs(f, p, ret);
+ return load_env_file_pairs_fd(fd, p, ret);
}
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
diff --git a/src/basic/os-util.h b/src/basic/os-util.h
index 3bafeaeb92..480f71e614 100644
--- a/src/basic/os-util.h
+++ b/src/basic/os-util.h
@@ -6,33 +6,49 @@
#include "time-util.h"
+typedef enum ImageClass {
+ IMAGE_MACHINE,
+ IMAGE_PORTABLE,
+ IMAGE_SYSEXT,
+ IMAGE_CONFEXT,
+ _IMAGE_CLASS_MAX,
+ _IMAGE_CLASS_INVALID = -EINVAL,
+} ImageClass;
+
+const char* image_class_to_string(ImageClass cl) _const_;
+ImageClass image_class_from_string(const char *s) _pure_;
+
/* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
bool image_name_is_valid(const char *s) _pure_;
-int path_is_extension_tree(const char *path, const char *extension, bool relax_extension_release_check);
+int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check);
static inline int path_is_os_tree(const char *path) {
- return path_is_extension_tree(path, NULL, false);
-}
-
-int open_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
-static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
- return open_extension_release(root, NULL, false, ret_path, ret_fd);
+ return path_is_extension_tree(_IMAGE_CLASS_INVALID, path, NULL, false);
}
-int fopen_extension_release(const char *root, const char *extension, bool relax_extension_release_check, char **ret_path, FILE **ret_file);
-static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
- return fopen_extension_release(root, NULL, false, ret_path, ret_file);
+int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_extension_release_at(int rfd, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+int open_os_release(const char *root, char **ret_path, int *ret_fd);
+int open_os_release_at(int rfd, char **ret_path, int *ret_fd);
+
+int parse_extension_release_sentinel(const char *root, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release(root, image_class, extension, relax_extension_release_check, ...) \
+ parse_extension_release_sentinel(root, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release(root, ...) \
+ parse_extension_release_sentinel(root, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int parse_extension_release_at_sentinel(int rfd, ImageClass image_class, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
+#define parse_extension_release_at(rfd, image_class, extension, relax_extension_release_check, ...) \
+ parse_extension_release_at_sentinel(rfd, image_class, relax_extension_release_check, extension, __VA_ARGS__, NULL)
+#define parse_os_release_at(rfd, ...) \
+ parse_extension_release_at_sentinel(rfd, _IMAGE_CLASS_INVALID, false, NULL, __VA_ARGS__, NULL)
+
+int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret);
+static inline int load_os_release_pairs(const char *root, char ***ret) {
+ return load_extension_release_pairs(root, _IMAGE_CLASS_INVALID, NULL, false, ret);
}
-
-int _parse_extension_release(const char *root, bool relax_extension_release_check, const char *extension, ...) _sentinel_;
-int _parse_os_release(const char *root, ...) _sentinel_;
-#define parse_extension_release(root, relax_extension_release_check, extension, ...) _parse_extension_release(root, relax_extension_release_check, extension, __VA_ARGS__, NULL)
-#define parse_os_release(root, ...) _parse_os_release(root, __VA_ARGS__, NULL)
-
-int load_extension_release_pairs(const char *root, const char *extension, bool relax_extension_release_check, char ***ret);
-int load_os_release_pairs(const char *root, char ***ret);
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol);
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 3445d31307..a53cbc73b8 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -50,7 +50,6 @@ int parse_pid(const char *s, pid_t* ret_pid) {
int r;
assert(s);
- assert(ret_pid);
r = safe_atolu(s, &ul);
if (r < 0)
@@ -64,7 +63,8 @@ int parse_pid(const char *s, pid_t* ret_pid) {
if (!pid_is_valid(pid))
return -ERANGE;
- *ret_pid = pid;
+ if (ret_pid)
+ *ret_pid = pid;
return 0;
}
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index 40a819d47d..0b0f0da760 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -8,7 +8,7 @@
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fs-util.h"
@@ -285,7 +285,7 @@ char **path_strv_resolve(char **l, const char *root) {
} else
t = *s;
- r = chase_symlinks(t, root, 0, &u, NULL);
+ r = chase(t, root, 0, &u, NULL);
if (r == -ENOENT) {
if (root) {
u = TAKE_PTR(orig);
@@ -491,25 +491,33 @@ bool path_equal_or_files_same(const char *a, const char *b, int flags) {
return path_equal(a, b) || files_same(a, b, flags) > 0;
}
-bool path_equal_filename(const char *a, const char *b) {
- _cleanup_free_ char *a_basename = NULL, *b_basename = NULL;
- int r;
+int path_compare_filename(const char *a, const char *b) {
+ _cleanup_free_ char *fa = NULL, *fb = NULL;
+ int r, j, k;
- assert(a);
- assert(b);
+ /* Order NULL before non-NULL */
+ r = CMP(!!a, !!b);
+ if (r != 0)
+ return r;
- r = path_extract_filename(a, &a_basename);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse basename of %s: %m", a);
- return false;
- }
- r = path_extract_filename(b, &b_basename);
- if (r < 0) {
- log_debug_errno(r, "Failed to parse basename of %s: %m", b);
- return false;
- }
+ j = path_extract_filename(a, &fa);
+ k = path_extract_filename(b, &fb);
- return path_equal(a_basename, b_basename);
+ /* When one of paths is "." or root, then order it earlier. */
+ r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL);
+ if (r != 0)
+ return r;
+
+ /* When one of paths is invalid (or we get OOM), 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. */
+ if (j < 0)
+ return strcmp(a, b);
+
+ return strcmp(fa, fb);
}
char* path_extend_internal(char **x, ...) {
@@ -621,16 +629,13 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f
assert(name);
- /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of
+ /* Function chase() is invoked only when root is not NULL, as using it regardless of
* root value would alter the behavior of existing callers for example: /bin/sleep would become
* /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when
* needed to avoid unforeseen regression or other complicated changes. */
if (root) {
- r = chase_symlinks(name,
- root,
- CHASE_PREFIX_ROOT,
- &path_name,
- /* ret_fd= */ NULL); /* prefix root to name in case full paths are not specified */
+ /* prefix root to name in case full paths are not specified */
+ r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL);
if (r < 0)
return r;
@@ -896,6 +901,8 @@ static const char *skip_slash_or_dot_backward(const char *path, const char *q) {
continue;
if (q > path && strneq(q - 1, "/.", 2))
continue;
+ if (q == path && *q == '.')
+ continue;
break;
}
return q;
@@ -920,6 +927,12 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char *
* ret: "bbbbb/cc//././"
* return value: 5 (== strlen("bbbbb"))
*
+ * Input: path: "//.//aaa///bbbbb/cc//././"
+ * next: "///bbbbb/cc//././"
+ * Output: next: "//.//aaa///bbbbb/cc//././" (next == path)
+ * ret: "aaa///bbbbb/cc//././"
+ * return value: 3 (== strlen("aaa"))
+ *
* Input: path: "/", ".", "", or NULL
* Output: next: equivalent to path
* ret: NULL
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index 56f01f41d8..7843599816 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -66,15 +66,18 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot
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_;
+int path_compare(const char *a, const char *b) _pure_;
static inline bool path_equal(const char *a, const char *b) {
return path_compare(a, b) == 0;
}
+int path_compare_filename(const char *a, const char *b);
+static inline bool path_equal_filename(const char *a, const char *b) {
+ return path_compare_filename(a, b) == 0;
+}
+
bool path_equal_or_files_same(const char *a, const char *b, int flags);
-/* Compares only the last portion of the input paths, ie: the filenames */
-bool path_equal_filename(const char *a, const char *b);
char* path_extend_internal(char **x, ...);
#define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c
index eea70d8606..39e9f2c668 100644
--- a/src/basic/proc-cmdline.c
+++ b/src/basic/proc-cmdline.c
@@ -7,17 +7,79 @@
#include "efivars.h"
#include "extract-word.h"
#include "fileio.h"
+#include "getopt-defs.h"
#include "initrd-util.h"
#include "macro.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
-#include "special.h"
#include "string-util.h"
+#include "strv.h"
#include "virt.h"
+int proc_cmdline_filter_pid1_args(
+ char **argv, /* input, may be reordered by this function. */
+ char ***ret) {
+
+ enum {
+ COMMON_GETOPT_ARGS,
+ SYSTEMD_GETOPT_ARGS,
+ SHUTDOWN_GETOPT_ARGS,
+ };
+
+ static const struct option options[] = {
+ COMMON_GETOPT_OPTIONS,
+ SYSTEMD_GETOPT_OPTIONS,
+ SHUTDOWN_GETOPT_OPTIONS,
+ {}
+ };
+
+ int saved_optind, saved_opterr, saved_optopt, argc;
+ char *saved_optarg;
+ char **filtered;
+ size_t idx;
+
+ assert(argv);
+ assert(ret);
+
+ /* Backup global variables. */
+ saved_optind = optind;
+ saved_opterr = opterr;
+ saved_optopt = optopt;
+ saved_optarg = optarg;
+
+ /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
+ * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
+ * the GNU extensions, but might be used previously. Hence, we need to always reset it. */
+ optind = 0;
+
+ /* Do not print an error message. */
+ opterr = 0;
+
+ /* Filter out all known options. */
+ argc = strv_length(argv);
+ while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
+ ;
+
+ idx = optind;
+
+ /* Restore global variables. */
+ optind = saved_optind;
+ opterr = saved_opterr;
+ optopt = saved_optopt;
+ optarg = saved_optarg;
+
+ filtered = strv_copy(strv_skip(argv, idx));
+ if (!filtered)
+ return -ENOMEM;
+
+ *ret = filtered;
+ return 0;
+}
+
int proc_cmdline(char **ret) {
const char *e;
+
assert(ret);
/* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
@@ -39,71 +101,86 @@ int proc_cmdline(char **ret) {
return read_one_line_file("/proc/cmdline", ret);
}
-static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
- const char *q = *p;
+static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
+ const char *e;
int r;
- for (;;) {
- _cleanup_free_ char *word = NULL;
- const char *c;
+ assert(ret);
+
+ /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
+ e = secure_getenv("SYSTEMD_PROC_CMDLINE");
+ if (e)
+ return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
- r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (detect_container() > 0) {
+ _cleanup_strv_free_ char **args = NULL;
+
+ r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
if (r < 0)
return r;
- if (r == 0)
- break;
- /* Filter out arguments that are intended only for the initrd */
- c = startswith(word, "rd.");
- if (c) {
- if (!in_initrd())
- continue;
+ if (filter_pid1_args)
+ return proc_cmdline_filter_pid1_args(args, ret);
- if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
- r = free_and_strdup(&word, c);
- if (r < 0)
- return r;
- }
+ *ret = TAKE_PTR(args);
+ return 0;
+
+ } else {
+ _cleanup_free_ char *s = NULL;
- } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
- continue; /* And optionally filter out arguments that are intended only for the host */
+ r = read_one_line_file("/proc/cmdline", &s);
+ if (r < 0)
+ return r;
- *p = q;
- *ret_word = TAKE_PTR(word);
- return 1;
+ return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
}
+}
- *p = q;
- *ret_word = NULL;
- return 0;
+int proc_cmdline_strv(char ***ret) {
+ return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
}
-int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
- const char *p;
+static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
+ char *c;
+
+ c = startswith(word, "rd.");
+ if (c) {
+ /* Filter out arguments that are intended only for the initrd */
+
+ if (!in_initrd())
+ return NULL;
+
+ if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
+ return c;
+
+ } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
+ /* And optionally filter out arguments that are intended only for the host */
+ return NULL;
+
+ return (char*) word;
+}
+
+static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
int r;
assert(parse_item);
- /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
- * clear. */
+ /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
+ * make this clear. */
assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
- char *value;
+ STRV_FOREACH(word, args) {
+ char *key, *value;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0)
- break;
+ key = mangle_word(*word, flags);
+ if (!key)
+ continue;
- value = strchr(word, '=');
+ value = strchr(key, '=');
if (value)
- *(value++) = 0;
+ *(value++) = '\0';
- r = parse_item(word, value, data);
+ r = parse_item(key, value, data);
if (r < 0)
return r;
}
@@ -112,7 +189,7 @@ int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item,
}
int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
- _cleanup_free_ char *line = NULL;
+ _cleanup_strv_free_ char **args = NULL;
int r;
assert(parse_item);
@@ -120,24 +197,30 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineF
/* We parse the EFI variable first, because later settings have higher priority. */
if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+ _cleanup_free_ char *line = NULL;
+
r = systemd_efi_options_variable(&line);
if (r < 0) {
if (r != -ENODATA)
log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
} else {
- r = proc_cmdline_parse_given(line, parse_item, data, flags);
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
- line = mfree(line);
+ r = proc_cmdline_parse_strv(args, parse_item, data, flags);
+ if (r < 0)
+ return r;
+
+ args = strv_free(args);
}
}
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
- return proc_cmdline_parse_given(line, parse_item, data, flags);
+ return proc_cmdline_parse_strv(args, parse_item, data, flags);
}
static bool relaxed_equal_char(char a, char b) {
@@ -172,24 +255,19 @@ bool proc_cmdline_key_streq(const char *x, const char *y) {
return true;
}
-static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
- _cleanup_free_ char *ret = NULL;
+static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
+ _cleanup_free_ char *v = NULL;
bool found = false;
- const char *p;
int r;
- assert(line);
assert(key);
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(p, args) {
+ const char *word;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0)
- break;
+ word = mangle_word(*p, flags);
+ if (!word)
+ continue;
if (ret_value) {
const char *e;
@@ -199,7 +277,7 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
continue;
if (*e == '=') {
- r = free_and_strdup(&ret, e+1);
+ r = free_and_strdup(&v, e+1);
if (r < 0)
return r;
@@ -209,7 +287,7 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
found = true;
} else {
- if (streq(word, key)) {
+ if (proc_cmdline_key_streq(word, key)) {
found = true;
break; /* we found what we were looking for */
}
@@ -217,12 +295,13 @@ static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags f
}
if (ret_value)
- *ret_value = TAKE_PTR(ret);
+ *ret_value = TAKE_PTR(v);
return found;
}
int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
+ _cleanup_strv_free_ char **args = NULL;
_cleanup_free_ char *line = NULL, *v = NULL;
int r;
@@ -246,14 +325,14 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
return -EINVAL;
- r = proc_cmdline(&line);
+ r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
- return cmdline_get_key(line, key, flags, ret_value);
+ return cmdline_get_key(args, key, flags, ret_value);
- r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
+ r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL);
if (r < 0)
return r;
if (r > 0) {
@@ -263,7 +342,6 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
return r;
}
- line = mfree(line);
r = systemd_efi_options_variable(&line);
if (r == -ENODATA) {
if (ret_value)
@@ -274,7 +352,12 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
if (r < 0)
return r;
- return cmdline_get_key(line, key, flags, ret_value);
+ args = strv_free(args);
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
+
+ return cmdline_get_key(args, key, flags, ret_value);
}
int proc_cmdline_get_bool(const char *key, bool *ret) {
@@ -302,75 +385,82 @@ int proc_cmdline_get_bool(const char *key, bool *ret) {
return 1;
}
-int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
- _cleanup_free_ char *line = NULL;
- bool processing_efi = true;
- const char *p;
- va_list ap;
+static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
int r, ret = 0;
- /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
- * this clear. */
- assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
+ for (;;) {
+ char **v;
+ const char *k, *e;
- /* This call may clobber arguments on failure! */
+ k = va_arg(ap, const char*);
+ if (!k)
+ break;
- if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
- r = systemd_efi_options_variable(&line);
- if (r < 0 && r != -ENODATA)
- log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
- }
+ assert_se(v = va_arg(ap, char**));
- p = line;
- for (;;) {
- _cleanup_free_ char *word = NULL;
+ STRV_FOREACH(p, args) {
+ const char *word;
- r = proc_cmdline_extract_first(&p, &word, flags);
- if (r < 0)
- return r;
- if (r == 0) {
- /* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */
- if (processing_efi) {
- processing_efi = false;
+ word = mangle_word(*p, flags);
+ if (!word)
+ continue;
- line = mfree(line);
- r = proc_cmdline(&line);
+ e = proc_cmdline_key_startswith(word, k);
+ if (e && *e == '=') {
+ r = free_and_strdup(v, e + 1);
if (r < 0)
return r;
- p = line;
- continue;
+ ret++;
}
-
- break;
}
+ }
- va_start(ap, flags);
+ return ret;
+}
- for (;;) {
- char **v;
- const char *k, *e;
+int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
+ _cleanup_strv_free_ char **args = NULL;
+ int r, ret = 0;
+ va_list ap;
- k = va_arg(ap, const char*);
- if (!k)
- break;
+ /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
+ * this clear. */
+ assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
- assert_se(v = va_arg(ap, char**));
+ /* This call may clobber arguments on failure! */
- e = proc_cmdline_key_startswith(word, k);
- if (e && *e == '=') {
- r = free_and_strdup(v, e + 1);
- if (r < 0) {
- va_end(ap);
- return r;
- }
+ if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
+ _cleanup_free_ char *line = NULL;
- ret++;
- }
- }
+ r = systemd_efi_options_variable(&line);
+ if (r < 0 && r != -ENODATA)
+ log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
+ if (r >= 0) {
+ r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
- va_end(ap);
+ va_start(ap, flags);
+ r = cmdline_get_key_ap(flags, args, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ ret = r;
+ args = strv_free(args);
+ }
}
- return ret;
+ r = proc_cmdline_strv(&args);
+ if (r < 0)
+ return r;
+
+ va_start(ap, flags);
+ r = cmdline_get_key_ap(flags, args, ap);
+ va_end(ap);
+ if (r < 0)
+ return r;
+
+ return ret + r;
}
diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h
index 45f3a27f27..a64d7757bc 100644
--- a/src/basic/proc-cmdline.h
+++ b/src/basic/proc-cmdline.h
@@ -14,9 +14,11 @@ typedef enum ProcCmdlineFlags {
typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data);
+int proc_cmdline_filter_pid1_args(char **argv, char ***ret);
+
int proc_cmdline(char **ret);
+int proc_cmdline_strv(char ***ret);
-int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags);
int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, ProcCmdlineFlags flags);
int proc_cmdline_get_key(const char *parameter, ProcCmdlineFlags flags, char **value);
diff --git a/src/basic/process-util.c b/src/basic/process-util.c
index 5d499a57b4..7de7d80cd4 100644
--- a/src/basic/process-util.c
+++ b/src/basic/process-util.c
@@ -222,18 +222,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
_cleanup_strv_free_ char **args = NULL;
- args = strv_parse_nulstr(t, k);
+ /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end.
+ * See also issue #21186. */
+ args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
if (!args)
return -ENOMEM;
- /* Drop trailing empty strings. See issue #21186. */
- STRV_FOREACH_BACKWARDS(p, args) {
- if (!isempty(*p))
- break;
-
- *p = mfree(*p);
- }
-
ans = quote_command_line(args, shflags);
if (!ans)
return -ENOMEM;
@@ -259,6 +253,28 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
return 0;
}
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
+ _cleanup_free_ char *t = NULL;
+ char **args;
+ size_t k;
+ int r;
+
+ assert(pid >= 0);
+ assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0);
+ assert(ret);
+
+ r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
+ if (r < 0)
+ return r;
+
+ args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
+ if (!args)
+ return -ENOMEM;
+
+ *ret = args;
+ return 0;
+}
+
int container_get_leader(const char *machine, pid_t *pid) {
_cleanup_free_ char *s = NULL, *class = NULL;
const char *p;
@@ -1223,6 +1239,7 @@ int safe_fork_full(
/* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
log_close();
log_set_open_when_needed(true);
+ log_settle_target();
}
if (name) {
@@ -1348,6 +1365,14 @@ int safe_fork_full(
}
}
+ if (!FLAGS_SET(flags, FORK_KEEP_NOTIFY_SOCKET)) {
+ r = RET_NERRNO(unsetenv("NOTIFY_SOCKET"));
+ if (r < 0) {
+ log_full_errno(prio, r, "Failed to unset $NOTIFY_SOCKET: %m");
+ _exit(EXIT_FAILURE);
+ }
+ }
+
if (ret_pid)
*ret_pid = getpid_cached();
@@ -1447,6 +1472,15 @@ int pidfd_get_pid(int fd, pid_t *ret) {
char *p;
int r;
+ /* Converts a pidfd into a pid. Well known errors:
+ *
+ * -EBADF → fd invalid
+ * -ENOSYS → /proc/ not mounted
+ * -ENOTTY → fd valid, but not a pidfd
+ * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
+ * -ESRCH → fd valid, but process is already reaped
+ */
+
if (fd < 0)
return -EBADF;
@@ -1454,22 +1488,22 @@ int pidfd_get_pid(int fd, pid_t *ret) {
r = read_full_virtual_file(path, &fdinfo, NULL);
if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */
- return -ESRCH;
+ return proc_mounted() > 0 ? -EBADF : -ENOSYS;
if (r < 0)
return r;
- p = startswith(fdinfo, "Pid:");
- if (!p) {
- p = strstr(fdinfo, "\nPid:");
- if (!p)
- return -ENOTTY; /* not a pidfd? */
-
- p += 5;
- }
+ p = find_line_startswith(fdinfo, "Pid:");
+ if (!p)
+ return -ENOTTY; /* not a pidfd? */
p += strspn(p, WHITESPACE);
p[strcspn(p, WHITESPACE)] = 0;
+ if (streq(p, "0"))
+ return -EREMOTE; /* PID is in foreign PID namespace? */
+ if (streq(p, "-1"))
+ return -ESRCH; /* refers to reaped process? */
+
return parse_pid(p, ret);
}
diff --git a/src/basic/process-util.h b/src/basic/process-util.h
index 6f6fc7d94e..230a0edb09 100644
--- a/src/basic/process-util.h
+++ b/src/basic/process-util.h
@@ -40,6 +40,7 @@ typedef enum ProcessCmdlineFlags {
int get_process_comm(pid_t pid, char **ret);
int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret);
int get_process_exe(pid_t pid, char **ret);
int get_process_uid(pid_t pid, uid_t *ret);
int get_process_gid(pid_t pid, gid_t *ret);
@@ -155,6 +156,7 @@ typedef enum ForkFlags {
FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */
FORK_NEW_USERNS = 1 << 14, /* Run child in its own user namespace */
FORK_CLOEXEC_OFF = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
+ FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
} ForkFlags;
int safe_fork_full(
diff --git a/src/basic/ratelimit.c b/src/basic/ratelimit.c
index f90a63b1a9..5675ec2f46 100644
--- a/src/basic/ratelimit.c
+++ b/src/basic/ratelimit.c
@@ -10,7 +10,6 @@
bool ratelimit_below(RateLimit *r) {
usec_t ts;
- bool good = false;
assert(r);
@@ -21,20 +20,32 @@ bool ratelimit_below(RateLimit *r) {
if (r->begin <= 0 ||
usec_sub_unsigned(ts, r->begin) > r->interval) {
- r->begin = ts;
+ r->begin = ts; /* Start a new time window */
+ r->num = 1; /* Reset counter */
+ return true;
+ }
- /* Reset counter */
- r->num = 0;
- good = true;
- } else if (r->num < r->burst)
- good = true;
+ if (_unlikely_(r->num == UINT_MAX))
+ return false;
r->num++;
- return good;
+ return r->num <= r->burst;
}
unsigned ratelimit_num_dropped(RateLimit *r) {
assert(r);
- return r->num > r->burst ? r->num - r->burst : 0;
+ if (r->num == UINT_MAX) /* overflow, return as special case */
+ return UINT_MAX;
+
+ return LESS_BY(r->num, r->burst);
+}
+
+usec_t ratelimit_end(const RateLimit *rl) {
+ assert(rl);
+
+ if (rl->begin == 0)
+ return 0;
+
+ return usec_add(rl->begin, rl->interval);
}
diff --git a/src/basic/ratelimit.h b/src/basic/ratelimit.h
index 2236189851..048084ece4 100644
--- a/src/basic/ratelimit.h
+++ b/src/basic/ratelimit.h
@@ -23,3 +23,5 @@ static inline bool ratelimit_configured(RateLimit *rl) {
bool ratelimit_below(RateLimit *r);
unsigned ratelimit_num_dropped(RateLimit *r);
+
+usec_t ratelimit_end(const RateLimit *rl);
diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
index 43b292252c..5b76948c06 100644
--- a/src/basic/socket-util.c
+++ b/src/basic/socket-util.c
@@ -1047,7 +1047,7 @@ ssize_t receive_one_fd_iov(
}
if (found)
- *ret_fd = *(int*) CMSG_DATA(found);
+ *ret_fd = *CMSG_TYPED_DATA(found, int);
else
*ret_fd = -EBADF;
@@ -1171,6 +1171,24 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng
return NULL;
}
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len) {
+ struct cmsghdr *cmsg;
+
+ assert(mh);
+ assert(buf);
+ assert(buf_len > 0);
+
+ /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used
+ * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on
+ * RISCV32. See issue #27241. */
+
+ cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len));
+ if (!cmsg)
+ return NULL;
+
+ return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len);
+}
+
int socket_ioctl_fd(void) {
int fd;
diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
index 0bfb29d417..b323b1b99f 100644
--- a/src/basic/socket-util.h
+++ b/src/basic/socket-util.h
@@ -175,18 +175,30 @@ int flush_accept(int fd);
#define CMSG_FOREACH(cmsg, mh) \
for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
+/* Returns the cmsghdr's data pointer, but safely cast to the specified type. Does two alignment checks: one
+ * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one
+ * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as
+ * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8
+ * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as
+ * riscv32. */
#define CMSG_TYPED_DATA(cmsg, type) \
({ \
- struct cmsghdr *_cmsg = cmsg; \
+ struct cmsghdr *_cmsg = (cmsg); \
+ assert_cc(alignof(type) <= alignof(struct cmsghdr)); \
_cmsg ? CAST_ALIGN_PTR(type, CMSG_DATA(_cmsg)) : (type*) NULL; \
})
struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len);
/* Type-safe, dereferencing version of cmsg_find() */
#define CMSG_FIND_DATA(mh, level, type, ctype) \
CMSG_TYPED_DATA(cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))), ctype)
+/* Type-safe version of cmsg_find_and_copy_data() */
+#define CMSG_FIND_AND_COPY_DATA(mh, level, type, ctype) \
+ (ctype*) cmsg_find_and_copy_data(mh, level, type, &(ctype){}, sizeof(ctype))
+
/* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type
* itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr"
* structures. */
diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c
index 88b8346a0f..335daca234 100644
--- a/src/basic/stat-util.c
+++ b/src/basic/stat-util.c
@@ -8,7 +8,7 @@
#include <unistd.h>
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "errno-util.h"
#include "fd-util.h"
@@ -145,24 +145,13 @@ int null_or_empty_path_with_root(const char *fn, const char *root) {
if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
return true;
- r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
+ r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
if (r < 0)
return r;
return null_or_empty(&st);
}
-int null_or_empty_fd(int fd) {
- struct stat st;
-
- assert(fd >= 0);
-
- if (fstat(fd, &st) < 0)
- return -errno;
-
- return null_or_empty(&st);
-}
-
static int fd_is_read_only_fs(int fd) {
struct statvfs st;
@@ -201,10 +190,10 @@ int files_same(const char *filea, const char *fileb, int flags) {
assert(fileb);
if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
- return -errno;
+ return log_debug_errno(errno, "Cannot stat %s: %m", filea);
if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
- return -errno;
+ return log_debug_errno(errno, "Cannot stat %s: %m", fileb);
return stat_inode_same(&a, &b);
}
@@ -307,6 +296,18 @@ int fd_verify_regular(int fd) {
return stat_verify_regular(&st);
}
+int verify_regular_at(int dir_fd, const char *path, bool follow) {
+ struct stat st;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+
+ if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
+ return -errno;
+
+ return stat_verify_regular(&st);
+}
+
int stat_verify_directory(const struct stat *st) {
assert(st);
@@ -454,6 +455,20 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
return 0;
}
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+ assert(path);
+ assert(ret);
+
+ fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, 0);
+ if (fd < 0)
+ return fd;
+
+ return RET_NERRNO(fstatfs(fd, ret));
+}
+
void inode_hash_func(const struct stat *q, struct siphash *state) {
siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
@@ -470,3 +485,26 @@ int inode_compare_func(const struct stat *a, const struct stat *b) {
}
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
+
+const char* inode_type_to_string(mode_t m) {
+
+ /* Returns a short string for the inode type. We use the same name as the underlying macros for each
+ * inode type. */
+
+ switch (m & S_IFMT) {
+ case S_IFREG:
+ return "reg";
+ case S_IFDIR:
+ return "dir";
+ case S_IFCHR:
+ return "chr";
+ case S_IFBLK:
+ return "blk";
+ case S_IFIFO:
+ return "fifo";
+ case S_IFSOCK:
+ return "sock";
+ }
+
+ return NULL;
+}
diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h
index de11c0cf7c..e6b84d215e 100644
--- a/src/basic/stat-util.h
+++ b/src/basic/stat-util.h
@@ -30,7 +30,6 @@ static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
bool null_or_empty(struct stat *st) _pure_;
int null_or_empty_path_with_root(const char *fn, const char *root);
-int null_or_empty_fd(int fd);
static inline int null_or_empty_path(const char *fn) {
return null_or_empty_path_with_root(fn, NULL);
@@ -65,6 +64,7 @@ int path_is_network_fs(const char *path);
int stat_verify_regular(const struct stat *st);
int fd_verify_regular(int fd);
+int verify_regular_at(int dir_fd, const char *path, bool follow);
int stat_verify_directory(const struct stat *st);
int fd_verify_directory(int fd);
@@ -79,6 +79,8 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b);
int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx);
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
+
#if HAS_FEATURE_MEMORY_SANITIZER
# warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
# define STRUCT_STATX_DEFINE(var) \
@@ -101,3 +103,5 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
void inode_hash_func(const struct stat *q, struct siphash *state);
int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
+
+const char* inode_type_to_string(mode_t m);
diff --git a/src/basic/string-table.h b/src/basic/string-table.h
index e3a26a623c..3be70dfade 100644
--- a/src/basic/string-table.h
+++ b/src/basic/string-table.h
@@ -95,6 +95,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,) \
_DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
+#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
diff --git a/src/basic/string-util.c b/src/basic/string-util.c
index ad8c9863bd..c74ee67dfe 100644
--- a/src/basic/string-util.c
+++ b/src/basic/string-util.c
@@ -1260,3 +1260,38 @@ char *strdupcspn(const char *a, const char *reject) {
return strndup(a, strcspn(a, reject));
}
+
+char *find_line_startswith(const char *haystack, const char *needle) {
+ char *p;
+
+ assert(haystack);
+ assert(needle);
+
+ /* Finds the first line in 'haystack' that starts with the specified string. Returns a pointer to the
+ * first character after it */
+
+ p = strstr(haystack, needle);
+ if (!p)
+ return NULL;
+
+ if (p > haystack)
+ while (p[-1] != '\n') {
+ p = strstr(p + 1, needle);
+ if (!p)
+ return NULL;
+ }
+
+ return p + strlen(needle);
+}
+
+char *startswith_strv(const char *string, char **strv) {
+ char *found = NULL;
+
+ STRV_FOREACH(i, strv) {
+ found = startswith(string, *i);
+ if (found)
+ break;
+ }
+
+ return found;
+}
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index e0a47a21a9..4430910e22 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -29,6 +29,18 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) {
return strstr(haystack, needle);
}
+static inline char *strstrafter(const char *haystack, const char *needle) {
+ char *p;
+
+ /* Returns NULL if not found, or pointer to first character after needle if found */
+
+ p = strstr_ptr(haystack, needle);
+ if (!p)
+ return NULL;
+
+ return p + strlen(needle);
+}
+
static inline const char* strnull(const char *s) {
return s ?: "(null)";
}
@@ -253,3 +265,10 @@ size_t strspn_from_end(const char *str, const char *accept);
char *strdupspn(const char *a, const char *accept);
char *strdupcspn(const char *a, const char *reject);
+
+char *find_line_startswith(const char *haystack, const char *needle);
+
+char *startswith_strv(const char *string, char **strv);
+
+#define STARTSWITH_SET(p, ...) \
+ startswith_strv(p, STRV_MAKE(__VA_ARGS__))
diff --git a/src/basic/strv.c b/src/basic/strv.c
index 5fcf3620a6..822dadeeb4 100644
--- a/src/basic/strv.c
+++ b/src/basic/strv.c
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include "alloc-util.h"
+#include "env-util.h"
#include "escape.h"
#include "extract-word.h"
#include "fileio.h"
@@ -63,6 +64,16 @@ char* strv_find_startswith(char * const *l, const char *name) {
return NULL;
}
+char* strv_find_first_field(char * const *needles, char * const *haystack) {
+ STRV_FOREACH(k, needles) {
+ char *value = strv_env_pairs_get((char **)haystack, *k);
+ if (value)
+ return value;
+ }
+
+ return NULL;
+}
+
char** strv_free(char **l) {
STRV_FOREACH(k, l)
free(*k);
diff --git a/src/basic/strv.h b/src/basic/strv.h
index 419cda1ee3..544d46a3f8 100644
--- a/src/basic/strv.h
+++ b/src/basic/strv.h
@@ -17,6 +17,9 @@ char* strv_find(char * const *l, const char *name) _pure_;
char* strv_find_case(char * const *l, const char *name) _pure_;
char* strv_find_prefix(char * const *l, const char *name) _pure_;
char* strv_find_startswith(char * const *l, const char *name) _pure_;
+/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
+ * of the first key from the first vector that is found in the second vector. */
+char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;
#define strv_contains(l, s) (!!strv_find((l), (s)))
#define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
@@ -197,18 +200,6 @@ static inline void strv_print(char * const *l) {
_x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
})
-#define STARTSWITH_SET(p, ...) \
- ({ \
- const char *_p = (p); \
- char *_found = NULL; \
- STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \
- _found = startswith(_p, *_i); \
- if (_found) \
- break; \
- } \
- _found; \
- })
-
#define ENDSWITH_SET(p, ...) \
({ \
const char *_p = (p); \
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index d3aa5c27e2..796c262acf 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -979,7 +979,7 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
&ttynr) != 1)
return -EIO;
- if (major(ttynr) == 0 && minor(ttynr) == 0)
+ if (devnum_is_zero(ttynr))
return -ENXIO;
if (d)
diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c
index 379d81d5c8..d44464dd7b 100644
--- a/src/basic/tmpfile-util.c
+++ b/src/basic/tmpfile-util.c
@@ -272,7 +272,7 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
return fd;
}
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
_cleanup_free_ char *tmp = NULL;
int r, fd;
@@ -286,7 +286,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
* which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
* "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
- fd = open_parent(target, O_TMPFILE|flags, 0640);
+ fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
if (fd >= 0) {
*ret_path = NULL;
return fd;
@@ -298,7 +298,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
if (r < 0)
return r;
- fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+ fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
if (fd < 0)
return -errno;
@@ -349,11 +349,12 @@ static int link_fd(int fd, int newdirfd, const char *newpath) {
return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
}
-int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, bool replace) {
_cleanup_free_ char *tmp = NULL;
int r;
assert(fd >= 0);
+ assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(target);
/* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
@@ -362,12 +363,12 @@ int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
if (path) {
if (replace)
- return RET_NERRNO(rename(path, target));
+ return RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
- return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+ return rename_noreplace(dir_fd, path, dir_fd, target);
}
- r = link_fd(fd, AT_FDCWD, target);
+ r = link_fd(fd, dir_fd, target);
if (r != -EEXIST || !replace)
return r;
@@ -381,12 +382,12 @@ int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
if (r < 0)
return r;
- if (link_fd(fd, AT_FDCWD, tmp) < 0)
+ if (link_fd(fd, dir_fd, tmp) < 0)
return -EEXIST; /* propagate original error */
- r = RET_NERRNO(rename(tmp, target));
+ r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
if (r < 0) {
- (void) unlink(tmp);
+ (void) unlinkat(dir_fd, tmp, 0);
return r;
}
diff --git a/src/basic/tmpfile-util.h b/src/basic/tmpfile-util.h
index 4665dafb24..f48ce10e68 100644
--- a/src/basic/tmpfile-util.h
+++ b/src/basic/tmpfile-util.h
@@ -2,6 +2,7 @@
#pragma once
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
@@ -22,10 +23,16 @@ int tempfn_random(const char *p, const char *extra, char **ret);
int tempfn_random_child(const char *p, const char *extra, char **ret);
int open_tmpfile_unlinkable(const char *directory, int flags);
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path);
+static inline int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+ return open_tmpfile_linkable_at(AT_FDCWD, target, flags, ret_path);
+}
int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file);
-int link_tmpfile(int fd, const char *path, const char *target, bool replace);
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, bool replace);
+static inline int link_tmpfile(int fd, const char *path, const char *target, bool replace) {
+ return link_tmpfile_at(fd, AT_FDCWD, path, target, replace);
+}
int flink_tmpfile(FILE *f, const char *path, const char *target, bool replace);
int mkdtemp_malloc(const char *template, char **ret);
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
index bdb1860246..86b66e2be0 100644
--- a/src/basic/unit-def.c
+++ b/src/basic/unit-def.c
@@ -180,27 +180,30 @@ static const char* const scope_state_table[_SCOPE_STATE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
static const char* const service_state_table[_SERVICE_STATE_MAX] = {
- [SERVICE_DEAD] = "dead",
- [SERVICE_CONDITION] = "condition",
- [SERVICE_START_PRE] = "start-pre",
- [SERVICE_START] = "start",
- [SERVICE_START_POST] = "start-post",
- [SERVICE_RUNNING] = "running",
- [SERVICE_EXITED] = "exited",
- [SERVICE_RELOAD] = "reload",
- [SERVICE_RELOAD_SIGNAL] = "reload-signal",
- [SERVICE_RELOAD_NOTIFY] = "reload-notify",
- [SERVICE_STOP] = "stop",
- [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
- [SERVICE_STOP_SIGTERM] = "stop-sigterm",
- [SERVICE_STOP_SIGKILL] = "stop-sigkill",
- [SERVICE_STOP_POST] = "stop-post",
- [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
- [SERVICE_FINAL_SIGTERM] = "final-sigterm",
- [SERVICE_FINAL_SIGKILL] = "final-sigkill",
- [SERVICE_FAILED] = "failed",
- [SERVICE_AUTO_RESTART] = "auto-restart",
- [SERVICE_CLEANING] = "cleaning",
+ [SERVICE_DEAD] = "dead",
+ [SERVICE_CONDITION] = "condition",
+ [SERVICE_START_PRE] = "start-pre",
+ [SERVICE_START] = "start",
+ [SERVICE_START_POST] = "start-post",
+ [SERVICE_RUNNING] = "running",
+ [SERVICE_EXITED] = "exited",
+ [SERVICE_RELOAD] = "reload",
+ [SERVICE_RELOAD_SIGNAL] = "reload-signal",
+ [SERVICE_RELOAD_NOTIFY] = "reload-notify",
+ [SERVICE_STOP] = "stop",
+ [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
+ [SERVICE_STOP_SIGTERM] = "stop-sigterm",
+ [SERVICE_STOP_SIGKILL] = "stop-sigkill",
+ [SERVICE_STOP_POST] = "stop-post",
+ [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
+ [SERVICE_FINAL_SIGTERM] = "final-sigterm",
+ [SERVICE_FINAL_SIGKILL] = "final-sigkill",
+ [SERVICE_FAILED] = "failed",
+ [SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart",
+ [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
+ [SERVICE_DEAD_RESOURCES_PINNED] = "dead-resources-pinned",
+ [SERVICE_AUTO_RESTART] = "auto-restart",
+ [SERVICE_CLEANING] = "cleaning",
};
DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
index bae132ea09..169e1f719e 100644
--- a/src/basic/unit-def.h
+++ b/src/basic/unit-def.h
@@ -144,6 +144,9 @@ typedef enum ServiceState {
SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
SERVICE_FINAL_SIGKILL,
SERVICE_FAILED,
+ SERVICE_DEAD_BEFORE_AUTO_RESTART,
+ SERVICE_FAILED_BEFORE_AUTO_RESTART,
+ SERVICE_DEAD_RESOURCES_PINNED, /* Like SERVICE_DEAD, but with pinned resources */
SERVICE_AUTO_RESTART,
SERVICE_CLEANING,
_SERVICE_STATE_MAX,
diff --git a/src/basic/unit-file.c b/src/basic/unit-file.c
index 1f79a2735e..41422579d6 100644
--- a/src/basic/unit-file.c
+++ b/src/basic/unit-file.c
@@ -2,7 +2,7 @@
#include "sd-id128.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fs-util.h"
@@ -71,10 +71,11 @@ int unit_symlink_name_compatible(const char *symlink, const char *target, bool i
}
int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, const char *target) {
- const char *src, *dst;
+ _cleanup_free_ char *src = NULL, *dst = NULL;
_cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
UnitType src_unit_type, dst_unit_type;
UnitNameFlags src_name_type, dst_name_type;
+ int r;
/* Check if the *alias* symlink is valid. This applies to symlinks like
* /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
@@ -87,8 +88,13 @@ int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, con
* -ELOOP for an alias to self.
*/
- src = basename(filename);
- dst = basename(target);
+ r = path_extract_filename(filename, &src);
+ if (r < 0)
+ return r;
+
+ r = path_extract_filename(target, &dst);
+ if (r < 0)
+ return r;
/* src checks */
@@ -322,7 +328,7 @@ int unit_file_resolve_symlink(
}
/* Get rid of "." and ".." components in target path */
- r = chase_symlinks(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
+ r = chase(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL);
if (r < 0)
return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m",
dir, filename, target);
@@ -432,7 +438,7 @@ int unit_file_build_name_map(
if (r < 0)
return log_oom();
- r = chase_symlinks(*dir, NULL, 0, &resolved_dir, NULL);
+ r = chase(*dir, NULL, 0, &resolved_dir, NULL);
if (r < 0) {
if (r != -ENOENT)
log_warning_errno(r, "Failed to resolve symlink %s, ignoring: %m", *dir);
@@ -762,7 +768,10 @@ int unit_file_find_fragment(
}
if (fragment && ret_names) {
- const char *fragment_basename = basename(fragment);
+ _cleanup_free_ char *fragment_basename = NULL;
+ r = path_extract_filename(fragment, &fragment_basename);
+ if (r < 0)
+ return r;
if (!streq(fragment_basename, unit_name)) {
/* Add names based on the fragment name to the set of names */
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index 0a47947d34..dd2642d214 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -13,7 +13,7 @@
#include "sd-messages.h"
#include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -23,7 +23,6 @@
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
-#include "path-util.h"
#include "random-util.h"
#include "string-util.h"
#include "strv.h"
@@ -156,21 +155,32 @@ bool is_nologin_shell(const char *shell) {
"/usr/bin/true");
}
-const char* default_root_shell(const char *root) {
+const char* default_root_shell_at(int rfd) {
/* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
* will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
* or any access errors. */
- int r = chase_symlinks(DEFAULT_USER_SHELL, root, CHASE_PREFIX_ROOT, NULL, NULL);
+ assert(rfd >= 0 || rfd == AT_FDCWD);
+
+ int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
if (r < 0 && r != -ENOENT)
- log_debug_errno(r, "Failed to look up shell '%s%s%s': %m",
- strempty(root), root ? "/" : "", DEFAULT_USER_SHELL);
+ log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
if (r > 0)
return DEFAULT_USER_SHELL;
return "/bin/sh";
}
+const char *default_root_shell(const char *root) {
+ _cleanup_close_ int rfd = -EBADF;
+
+ rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (rfd < 0)
+ return "/bin/sh";
+
+ return default_root_shell_at(rfd);
+}
+
static int synthesize_user_creds(
const char **username,
uid_t *uid, gid_t *gid,
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
index a08683bcea..5aca6307e0 100644
--- a/src/basic/user-util.h
+++ b/src/basic/user-util.h
@@ -80,7 +80,8 @@ int take_etc_passwd_lock(const char *root);
#define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1))
#define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1))
-#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
+#define ETC_PASSWD_LOCK_FILENAME ".pwd.lock"
+#define ETC_PASSWD_LOCK_PATH "/etc/" ETC_PASSWD_LOCK_FILENAME
/* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer
* NULL is special */
@@ -130,6 +131,7 @@ int putsgent_sane(const struct sgrp *sg, FILE *stream);
#endif
bool is_nologin_shell(const char *shell);
+const char* default_root_shell_at(int rfd);
const char* default_root_shell(const char *root);
int is_this_me(const char *username);
diff --git a/src/basic/virt.c b/src/basic/virt.c
index f264cc6eb3..aadc923bb5 100644
--- a/src/basic/virt.c
+++ b/src/basic/virt.c
@@ -264,6 +264,7 @@ static Virtualization detect_vm_dmi(void) {
* so we fallback to using the product name which is less restricted
* to distinguish metal systems from virtualized instances */
_cleanup_free_ char *s = NULL;
+ const char *e;
r = read_full_virtual_file("/sys/class/dmi/id/product_name", &s, NULL);
/* In EC2, virtualized is much more common than metal, so if for some reason
@@ -273,8 +274,9 @@ static Virtualization detect_vm_dmi(void) {
" assuming virtualized: %m");
return VIRTUALIZATION_AMAZON;
}
- if (endswith(truncate_nl(s), ".metal")) {
- log_debug("DMI product name ends with '.metal', assuming no virtualization");
+ e = strstrafter(truncate_nl(s), ".metal");
+ if (e && IN_SET(*e, 0, '-')) {
+ log_debug("DMI product name has '.metal', assuming no virtualization");
return VIRTUALIZATION_NONE;
} else
return VIRTUALIZATION_AMAZON;