diff options
Diffstat (limited to 'src/basic')
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(©_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; |