diff options
Diffstat (limited to 'src')
270 files changed, 6227 insertions, 3341 deletions
diff --git a/src/analyze/analyze-time-data.h b/src/analyze/analyze-time-data.h index e7ffd8549d..fd228b5929 100644 --- a/src/analyze/analyze-time-data.h +++ b/src/analyze/analyze-time-data.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include <sd-bus.h> +#include "sd-bus.h" #include "time-util.h" #include "unit-def.h" diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 551dcb8b27..55ff91d76c 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -376,7 +376,7 @@ static int vl_method_ask(sd_varlink *link, sd_json_variant *parameters, sd_varli static int vl_server(void) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; - _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; int r; r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_INHERIT_USERDATA, /* userdata= */ &polkit_registry); diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 89c6cbec41..e4104c5da5 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -28,7 +28,7 @@ static inline bool capability_is_set(uint64_t v) { unsigned cap_last_cap(void); int have_effective_cap(int value); -int capability_gain_cap_setpcap(cap_t *return_caps); +int capability_gain_cap_setpcap(cap_t *ret_before_caps); int capability_bounding_set_drop(uint64_t keep, bool right_now); int capability_bounding_set_drop_usermode(uint64_t keep); @@ -43,12 +43,6 @@ int keep_capability(cap_value_t cv); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(cap_t, cap_free, NULL); #define _cleanup_cap_free_ _cleanup_(cap_freep) -static inline void cap_free_charpp(char **p) { - if (*p) - cap_free(*p); -} -#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) - static inline uint64_t all_capabilities(void) { return UINT64_MAX >> (63 - cap_last_cap()); } diff --git a/src/basic/chase.c b/src/basic/chase.c index 43fad0d93f..5d064921b5 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -89,9 +89,8 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); + assert(!FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR)); 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 */ @@ -244,8 +243,15 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (root_fd < 0) return -errno; - if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) - append_trail_slash = ENDSWITH_SET(buffer, "/", "/."); + if (ENDSWITH_SET(buffer, "/", "/.")) { + flags |= CHASE_MUST_BE_DIRECTORY; + if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) + append_trail_slash = true; + } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/..")) + flags |= CHASE_MUST_BE_DIRECTORY; + + if (FLAGS_SET(flags, CHASE_PARENT)) + flags |= CHASE_MUST_BE_DIRECTORY; for (todo = buffer;;) { _cleanup_free_ char *first = NULL; @@ -256,19 +262,15 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); if (r < 0) return r; - if (r == 0) { /* We reached the end. */ - if (append_trail_slash) - if (!strextend(&done, "/")) - return -ENOMEM; + if (r == 0) /* We reached the end. */ break; - } first = strndup(e, r); if (!first) return -ENOMEM; /* Two dots? Then chop off the last bit of what we already found out. */ - if (path_equal(first, "..")) { + if (streq(first, "..")) { _cleanup_free_ char *parent = NULL; _cleanup_close_ int fd_parent = -EBADF; struct stat st_parent; @@ -370,13 +372,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (r != -ENOENT) return r; - if (!isempty(todo) && !path_is_safe(todo)) + if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */ return r; - if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) { + if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) { child = xopenat_full(fd, first, - O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, + O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC, /* xopen_flags = */ 0, 0755); if (child < 0) @@ -477,12 +479,18 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int close_and_replace(fd, child); } - if (FLAGS_SET(flags, CHASE_PARENT)) { + if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { r = stat_verify_directory(&st); if (r < 0) return r; } + if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) { + r = stat_verify_regular(&st); + if (r < 0) + return r; + } + if (ret_path) { if (FLAGS_SET(flags, CHASE_EXTRACT_FILENAME) && done) { _cleanup_free_ char *f = NULL; @@ -497,11 +505,15 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (!done) { assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)); - done = strdup(append_trail_slash ? "./" : "."); + done = strdup("."); if (!done) return -ENOMEM; } + if (append_trail_slash) + if (!strextend(&done, "/")) + return -ENOMEM; + *ret_path = TAKE_PTR(done); } diff --git a/src/basic/chase.h b/src/basic/chase.h index cfc714b9f7..eda7cad0b7 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -27,12 +27,10 @@ typedef enum ChaseFlags { * 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_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */ CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ + CHASE_MUST_BE_DIRECTORY = 1 << 13, /* Fail if returned inode fd is not a dir */ + CHASE_MUST_BE_REGULAR = 1 << 14, /* Fail if returned inode fd is not a regular file */ } ChaseFlags; bool unsafe_transition(const struct stat *a, const struct stat *b); diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h index e109de9913..0efca56780 100644 --- a/src/basic/devnum-util.h +++ b/src/basic/devnum-util.h @@ -9,6 +9,9 @@ int parse_devnum(const char *s, dev_t *ret); +#define DEVNUM_MAJOR_MAX ((UINT32_C(1) << 12) - 1U) +#define DEVNUM_MINOR_MAX ((UINT32_C(1) << 20) - 1U) + /* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the * specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of * major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of @@ -18,14 +21,14 @@ int parse_devnum(const char *s, dev_t *ret); #define DEVICE_MAJOR_VALID(x) \ ({ \ typeof(x) _x = (x), _y = 0; \ - _x >= _y && _x < (UINT32_C(1) << 12); \ + _x >= _y && _x <= DEVNUM_MAJOR_MAX; \ \ }) #define DEVICE_MINOR_VALID(x) \ ({ \ typeof(x) _x = (x), _y = 0; \ - _x >= _y && _x < (UINT32_C(1) << 20); \ + _x >= _y && _x <= DEVNUM_MINOR_MAX; \ }) int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret); @@ -54,3 +57,6 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) { static inline bool devnum_is_zero(dev_t d) { return major(d) == 0 && minor(d) == 0; } + +#define DEVNUM_TO_PTR(u) ((void*) (uintptr_t) (u)) +#define PTR_TO_DEVNUM(p) ((dev_t) ((uintptr_t) (p))) diff --git a/src/basic/escape.c b/src/basic/escape.c index 2067be4092..e50ae68cc6 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -365,6 +365,8 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape char *ans, *t, *prev, *prev2; const char *f; + assert(s); + /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be * reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through * unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings. @@ -397,7 +399,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape if ((unsigned char) *f < ' ' || (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) || - *f == '\\' || strchr(bad, *f)) { + *f == '\\' || (bad && strchr(bad, *f))) { if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width) break; @@ -437,7 +439,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags) { if (FLAGS_SET(flags, XESCAPE_8_BIT)) - return xescape_full(str, "", console_width, flags); + return xescape_full(str, /* bad= */ NULL, console_width, flags); else return utf8_escape_non_printable_full(str, console_width, diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index c112f8dbad..be22d6a04f 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1113,8 +1113,6 @@ int fds_are_same_mount(int fd1, int fd2) { int mntid; r = path_get_mnt_id_at_fallback(fd1, "", &mntid); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); @@ -1127,8 +1125,6 @@ int fds_are_same_mount(int fd1, int fd2) { int mntid; r = path_get_mnt_id_at_fallback(fd2, "", &mntid); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 9f2bbc1323..25c4518261 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -298,19 +298,22 @@ int write_string_file_full( _cleanup_close_ int fd = -EBADF; int r; - assert(fn); + assert(dir_fd == AT_FDCWD || dir_fd >= 0); assert(line); /* We don't know how to verify whether the file contents was already on-disk. */ assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC))); if (flags & WRITE_STRING_FILE_MKDIR_0755) { + assert(fn); + r = mkdirat_parents(dir_fd, fn, 0755); if (r < 0) return r; } if (flags & WRITE_STRING_FILE_ATOMIC) { + assert(fn); assert(flags & WRITE_STRING_FILE_CREATE); r = write_string_file_atomic_at(dir_fd, fn, line, flags, ts); @@ -320,25 +323,31 @@ int write_string_file_full( return r; } - mode_t mode = write_string_file_flags_to_mode(flags); + /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ + if (isempty(fn)) + fd = fd_reopen(ASSERT_FD(dir_fd), O_CLOEXEC | O_NOCTTY | + (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY)); + else { + mode_t mode = write_string_file_flags_to_mode(flags); - if (FLAGS_SET(flags, WRITE_STRING_FILE_LABEL|WRITE_STRING_FILE_CREATE)) { - r = label_ops_pre(dir_fd, label_fn ?: fn, mode); - if (r < 0) - goto fail; + if (FLAGS_SET(flags, WRITE_STRING_FILE_LABEL|WRITE_STRING_FILE_CREATE)) { + r = label_ops_pre(dir_fd, label_fn ?: fn, mode); + if (r < 0) + goto fail; - call_label_ops_post = true; - } + call_label_ops_post = true; + } - /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ - fd = openat_report_new( - dir_fd, fn, O_CLOEXEC | O_NOCTTY | - (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | - (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) | - (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | - (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), - mode, - &made_file); + fd = openat_report_new( + dir_fd, fn, O_CLOEXEC | O_NOCTTY | + (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), + mode, + &made_file); + } if (fd < 0) { r = fd; goto fail; @@ -380,7 +389,7 @@ fail: /* OK, the operation failed, but let's see if the right contents in place already. If so, eat up the * error. */ - if (verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE)) > 0) + if (verify_file_at(dir_fd, fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE)) > 0) return 0; return r; @@ -442,7 +451,6 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext size_t l, k; int r; - assert(fn); assert(blob); l = strlen(blob); @@ -454,7 +462,7 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext if (!buf) return -ENOMEM; - r = fopen_unlocked_at(dir_fd, fn, "re", 0, &f); + r = fopen_unlocked_at(dir_fd, strempty(fn), "re", 0, &f); if (r < 0) return r; @@ -474,7 +482,13 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext return 1; } -int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size) { +int read_virtual_file_at( + int dir_fd, + const char *filename, + size_t max_size, + char **ret_contents, + size_t *ret_size) { + _cleanup_free_ char *buf = NULL; size_t n, size; int n_retries; @@ -493,9 +507,17 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r * contents* may be returned. (Though the read is still done using one syscall.) Returns 0 on * partial success, 1 if untruncated contents were read. */ - assert(fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(max_size <= READ_VIRTUAL_BYTES_MAX || max_size == SIZE_MAX); + _cleanup_close_ int fd = -EBADF; + if (isempty(filename)) + fd = fd_reopen(ASSERT_FD(dir_fd), O_RDONLY | O_NOCTTY | O_CLOEXEC); + else + fd = RET_NERRNO(openat(dir_fd, filename, O_RDONLY | O_NOCTTY | O_CLOEXEC)); + if (fd < 0) + return fd; + /* Limit the number of attempts to read the number of bytes returned by fstat(). */ n_retries = 3; @@ -619,31 +641,6 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r return !truncated; } -int read_virtual_file_at( - int dir_fd, - const char *filename, - size_t max_size, - char **ret_contents, - size_t *ret_size) { - - _cleanup_close_ int fd = -EBADF; - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - - if (!filename) { - if (dir_fd == AT_FDCWD) - return -EBADF; - - return read_virtual_file_fd(dir_fd, max_size, ret_contents, ret_size); - } - - fd = openat(dir_fd, filename, O_RDONLY | O_NOCTTY | O_CLOEXEC); - if (fd < 0) - return -errno; - - return read_virtual_file_fd(fd, max_size, ret_contents, ret_size); -} - int read_full_stream_full( FILE *f, const char *filename, @@ -723,7 +720,7 @@ int read_full_stream_full( size_t k; /* If we shall fail when reading overly large data, then read exactly one byte more than the - * specified size at max, since that'll tell us if there's anymore data beyond the limit*/ + * specified size at max, since that'll tell us if there's anymore data beyond the limit. */ if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size) n_next = size + 1; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index bd053050e1..49da9a677c 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -56,6 +56,9 @@ int write_string_file_full(int dir_fd, const char *fn, const char *line, WriteSt static inline int write_string_file_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags) { return write_string_file_full(dir_fd, fn, line, flags, NULL, NULL); } +static inline int write_string_file_fd(int dir_fd, const char *line, WriteStringFileFlags flags) { + return write_string_file_at(dir_fd, NULL, line, flags); +} static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { return write_string_file_at(AT_FDCWD, fn, line, flags); } @@ -75,8 +78,10 @@ static inline int read_full_file(const char *filename, char **ret_contents, size return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); } -int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size); int read_virtual_file_at(int dir_fd, const char *filename, size_t max_size, char **ret_contents, size_t *ret_size); +static inline int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size) { + return read_virtual_file_at(fd, NULL, max_size, ret_contents, ret_size); +} static inline int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size) { return read_virtual_file_at(AT_FDCWD, filename, max_size, ret_contents, ret_size); } @@ -90,9 +95,6 @@ static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_siz } int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl); -static inline int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { - return verify_file_at(AT_FDCWD, fn, blob, accept_extra_nl); -} int script_get_shebang_interpreter(const char *path, char **ret); diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 5ee7417eeb..b2c0107990 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -163,3 +163,21 @@ static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, Loc int link_fd(int fd, int newdirfd, const char *newpath); int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); + +static inline int at_flags_normalize_nofollow(int flags) { + if (FLAGS_SET(flags, AT_SYMLINK_FOLLOW)) { + assert(!FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW)); + flags &= ~AT_SYMLINK_FOLLOW; + } else + flags |= AT_SYMLINK_NOFOLLOW; + return flags; +} + +static inline int at_flags_normalize_follow(int flags) { + if (FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW)) { + assert(!FLAGS_SET(flags, AT_SYMLINK_FOLLOW)); + flags &= ~AT_SYMLINK_NOFOLLOW; + } else + flags |= AT_SYMLINK_FOLLOW; + return flags; +} diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c index 251ee4f069..b122c300b8 100644 --- a/src/basic/hash-funcs.c +++ b/src/basic/hash-funcs.c @@ -10,15 +10,23 @@ void string_hash_func(const char *p, struct siphash *state) { siphash24_compress(p, strlen(p) + 1, state); } -DEFINE_HASH_OPS(string_hash_ops, char, string_hash_func, string_compare_func); -DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(string_hash_ops_free, - char, string_hash_func, string_compare_func, free); -DEFINE_HASH_OPS_FULL(string_hash_ops_free_free, - char, string_hash_func, string_compare_func, free, - void, free); -DEFINE_HASH_OPS_FULL(string_hash_ops_free_strv_free, - char, string_hash_func, string_compare_func, free, - char*, strv_free); +DEFINE_HASH_OPS(string_hash_ops, + char, string_hash_func, string_compare_func); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + string_hash_ops_free, + char, string_hash_func, string_compare_func, free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + string_hash_ops_value_free, + char, string_hash_func, string_compare_func, + void, free); +DEFINE_HASH_OPS_FULL( + string_hash_ops_free_free, + char, string_hash_func, string_compare_func, free, + void, free); +DEFINE_HASH_OPS_FULL( + string_hash_ops_free_strv_free, + char, string_hash_func, string_compare_func, free, + char*, strv_free); void path_hash_func(const char *q, struct siphash *state) { bool add_slash = false; @@ -59,12 +67,15 @@ void path_hash_func(const char *q, struct siphash *state) { } } -DEFINE_HASH_OPS(path_hash_ops, char, path_hash_func, path_compare); -DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(path_hash_ops_free, - char, path_hash_func, path_compare, free); -DEFINE_HASH_OPS_FULL(path_hash_ops_free_free, - char, path_hash_func, path_compare, free, - void, free); +DEFINE_HASH_OPS(path_hash_ops, + char, path_hash_func, path_compare); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + path_hash_ops_free, + char, path_hash_func, path_compare, free); +DEFINE_HASH_OPS_FULL( + path_hash_ops_free_free, + char, path_hash_func, path_compare, free, + void, free); void trivial_hash_func(const void *p, struct siphash *state) { siphash24_compress_typesafe(p, state); @@ -74,23 +85,19 @@ int trivial_compare_func(const void *a, const void *b) { return CMP(a, b); } -const struct hash_ops trivial_hash_ops = { - .hash = trivial_hash_func, - .compare = trivial_compare_func, -}; - -const struct hash_ops trivial_hash_ops_free = { - .hash = trivial_hash_func, - .compare = trivial_compare_func, - .free_key = free, -}; - -const struct hash_ops trivial_hash_ops_free_free = { - .hash = trivial_hash_func, - .compare = trivial_compare_func, - .free_key = free, - .free_value = free, -}; +DEFINE_HASH_OPS(trivial_hash_ops, + void, trivial_hash_func, trivial_compare_func); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + trivial_hash_ops_free, + void, trivial_hash_func, trivial_compare_func, free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + trivial_hash_ops_value_free, + void, trivial_hash_func, trivial_compare_func, + void, free); +DEFINE_HASH_OPS_FULL( + trivial_hash_ops_free_free, + void, trivial_hash_func, trivial_compare_func, free, + void, free); void uint64_hash_func(const uint64_t *p, struct siphash *state) { siphash24_compress_typesafe(*p, state); @@ -100,7 +107,12 @@ int uint64_compare_func(const uint64_t *a, const uint64_t *b) { return CMP(*a, *b); } -DEFINE_HASH_OPS(uint64_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func); +DEFINE_HASH_OPS(uint64_hash_ops, + uint64_t, uint64_hash_func, uint64_compare_func); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + uint64_hash_ops_value_free, + uint64_t, uint64_hash_func, uint64_compare_func, + void, free); #if SIZEOF_DEV_T != 8 void devt_hash_func(const dev_t *p, struct siphash *state) { diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h index 3804e94d98..d0736807ba 100644 --- a/src/basic/hash-funcs.h +++ b/src/basic/hash-funcs.h @@ -77,6 +77,7 @@ void string_hash_func(const char *p, struct siphash *state); #define string_compare_func strcmp extern const struct hash_ops string_hash_ops; extern const struct hash_ops string_hash_ops_free; +extern const struct hash_ops string_hash_ops_value_free; extern const struct hash_ops string_hash_ops_free_free; extern const struct hash_ops string_hash_ops_free_strv_free; @@ -91,6 +92,7 @@ void trivial_hash_func(const void *p, struct siphash *state); int trivial_compare_func(const void *a, const void *b) _const_; extern const struct hash_ops trivial_hash_ops; extern const struct hash_ops trivial_hash_ops_free; +extern const struct hash_ops trivial_hash_ops_value_free; extern const struct hash_ops trivial_hash_ops_free_free; /* 32-bit values we can always just embed in the pointer itself, but in order to support 32-bit archs we need store 64-bit @@ -98,6 +100,7 @@ extern const struct hash_ops trivial_hash_ops_free_free; void uint64_hash_func(const uint64_t *p, struct siphash *state); int uint64_compare_func(const uint64_t *a, const uint64_t *b) _pure_; extern const struct hash_ops uint64_hash_ops; +extern const struct hash_ops uint64_hash_ops_value_free; /* On some archs dev_t is 32-bit, and on others 64-bit. And sometimes it's 64-bit on 32-bit archs, and sometimes 32-bit on * 64-bit archs. Yuck! */ diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h index 01a4fb3204..091062b5e9 100644 --- a/src/basic/hashmap.h +++ b/src/basic/hashmap.h @@ -99,27 +99,6 @@ static inline OrderedHashmap* ordered_hashmap_free(OrderedHashmap *h) { return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, NULL); } -static inline Hashmap* hashmap_free_free(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, free); -} -static inline OrderedHashmap* ordered_hashmap_free_free(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, free); -} - -static inline Hashmap* hashmap_free_free_key(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, NULL); -} -static inline OrderedHashmap* ordered_hashmap_free_free_key(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, NULL); -} - -static inline Hashmap* hashmap_free_free_free(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, free); -} -static inline OrderedHashmap* ordered_hashmap_free_free_free(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, free); -} - IteratedCache* iterated_cache_free(IteratedCache *cache); int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries); @@ -293,27 +272,6 @@ static inline void ordered_hashmap_clear(OrderedHashmap *h) { _hashmap_clear(HASHMAP_BASE(h), NULL, NULL); } -static inline void hashmap_clear_free(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), NULL, free); -} -static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), NULL, free); -} - -static inline void hashmap_clear_free_key(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, NULL); -} -static inline void ordered_hashmap_clear_free_key(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, NULL); -} - -static inline void hashmap_clear_free_free(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, free); -} -static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, free); -} - /* * Note about all *_first*() functions * @@ -459,20 +417,10 @@ static inline int ordered_hashmap_dump_keys_sorted(OrderedHashmap *h, void ***re _ORDERED_HASHMAP_FOREACH_KEY(e, k, h, UNIQ_T(i, UNIQ)) DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_key); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_key); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); #define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) -#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) -#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) #define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) -#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) -#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free); diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index 79e4959be7..1d8e60330c 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -866,6 +866,9 @@ void hexdump(FILE *f, const void *p, size_t s) { assert(b || s == 0); + if (s == SIZE_MAX) + s = strlen(p); + if (!f) f = stdout; diff --git a/src/basic/log.c b/src/basic/log.c index cbc6edc95d..2250dfe927 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -397,7 +397,7 @@ void log_forget_fds(void) { } int log_set_max_level(int level) { - assert(level == LOG_NULL || LOG_PRI(level) == level); + assert(level == LOG_NULL || log_level_is_valid(level)); int old = log_max_level; log_max_level = level; diff --git a/src/basic/log.h b/src/basic/log.h index 3859a6c41d..02f3f509d1 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -34,6 +34,7 @@ typedef enum LogTarget{ /* This log level disables logging completely. It can only be passed to log_set_max_level() and cannot be * used as a regular log level. */ #define LOG_NULL (LOG_EMERG - 1) +assert_cc(LOG_NULL == -1); #define SYNTHETIC_ERRNO(num) (abs(num) | (1 << 30)) #define IS_SYNTHETIC_ERRNO(val) (((val) >> 30) == 1) diff --git a/src/basic/missing_threads.h b/src/basic/missing_threads.h index d48e05d586..c7da1dbd5e 100644 --- a/src/basic/missing_threads.h +++ b/src/basic/missing_threads.h @@ -5,7 +5,7 @@ #if HAVE_THREADS_H # include <threads.h> #elif !(defined(thread_local)) -# if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__)) +# ifndef __STDC_NO_THREADS__ # define thread_local _Thread_local # else # define thread_local __thread diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 15655cc075..aac8b19430 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -37,10 +37,10 @@ #define ORIGINAL_MAX_HANDLE_SZ 128 bool is_name_to_handle_at_fatal_error(int err) { - /* name_to_handle_at() can return "acceptable" errors that are due to the context. For example the - * kernel does not support name_to_handle_at() at all (ENOSYS), or the syscall was blocked - * (EACCES/EPERM; maybe through seccomp, because we are running inside of a container), or the mount - * point is not triggered yet (EOVERFLOW, think autofs+nfs4), or some general name_to_handle_at() + /* name_to_handle_at() can return "acceptable" errors that are due to the context. For example + * the file system does not support name_to_handle_at() (EOPNOTSUPP), or the syscall was blocked + * (EACCES/EPERM; maybe through seccomp, because we are running inside of a container), or + * the mount point is not triggered yet (EOVERFLOW, think autofs+nfs4), or some general name_to_handle_at() * flakiness (EINVAL). However other errors are not supposed to happen and therefore are considered * fatal ones. */ @@ -137,9 +137,7 @@ int name_to_handle_at_try_fid( * (i.e. older than Linux 6.5). */ r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags | AT_HANDLE_FID); - if (r >= 0) - return r; - if (is_name_to_handle_at_fatal_error(r)) + if (r >= 0 || is_name_to_handle_at_fatal_error(r)) return r; return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags & ~AT_HANDLE_FID); @@ -149,11 +147,10 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mn char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; _cleanup_free_ char *fdinfo = NULL; _cleanup_close_ int subfd = -EBADF; - char *p; int r; - assert(ret_mnt_id); assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); + assert(ret_mnt_id); if ((flags & AT_EMPTY_PATH) && isempty(filename)) xsprintf(path, "/proc/self/fdinfo/%i", fd); @@ -166,16 +163,16 @@ 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 proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS; + if (r == -ENOENT) + return proc_fd_enoent_errno(); if (r < 0) return r; - p = find_line_startswith(fdinfo, "mnt_id:"); - if (!p) /* The mnt_id field is a relatively new addition */ - return -EOPNOTSUPP; + char *p = find_line_startswith(fdinfo, "mnt_id:"); + if (!p) + return -EBADMSG; - p += strspn(p, WHITESPACE); + p = skip_leading_chars(p, /* bad = */ NULL); p[strcspn(p, WHITESPACE)] = 0; return safe_atoi(p, ret_mnt_id); @@ -215,28 +212,36 @@ bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) return memcmp_nn(a->f_handle, a->handle_bytes, b->f_handle, b->handle_bytes) == 0; } -int fd_is_mount_point(int fd, const char *filename, int flags) { - _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL; - int mount_id = -1, mount_id_parent = -1; - bool nosupp = false, check_st_dev = true; - STRUCT_STATX_DEFINE(sx); - struct stat a, b; +int is_mount_point_at(int fd, const char *filename, int flags) { + bool fd_is_self; int r; - assert(fd >= 0); + assert(fd >= 0 || fd == AT_FDCWD); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); - if (!filename) { - /* If the file name is specified as NULL we'll see if the specified 'fd' is a mount - * point. That's only supported if the kernel supports statx(), or if the inode specified via - * 'fd' refers to a directory. Otherwise, we'll have to fail (ENOTDIR), because we have no - * kernel API to query the information we need. */ - flags |= AT_EMPTY_PATH; - filename = ""; - } else if (!filename_possibly_with_slash_suffix(filename)) - /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode further - * up or down the tree then immediately below the specified directory fd. */ - return -EINVAL; + if (isempty(filename)) { + if (fd == AT_FDCWD) + filename = "."; + else { + /* If the file name is empty we'll see if the specified 'fd' is a mount point. + * That's only supported if the kernel supports statx(), or if the inode specified + * via 'fd' refers to a directory. Otherwise, we'll have to fail (ENOTDIR), because + * we have no kernel API to query the information we need. */ + flags |= AT_EMPTY_PATH; + filename = ""; + } + + fd_is_self = true; + } else if (STR_IN_SET(filename, ".", "./")) + fd_is_self = true; + else { + /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode + * further up or down the tree then immediately below the specified directory fd. */ + if (!filename_possibly_with_slash_suffix(filename)) + return -EINVAL; + + fd_is_self = false; + } /* First we will try statx()' STATX_ATTR_MOUNT_ROOT attribute, which is our ideal API, available * since kernel 5.8. @@ -249,18 +254,17 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { * If that didn't work we will try to read the mount id from /proc/self/fdinfo/<fd>. This is almost * as good as name_to_handle_at(), however, does not return the opaque file handle. The opaque file * handle is pretty useful to detect the root directory, which we should always consider a mount - * point. Hence we use this only as fallback. Exporting the mnt_id in fdinfo is a pretty recent - * kernel addition. + * point. Hence we use this only as fallback. * - * As last fallback we do traditional fstat() based st_dev comparisons. This is how things were - * traditionally done, but unionfs breaks this since it exposes file systems with a variety of st_dev - * 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) | + * Note that traditionally the check is done via fstat()-based st_dev comparisons. However, various + * file systems don't guarantee same st_dev across single fs anymore, e.g. unionfs exposes file systems + * with a variety of st_dev reported. Also, btrfs subvolumes have different st_dev, even though + * they aren't real mounts of their own. */ + + STRUCT_STATX_DEFINE(sx); + + if (statx(fd, filename, + at_flags_normalize_nofollow(flags) | 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, @@ -274,6 +278,10 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { } else if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) /* yay! */ return FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); + _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL; + int mount_id = -1, mount_id_parent = -1; + bool nosupp = false; + r = name_to_handle_at_try_fid(fd, filename, &h, &mount_id, flags); if (r < 0) { if (is_name_to_handle_at_fatal_error(r)) @@ -281,13 +289,12 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { if (!ERRNO_IS_NOT_SUPPORTED(r)) goto fallback_fdinfo; - /* This kernel or file system does not support name_to_handle_at(), hence let's see - * if the upper fs supports it (in which case it is a mount point), otherwise fall - * back to the traditional stat() logic */ + /* This file system does not support name_to_handle_at(), hence let's see if the upper fs + * supports it (in which case it is a mount point), otherwise fall back to the fdinfo logic. */ nosupp = true; } - if (isempty(filename)) + if (fd_is_self) r = name_to_handle_at_try_fid(fd, "..", &h_parent, &mount_id_parent, 0); /* can't work for non-directories 😢 */ else r = name_to_handle_at_try_fid(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH); @@ -319,12 +326,10 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { fallback_fdinfo: r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || ERRNO_IS_NEG_PRIVILEGE(r)) - goto fallback_fstat; if (r < 0) return r; - if (isempty(filename)) + if (fd_is_self) r = fd_fdinfo_mnt_id(fd, "..", 0, &mount_id_parent); /* can't work for non-directories 😢 */ else r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); @@ -336,38 +341,33 @@ fallback_fdinfo: /* Hmm, so, the mount ids are the same. This leaves one special case though for the root file * system. For that, let's see if the parent directory has the same inode as we are interested - * in. Hence, let's also do fstat() checks now, too, but avoid the st_dev comparisons, since they - * aren't that useful on unionfs mounts. */ - check_st_dev = false; + * in. */ + + struct stat a, b; -fallback_fstat: /* yay for fstatat() taking a different set of flags than the other _at() above */ - if (flags & AT_SYMLINK_FOLLOW) - flags &= ~AT_SYMLINK_FOLLOW; - else - flags |= AT_SYMLINK_NOFOLLOW; - if (fstatat(fd, filename, &a, flags) < 0) + if (fstatat(fd, filename, &a, at_flags_normalize_nofollow(flags)) < 0) return -errno; - if (isempty(filename)) + if (fd_is_self) r = fstatat(fd, "..", &b, 0); else r = fstatat(fd, "", &b, AT_EMPTY_PATH); if (r < 0) return -errno; - /* A directory with same device and inode as its parent? Must be the root directory */ - if (stat_inode_same(&a, &b)) - return 1; - - return check_st_dev && (a.st_dev != b.st_dev); + /* A directory with same device and inode as its parent must be the root directory. Otherwise + * not a mount point. + * + * NB: we avoid inode_same_at() here because it internally attempts name_to_handle_at_try_fid() first, + * which is redundant. */ + return stat_inode_same(&a, &b); } /* flags can be AT_SYMLINK_FOLLOW or 0 */ int path_is_mount_point_full(const char *path, const char *root, int flags) { - _cleanup_free_ char *canonical = NULL; - _cleanup_close_ int fd = -EBADF; - int r; + _cleanup_close_ int dfd = -EBADF; + _cleanup_free_ char *fn = NULL; assert(path); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); @@ -375,22 +375,16 @@ int path_is_mount_point_full(const char *path, const char *root, int flags) { if (path_equal(path, "/")) return 1; - /* we need to resolve symlinks manually, we can't just rely on fd_is_mount_point() to do that for us; + /* we need to resolve symlinks manually, we can't just rely on is_mount_point_at() to do that for us; * if we have a structure like /bin -> /usr/bin/ and /usr is a mount point, then the parent that we * look at needs to be /usr, not /. */ - if (FLAGS_SET(flags, AT_SYMLINK_FOLLOW)) { - r = chase(path, root, CHASE_TRAIL_SLASH, &canonical, NULL); - if (r < 0) - return r; - - path = canonical; - } - - fd = open_parent(path, O_PATH|O_CLOEXEC, 0); - if (fd < 0) - return fd; + dfd = chase_and_open_parent(path, root, + CHASE_TRAIL_SLASH|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : CHASE_NOFOLLOW), + &fn); + if (dfd < 0) + return dfd; - return fd_is_mount_point(fd, last_path_component(path), flags); + return is_mount_point_at(dfd, fn, flags); } int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret) { @@ -400,7 +394,7 @@ int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret) { assert(ret); r = name_to_handle_at_loop(dir_fd, path, NULL, ret, isempty(path) ? AT_EMPTY_PATH : 0); - if (r == 0 || is_name_to_handle_at_fatal_error(r)) + if (r >= 0 || is_name_to_handle_at_fatal_error(r)) return r; return fd_fdinfo_mnt_id(dir_fd, path, isempty(path) ? AT_EMPTY_PATH : 0, ret); diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index f506e01a41..43e4758143 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -49,7 +49,7 @@ static inline int path_get_mnt_id(const char *path, int *ret) { return path_get_mnt_id_at(AT_FDCWD, path, ret); } -int fd_is_mount_point(int fd, const char *filename, int flags); +int is_mount_point_at(int fd, const char *filename, int flags); int path_is_mount_point_full(const char *path, const char *root, int flags); static inline int path_is_mount_point(const char *path) { return path_is_mount_point_full(path, NULL, 0); diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 6c559e4bf8..99cece1674 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -558,7 +558,7 @@ int userns_acquire(const char *uid_map, const char *gid_map) { } int userns_enter_and_pin(int userns_fd, pid_t *ret_pid) { - _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; + _cleanup_close_pair_ int pfd[2] = EBADF_PAIR; _cleanup_(sigkill_waitp) pid_t pid = 0; ssize_t n; char x; diff --git a/src/basic/ordered-set.h b/src/basic/ordered-set.h index e73da20573..5f2b45309f 100644 --- a/src/basic/ordered-set.h +++ b/src/basic/ordered-set.h @@ -22,18 +22,10 @@ static inline void ordered_set_clear(OrderedSet *s) { return ordered_hashmap_clear((OrderedHashmap*) s); } -static inline void ordered_set_clear_free(OrderedSet *s) { - return ordered_hashmap_clear_free((OrderedHashmap*) s); -} - static inline OrderedSet* ordered_set_free(OrderedSet *s) { return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s); } -static inline OrderedSet* ordered_set_free_free(OrderedSet *s) { - return (OrderedSet*) ordered_hashmap_free_free((OrderedHashmap*) s); -} - static inline int ordered_set_contains(OrderedSet *s, const void *p) { return ordered_hashmap_contains((OrderedHashmap*) s, p); } @@ -103,7 +95,5 @@ void ordered_set_print(FILE *f, const char *field, OrderedSet *s); ordered_set_free(ordered_set_clear_with_destructor(s, f)) DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); #define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) -#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) diff --git a/src/basic/pidfd-util.c b/src/basic/pidfd-util.c index c90699d066..82064e162a 100644 --- a/src/basic/pidfd-util.c +++ b/src/basic/pidfd-util.c @@ -8,7 +8,10 @@ #include "fileio.h" #include "macro.h" #include "memory-util.h" +#include "missing_fs.h" #include "missing_magic.h" +#include "missing_threads.h" +#include "mountpoint-util.h" #include "parse-util.h" #include "path-util.h" #include "pidfd-util.h" @@ -18,16 +21,14 @@ static int have_pidfs = -1; -static int pidfd_check_pidfs(void) { +static int pidfd_check_pidfs(int pid_fd) { + + /* NB: the passed fd *must* be acquired via pidfd_open(), i.e. must be a true pidfd! */ if (have_pidfs >= 0) return have_pidfs; - _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0); - if (fd < 0) - return -errno; - - return (have_pidfs = fd_is_fs_type(fd, PID_FS_MAGIC)); + return (have_pidfs = fd_is_fs_type(pid_fd, PID_FS_MAGIC)); } int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) { @@ -227,16 +228,41 @@ int pidfd_get_cgroupid(int fd, uint64_t *ret) { } int pidfd_get_inode_id(int fd, uint64_t *ret) { + static bool file_handle_supported = true; int r; assert(fd >= 0); - r = pidfd_check_pidfs(); + r = pidfd_check_pidfs(fd); if (r < 0) return r; if (r == 0) return -EOPNOTSUPP; + if (file_handle_supported) { + union { + struct file_handle file_handle; + uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)]; + } fh = { + .file_handle.handle_bytes = sizeof(uint64_t), + .file_handle.handle_type = FILEID_KERNFS, + }; + int mnt_id; + + r = RET_NERRNO(name_to_handle_at(fd, "", &fh.file_handle, &mnt_id, AT_EMPTY_PATH)); + if (r >= 0) { + if (ret) + *ret = *(uint64_t*) fh.file_handle.f_handle; + return 0; + } + assert(r != -EOVERFLOW); + if (is_name_to_handle_at_fatal_error(r)) + return r; + + file_handle_supported = false; + } + +#if SIZEOF_INO_T == 8 struct stat st; if (fstat(fd, &st) < 0) return -errno; @@ -244,4 +270,43 @@ int pidfd_get_inode_id(int fd, uint64_t *ret) { if (ret) *ret = (uint64_t) st.st_ino; return 0; + +#elif SIZEOF_INO_T == 4 + /* On 32-bit systems (where sizeof(ino_t) == 4), the inode id returned by fstat() cannot be used to + * reliably identify the process, nor can we communicate the origin of the id with the clients. + * Hence let's just refuse to acquire pidfdid through fstat() here. All clients shall also insist on + * the 64-bit id from name_to_handle_at(). */ + return -EOPNOTSUPP; +#else +# error Unsupported ino_t size +#endif +} + +int pidfd_get_inode_id_self_cached(uint64_t *ret) { + static thread_local uint64_t cached = 0; + static thread_local pid_t initialized = 0; /* < 0: cached error; == 0: invalid; > 0: valid and pid that was current */ + int r; + + assert(ret); + + if (initialized == getpid_cached()) { + *ret = cached; + return 0; + } + if (initialized < 0) + return initialized; + + _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0); + if (fd < 0) + return -errno; + + r = pidfd_get_inode_id(fd, &cached); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return (initialized = -EOPNOTSUPP); + if (r < 0) + return r; + + *ret = cached; + initialized = getpid_cached(); + return 0; } diff --git a/src/basic/pidfd-util.h b/src/basic/pidfd-util.h index 374e96261b..c20de6df67 100644 --- a/src/basic/pidfd-util.h +++ b/src/basic/pidfd-util.h @@ -17,3 +17,5 @@ int pidfd_get_uid(int fd, uid_t *ret); int pidfd_get_cgroupid(int fd, uint64_t *ret); int pidfd_get_inode_id(int fd, uint64_t *ret); + +int pidfd_get_inode_id_self_cached(uint64_t *ret); diff --git a/src/basic/pidref.c b/src/basic/pidref.c index ccfa2903b6..9b4922b160 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -83,14 +83,17 @@ bool pidref_equal(PidRef *a, PidRef *b) { } int pidref_set_pid(PidRef *pidref, pid_t pid) { + uint64_t pidfdid = 0; int fd; assert(pidref); if (pid < 0) return -ESRCH; - if (pid == 0) + if (pid == 0) { pid = getpid_cached(); + (void) pidfd_get_inode_id_self_cached(&pidfdid); + } fd = pidfd_open(pid, 0); if (fd < 0) { @@ -104,6 +107,7 @@ int pidref_set_pid(PidRef *pidref, pid_t pid) { *pidref = (PidRef) { .fd = fd, .pid = pid, + .fd_id = pidfdid, }; return 0; @@ -388,17 +392,32 @@ int pidref_verify(const PidRef *pidref) { return 1; /* We have a pidfd and it still points to the PID we have, hence all is *really* OK → return 1 */ } -bool pidref_is_self(const PidRef *pidref) { - if (!pidref) +bool pidref_is_self(PidRef *pidref) { + if (!pidref_is_set(pidref)) return false; if (pidref_is_remote(pidref)) return false; - return pidref->pid == getpid_cached(); + if (pidref->pid != getpid_cached()) + return false; + + /* PID1 cannot exit, hence no point in comparing pidfd IDs, they can never change */ + if (pidref->pid == 1) + return true; + + /* Also compare pidfd ID if we can get it */ + if (pidref_acquire_pidfd_id(pidref) < 0) + return true; + + uint64_t self_id; + if (pidfd_get_inode_id_self_cached(&self_id) < 0) + return true; + + return pidref->fd_id == self_id; } -int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) { +int pidref_wait(PidRef *pidref, siginfo_t *ret, int options) { int r; if (!pidref_is_set(pidref)) @@ -424,7 +443,7 @@ int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) { return 0; } -int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret) { +int pidref_wait_for_terminate(PidRef *pidref, siginfo_t *ret) { int r; for (;;) { diff --git a/src/basic/pidref.h b/src/basic/pidref.h index a268af4603..0198db8f8e 100644 --- a/src/basic/pidref.h +++ b/src/basic/pidref.h @@ -39,11 +39,11 @@ struct PidRef { their own pidfs and each process comes with a unique inode number */ }; -#define PIDREF_NULL (const PidRef) { .fd = -EBADF } +#define PIDREF_NULL (PidRef) { .fd = -EBADF } /* A special pidref value that we are using when a PID shall be automatically acquired from some surrounding * context, for example connection peer. Much like PIDREF_NULL it will be considered unset by - * pidref_is_set().*/ + * pidref_is_set(). */ #define PIDREF_AUTOMATIC (const PidRef) { .pid = PID_AUTOMATIC, .fd = -EBADF } /* Turns a pid_t into a PidRef structure on-the-fly *without* acquiring a pidfd for it. (As opposed to @@ -70,14 +70,14 @@ bool pidref_equal(PidRef *a, PidRef *b); int pidref_set_pid(PidRef *pidref, pid_t pid); int pidref_set_pidstr(PidRef *pidref, const char *pid); int pidref_set_pidfd(PidRef *pidref, int fd); -int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/ +int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success */ int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */ int pidref_set_parent(PidRef *ret); static inline int pidref_set_self(PidRef *pidref) { return pidref_set_pid(pidref, 0); } -bool pidref_is_self(const PidRef *pidref); +bool pidref_is_self(PidRef *pidref); void pidref_done(PidRef *pidref); PidRef* pidref_free(PidRef *pidref); @@ -92,8 +92,8 @@ int pidref_kill(const PidRef *pidref, int sig); int pidref_kill_and_sigcont(const PidRef *pidref, int sig); int pidref_sigqueue(const PidRef *pidref, int sig, int value); -int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options); -int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret); +int pidref_wait(PidRef *pidref, siginfo_t *siginfo, int options); +int pidref_wait_for_terminate(PidRef *pidref, siginfo_t *ret); static inline void pidref_done_sigkill_wait(PidRef *pidref) { if (!pidref_is_set(pidref)) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 21e296864a..fbde9c35e6 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1120,7 +1120,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { return 0; } -int pidref_is_my_child(const PidRef *pid) { +int pidref_is_my_child(PidRef *pid) { int r; if (!pidref_is_set(pid)) @@ -1150,7 +1150,7 @@ int pid_is_my_child(pid_t pid) { return pidref_is_my_child(&PIDREF_MAKE_FROM_PID(pid)); } -int pidref_is_unwaited(const PidRef *pid) { +int pidref_is_unwaited(PidRef *pid) { int r; /* Checks whether a PID is still valid at all, including a zombie */ @@ -2290,9 +2290,8 @@ int read_errno(int errno_fd) { assert(errno_fd >= 0); - /* The issue here is that it's impossible to distinguish between - * an error code returned by child and IO error arrised when reading it. - * So, the function logs errors and return EIO for the later case. */ + /* The issue here is that it's impossible to distinguish between an error code returned by child and + * IO error arose when reading it. So, the function logs errors and return EIO for the later case. */ ssize_t n = loop_read(errno_fd, &r, sizeof(r), /* do_poll = */ false); if (n < 0) { diff --git a/src/basic/process-util.h b/src/basic/process-util.h index cfb967c3bc..58fff2b174 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -90,9 +90,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value); int pid_is_alive(pid_t pid); int pidref_is_alive(const PidRef *pidref); int pid_is_unwaited(pid_t pid); -int pidref_is_unwaited(const PidRef *pidref); +int pidref_is_unwaited(PidRef *pidref); int pid_is_my_child(pid_t pid); -int pidref_is_my_child(const PidRef *pidref); +int pidref_is_my_child(PidRef *pidref); int pidref_from_same_root_fs(PidRef *a, PidRef *b); bool is_main_thread(void); diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 866f0ba5ed..fec4f81035 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -23,6 +23,7 @@ #include "missing_syscall.h" #include "missing_threads.h" #include "parse-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "random-util.h" #include "sha256.h" @@ -39,6 +40,7 @@ static void fallback_random_bytes(void *p, size_t n) { uint64_t call_id, block_id; usec_t stamp_mono, stamp_real; pid_t pid, tid; + uint64_t pidfdid; uint8_t auxval[16]; } state = { /* Arbitrary domain separation to prevent other usage of AT_RANDOM from clashing. */ @@ -51,6 +53,7 @@ static void fallback_random_bytes(void *p, size_t n) { memcpy(state.label, "systemd fallback random bytes v1", sizeof(state.label)); memcpy(state.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(state.auxval)); + (void) pidfd_get_inode_id_self_cached(&state.pidfdid); while (n > 0) { struct sha256_ctx ctx; diff --git a/src/basic/recurse-dir.c b/src/basic/recurse-dir.c index 389a784c40..3d6061cfc0 100644 --- a/src/basic/recurse-dir.c +++ b/src/basic/recurse-dir.c @@ -384,7 +384,7 @@ int recurse_dir( if (sx_valid && FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) is_mount = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); else { - r = fd_is_mount_point(dir_fd, de->entries[i]->d_name, 0); + r = is_mount_point_at(dir_fd, de->entries[i]->d_name, 0); if (r < 0) log_debug_errno(r, "Failed to determine whether %s is a submount, assuming not: %m", p); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 2181ee2df5..10bd7c0fe1 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -298,7 +298,7 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl flags |= AT_EMPTY_PATH; } - int ntha_flags = (flags & AT_EMPTY_PATH) | (FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? 0 : AT_SYMLINK_FOLLOW); + int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW); _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL; int mntida = -1, mntidb = -1; diff --git a/src/basic/strv.c b/src/basic/strv.c index a92b1234a3..d817140cc9 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -631,7 +631,7 @@ int strv_insert(char ***l, size_t position, char *value) { n = strv_length(*l); position = MIN(position, n); - /* check for overflow and increase*/ + /* check for overflow and increase */ if (n > SIZE_MAX - 2) return -ENOMEM; m = n + 2; @@ -861,6 +861,26 @@ int strv_compare(char * const *a, char * const *b) { return 0; } +bool strv_equal_ignore_order(char **a, char **b) { + + /* Just like strv_equal(), but doesn't care about the order of elements or about redundant entries + * (i.e. it's even ok if the number of entries in the array differ, as long as the difference just + * consists of repetitions). */ + + if (a == b) + return true; + + STRV_FOREACH(i, a) + if (!strv_contains(b, *i)) + return false; + + STRV_FOREACH(i, b) + if (!strv_contains(a, *i)) + return false; + + return true; +} + void strv_print_full(char * const *l, const char *prefix) { STRV_FOREACH(s, l) printf("%s%s\n", strempty(prefix), *s); @@ -1043,8 +1063,6 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { return 0; } -DEFINE_PRIVATE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free); - static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) { char **l; int r; @@ -1095,7 +1113,7 @@ int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HA assert(key); assert(value); - r = _hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); + r = _hashmap_ensure_allocated(h, &string_hash_ops_free_strv_free HASHMAP_DEBUG_PASS_ARGS); if (r < 0) return r; @@ -1109,7 +1127,7 @@ int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const assert(key); assert(value); - r = _ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); + r = _ordered_hashmap_ensure_allocated(h, &string_hash_ops_free_strv_free HASHMAP_DEBUG_PASS_ARGS); if (r < 0) return r; diff --git a/src/basic/strv.h b/src/basic/strv.h index 49ef19dcb5..86ba06f835 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -96,6 +96,8 @@ static inline bool strv_equal(char * const *a, char * const *b) { return strv_compare(a, b) == 0; } +bool strv_equal_ignore_order(char **a, char **b); + char** strv_new_internal(const char *x, ...) _sentinel_; char** strv_new_ap(const char *x, va_list ap); #define strv_new(...) strv_new_internal(__VA_ARGS__, NULL) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index eeeb657623..678cd4ed4d 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -764,31 +764,25 @@ int make_console_stdio(void) { return 0; } -bool tty_is_vc(const char *tty) { +static int vtnr_from_tty_raw(const char *tty, unsigned *ret) { assert(tty); - return vtnr_from_tty(tty) >= 0; -} + tty = skip_dev_prefix(tty); -bool tty_is_console(const char *tty) { - assert(tty); + const char *e = startswith(tty, "tty"); + if (!e) + return -EINVAL; - return streq(skip_dev_prefix(tty), "console"); + return safe_atou(e, ret); } int vtnr_from_tty(const char *tty) { + unsigned u; int r; assert(tty); - tty = skip_dev_prefix(tty); - - const char *e = startswith(tty, "tty"); - if (!e) - return -EINVAL; - - unsigned u; - r = safe_atou(e, &u); + r = vtnr_from_tty_raw(tty, &u); if (r < 0) return r; if (!vtnr_is_valid(u)) @@ -797,6 +791,23 @@ int vtnr_from_tty(const char *tty) { return (int) u; } +bool tty_is_vc(const char *tty) { + assert(tty); + + /* NB: for >= 0 values no range check is conducted here, on the assumption that the caller will + * either extract vtnr through vtnr_from_tty() later where ERANGE would be reported, or doesn't care + * about whether it's strictly valid, but only asking "does this fall into the vt catogory?", for which + * "yes" seems to be a better answer. */ + + return vtnr_from_tty_raw(tty, /* ret = */ NULL) >= 0; +} + +bool tty_is_console(const char *tty) { + assert(tty); + + return streq(skip_dev_prefix(tty), "console"); +} + int resolve_dev_console(char **ret) { int r; @@ -855,13 +866,12 @@ int resolve_dev_console(char **ret) { int get_kernel_consoles(char ***ret) { _cleanup_strv_free_ char **l = NULL; _cleanup_free_ char *line = NULL; - const char *p; int r; assert(ret); - /* If /sys is mounted read-only this means we are running in some kind of container environment. In that - * case /sys would reflect the host system, not us, hence ignore the data we can read from it. */ + /* If /sys/ is mounted read-only this means we are running in some kind of container environment. + * In that case /sys/ would reflect the host system, not us, hence ignore the data we can read from it. */ if (path_is_read_only_fs("/sys") > 0) goto fallback; @@ -869,8 +879,7 @@ int get_kernel_consoles(char ***ret) { if (r < 0) return r; - p = line; - for (;;) { + for (const char *p = line;;) { _cleanup_free_ char *tty = NULL, *path = NULL; r = extract_first_word(&p, &tty, NULL, 0); @@ -906,8 +915,7 @@ int get_kernel_consoles(char ***ret) { } *ret = TAKE_PTR(l); - - return 0; + return strv_length(*ret); fallback: r = strv_extend(&l, "/dev/console"); @@ -915,7 +923,6 @@ fallback: return r; *ret = TAKE_PTR(l); - return 0; } diff --git a/src/basic/time-util.c b/src/basic/time-util.c index aa51334100..77f354fd1f 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -1636,7 +1636,7 @@ int mktime_or_timegm_usec( assert(tm); - if (tm->tm_year < 69) /* early check for negative (i.e. before 1970) time_t (Note that in some timezones the epoch is in the year 1969!)*/ + if (tm->tm_year < 69) /* early check for negative (i.e. before 1970) time_t (Note that in some timezones the epoch is in the year 1969!) */ return -ERANGE; if ((usec_t) tm->tm_year > CONST_MIN(USEC_INFINITY / USEC_PER_YEAR, (usec_t) TIME_T_MAX / (365U * 24U * 60U * 60U)) - 1900) /* early check for possible overrun of usec_t or time_t */ return -ERANGE; diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 129d61b99c..3be9d16333 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -344,6 +344,20 @@ static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); +static const char* const job_mode_table[_JOB_MODE_MAX] = { + [JOB_FAIL] = "fail", + [JOB_REPLACE] = "replace", + [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", + [JOB_ISOLATE] = "isolate", + [JOB_FLUSH] = "flush", + [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", + [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", + [JOB_TRIGGERING] = "triggering", + [JOB_RESTART_DEPENDENCIES] = "restart-dependencies", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); + SpecialGlyph unit_active_state_to_glyph(UnitActiveState state) { static const SpecialGlyph map[_UNIT_ACTIVE_STATE_MAX] = { [UNIT_ACTIVE] = SPECIAL_GLYPH_BLACK_CIRCLE, diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index bead8b47ac..a9548e2991 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -283,6 +283,20 @@ typedef enum NotifyAccess { _NOTIFY_ACCESS_INVALID = -EINVAL, } NotifyAccess; +typedef enum JobMode { + JOB_FAIL, /* Fail if a conflicting job is already queued */ + JOB_REPLACE, /* Replace an existing conflicting job */ + JOB_REPLACE_IRREVERSIBLY,/* Like JOB_REPLACE + produce irreversible jobs */ + JOB_ISOLATE, /* Start a unit, and stop all others */ + JOB_FLUSH, /* Flush out all other queued jobs when queueing this one */ + JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ + JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ + JOB_TRIGGERING, /* Adds TRIGGERED_BY dependencies to the same transaction */ + JOB_RESTART_DEPENDENCIES,/* A "start" job for the specified unit becomes "restart" for depending units */ + _JOB_MODE_MAX, + _JOB_MODE_INVALID = -EINVAL, +} JobMode; + char* unit_dbus_path_from_name(const char *name); int unit_name_from_dbus_path(const char *path, char **name); @@ -346,4 +360,7 @@ UnitDependency unit_dependency_from_string(const char *s) _pure_; const char* notify_access_to_string(NotifyAccess i) _const_; NotifyAccess notify_access_from_string(const char *s) _pure_; +const char* job_mode_to_string(JobMode t) _const_; +JobMode job_mode_from_string(const char *s) _pure_; + SpecialGlyph unit_active_state_to_glyph(UnitActiveState state); diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index d2daf87ec9..896d85bf5c 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -175,7 +175,7 @@ int fd_getcrtime_at( * most sense. */ if (statx(fd, strempty(path), - (flags & ~AT_SYMLINK_FOLLOW)|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW)|AT_STATX_DONT_SYNC, + at_flags_normalize_nofollow(flags)|AT_STATX_DONT_SYNC, STATX_BTIME, &sx) >= 0 && (sx.stx_mask & STATX_BTIME) && diff --git a/src/boot/boot.c b/src/boot/boot.c index 4ef519d404..53bd22c42d 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -683,7 +683,7 @@ static bool menu_run( bool new_mode = true, clear = true; bool refresh = true, highlight = false; size_t x_start = 0, y_start = 0, y_status = 0, x_max, y_max; - _cleanup_(strv_freep) char16_t **lines = NULL; + _cleanup_strv_free_ char16_t **lines = NULL; _cleanup_free_ char16_t *clearline = NULL, *separator = NULL, *status = NULL; uint64_t timeout_efivar_saved = config->timeout_sec_efivar; uint32_t timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec; @@ -1179,12 +1179,11 @@ static void config_add_entry(Config *config, BootEntry *entry) { /* This is just for paranoia. */ assert(config->n_entries < IDX_MAX); - if ((config->n_entries & 15) == 0) { + if ((config->n_entries & 15) == 0) config->entries = xrealloc( config->entries, sizeof(void *) * config->n_entries, sizeof(void *) * (config->n_entries + 16)); - } config->entries[config->n_entries++] = entry; } @@ -1374,7 +1373,7 @@ static void boot_entry_parse_tries( static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) { _cleanup_free_ char16_t* old_path = NULL, *new_path = NULL; - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; _cleanup_free_ EFI_FILE_INFO *file_info = NULL; size_t file_info_size; EFI_STATUS err; @@ -1387,7 +1386,7 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) { if (!entry->path || !entry->current_name || !entry->next_name) return EFI_SUCCESS; - _cleanup_(file_closep) EFI_FILE *root = NULL; + _cleanup_file_close_ EFI_FILE *root = NULL; err = open_volume(entry->device, &root); if (err != EFI_SUCCESS) return log_error_status(err, "Error opening entry root path: %m"); @@ -1396,7 +1395,7 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) { err = root->Open(root, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL); if (err != EFI_SUCCESS) - return log_error_status(err, "Error opening boot entry: %m"); + return log_error_status(err, "Error opening boot entry '%ls': %m", old_path); err = get_file_info(handle, &file_info, &file_info_size); if (err != EFI_SUCCESS) @@ -1525,7 +1524,7 @@ static void boot_entry_add_type1( return; /* check existence */ - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) return; @@ -1652,7 +1651,7 @@ static void config_load_type1_entries( EFI_FILE *root_dir, const char16_t *loaded_image_path) { - _cleanup_(file_closep) EFI_FILE *entries_dir = NULL; + _cleanup_file_close_ EFI_FILE *entries_dir = NULL; _cleanup_free_ EFI_FILE_INFO *f = NULL; size_t f_size = 0; EFI_STATUS err; @@ -1907,7 +1906,7 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { assert(root_dir); assert(loader_path); - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; err = root_dir->Open(root_dir, &handle, (char16_t *) loader_path, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) return false; @@ -1970,7 +1969,7 @@ static BootEntry* config_add_entry_loader_auto( } /* check existence */ - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; EFI_STATUS err = root_dir->Open(root_dir, &handle, (char16_t *) loader, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) return NULL; @@ -2007,7 +2006,7 @@ static void config_add_entry_osx(Config *config) { return; for (size_t i = 0; i < n_handles; i++) { - _cleanup_(file_closep) EFI_FILE *root = NULL; + _cleanup_file_close_ EFI_FILE *root = NULL; if (open_volume(handles[i], &root) != EFI_SUCCESS) continue; @@ -2164,7 +2163,7 @@ static void boot_entry_add_type2( assert(path); assert(filename); - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; err = dir->Open(dir, &handle, (char16_t *) filename, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) return; @@ -2364,7 +2363,7 @@ static void config_load_type2_entries( EFI_HANDLE *device, EFI_FILE *root_dir) { - _cleanup_(file_closep) EFI_FILE *linux_dir = NULL; + _cleanup_file_close_ EFI_FILE *linux_dir = NULL; _cleanup_free_ EFI_FILE_INFO *f = NULL; size_t f_size = 0; EFI_STATUS err; @@ -2403,7 +2402,7 @@ static void config_load_xbootldr( Config *config, EFI_HANDLE *device) { - _cleanup_(file_closep) EFI_FILE *root_dir = NULL; + _cleanup_file_close_ EFI_FILE *root_dir = NULL; EFI_HANDLE new_device = NULL; /* avoid false maybe-uninitialized warning */ EFI_STATUS err; @@ -2455,7 +2454,7 @@ static EFI_STATUS initrd_prepare( else options = xasprintf("initrd=%ls", *i); - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, 0); if (err != EFI_SUCCESS) return err; @@ -2476,7 +2475,7 @@ static EFI_STATUS initrd_prepare( uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); STRV_FOREACH(i, entry->initrd) { - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, 0); if (err != EFI_SUCCESS) return err; @@ -2539,7 +2538,7 @@ static EFI_STATUS image_start( if (entry->call) (void) entry->call(); - _cleanup_(file_closep) EFI_FILE *image_root = NULL; + _cleanup_file_close_ EFI_FILE *image_root = NULL; err = open_volume(entry->device, &image_root); if (err != EFI_SUCCESS) return log_error_status(err, "Error opening root path: %m"); @@ -2697,7 +2696,7 @@ static void save_selected_entry(const Config *config, const BootEntry *entry) { static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) { EFI_STATUS err; - _cleanup_(file_closep) EFI_FILE *keys_basedir = NULL; + _cleanup_file_close_ EFI_FILE *keys_basedir = NULL; if (!IN_SET(secure_boot_mode(), SECURE_BOOT_SETUP, SECURE_BOOT_AUDIT)) return EFI_SUCCESS; @@ -2869,7 +2868,7 @@ static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI static EFI_STATUS run(EFI_HANDLE image) { EFI_LOADED_IMAGE_PROTOCOL *loaded_image; - _cleanup_(file_closep) EFI_FILE *root_dir = NULL; + _cleanup_file_close_ EFI_FILE *root_dir = NULL; _cleanup_(config_free) Config config = {}; EFI_STATUS err; uint64_t init_usec; diff --git a/src/boot/console.c b/src/boot/console.c index 067ee7c091..b174146cdf 100644 --- a/src/boot/console.c +++ b/src/boot/console.c @@ -117,10 +117,8 @@ EFI_STATUS console_key_read(uint64_t *key, uint64_t timeout_usec) { /* If the extra input device we found returns something, always use that instead * to work around broken firmware freezing on ConIn/ConInEx. */ - if (extraInEx && BS->CheckEvent(extraInEx->WaitForKeyEx) == EFI_SUCCESS) { - conInEx = extraInEx; - extraInEx = NULL; - } + if (extraInEx && BS->CheckEvent(extraInEx->WaitForKeyEx) == EFI_SUCCESS) + conInEx = TAKE_PTR(extraInEx); /* Do not fall back to ConIn if we have a ConIn that supports TextInputEx. * The two may be out of sync on some firmware, giving us double input. */ diff --git a/src/boot/cpio.c b/src/boot/cpio.c index ba439740f7..fc5e303d7e 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -314,11 +314,11 @@ EFI_STATUS pack_cpio( struct iovec *ret_buffer, bool *ret_measured) { - _cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL; + _cleanup_file_close_ EFI_FILE *root = NULL, *extra_dir = NULL; size_t dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0; _cleanup_free_ char16_t *rel_dropin_dir = NULL; _cleanup_free_ EFI_FILE_INFO *dirent = NULL; - _cleanup_(strv_freep) char16_t **items = NULL; + _cleanup_strv_free_ char16_t **items = NULL; _cleanup_free_ void *buffer = NULL; uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ EFI_STATUS err; diff --git a/src/boot/devicetree.c b/src/boot/devicetree.c index 2ec5ca28dd..85fc07c49f 100644 --- a/src/boot/devicetree.c +++ b/src/boot/devicetree.c @@ -63,7 +63,7 @@ static EFI_STATUS devicetree_fixup(struct devicetree_state *state, size_t len) { } EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir, char16_t *name) { - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; _cleanup_free_ EFI_FILE_INFO *info = NULL; size_t len; EFI_STATUS err; diff --git a/src/boot/drivers.c b/src/boot/drivers.c index 067455771f..078c6cb993 100644 --- a/src/boot/drivers.c +++ b/src/boot/drivers.c @@ -73,7 +73,7 @@ EFI_STATUS load_drivers( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI_FILE *root_dir) { - _cleanup_(file_closep) EFI_FILE *drivers_dir = NULL; + _cleanup_file_close_ EFI_FILE *drivers_dir = NULL; _cleanup_free_ EFI_FILE_INFO *dirent = NULL; size_t dirent_size = 0, n_succeeded = 0; EFI_STATUS err; diff --git a/src/boot/pe.h b/src/boot/pe.h index 56312b15b3..a2847de882 100644 --- a/src/boot/pe.h +++ b/src/boot/pe.h @@ -3,7 +3,7 @@ #include "efi.h" -/* This is the actual PE format of the section header*/ +/* This is the actual PE format of the section header */ typedef struct PeSectionHeader { uint8_t Name[8]; uint32_t VirtualSize; diff --git a/src/boot/random-seed.c b/src/boot/random-seed.c index 920f55eefc..9837a85ccd 100644 --- a/src/boot/random-seed.c +++ b/src/boot/random-seed.c @@ -121,7 +121,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { _cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL; struct linux_efi_random_seed *previous_seed_table = NULL; _cleanup_free_ void *seed = NULL, *system_token = NULL; - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; _cleanup_free_ EFI_FILE_INFO *info = NULL; struct sha256_ctx hash; uint64_t uefi_monotonic_counter = 0; @@ -199,7 +199,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0); if (err != EFI_SUCCESS) { - if (err != EFI_NOT_FOUND && err != EFI_WRITE_PROTECTED) + if (!IN_SET(err, EFI_NOT_FOUND, EFI_WRITE_PROTECTED)) log_error_status(err, "Failed to open random seed file: %m"); return err; } diff --git a/src/boot/secure-boot.c b/src/boot/secure-boot.c index dc61457512..fc8b96ef44 100644 --- a/src/boot/secure-boot.c +++ b/src/boot/secure-boot.c @@ -115,7 +115,7 @@ EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool printf("\n"); } - _cleanup_(file_closep) EFI_FILE *dir = NULL; + _cleanup_file_close_ EFI_FILE *dir = NULL; err = open_directory(root_dir, path, &dir); if (err != EFI_SUCCESS) diff --git a/src/boot/shim.c b/src/boot/shim.c index 5928553471..514a1d0eda 100644 --- a/src/boot/shim.c +++ b/src/boot/shim.c @@ -56,7 +56,7 @@ static bool shim_validate( if (err != EFI_SUCCESS) return false; - _cleanup_(file_closep) EFI_FILE *root = NULL; + _cleanup_file_close_ EFI_FILE *root = NULL; err = open_volume(device_handle, &root); if (err != EFI_SUCCESS) return false; diff --git a/src/boot/splash.c b/src/boot/splash.c index 8daeb71cb2..7cf8d0d0c9 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -86,7 +86,7 @@ static EFI_STATUS bmp_parse_header( case 16: case 32: - if (dib->compression != 0 && dib->compression != 3) + if (!IN_SET(dib->compression, 0, 3)) return EFI_UNSUPPORTED; break; diff --git a/src/boot/stub.c b/src/boot/stub.c index cf990df2e7..e74b7db95a 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -294,7 +294,7 @@ static EFI_STATUS load_addons_from_dir( size_t *n_items, size_t *n_allocated) { - _cleanup_(file_closep) EFI_FILE *extra_dir = NULL; + _cleanup_file_close_ EFI_FILE *extra_dir = NULL; _cleanup_free_ EFI_FILE_INFO *dirent = NULL; size_t dirent_size = 0; EFI_STATUS err; @@ -547,8 +547,8 @@ static EFI_STATUS load_addons( NamedAddon **ucode_addons, /* Ditto */ size_t *n_ucode_addons) { - _cleanup_(strv_freep) char16_t **items = NULL; - _cleanup_(file_closep) EFI_FILE *root = NULL; + _cleanup_strv_free_ char16_t **items = NULL; + _cleanup_file_close_ EFI_FILE *root = NULL; size_t n_items = 0, n_allocated = 0; EFI_STATUS err; @@ -718,7 +718,7 @@ static void refresh_random_seed(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { if (FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED)) return; - _cleanup_(file_closep) EFI_FILE *esp_dir = NULL; + _cleanup_file_close_ EFI_FILE *esp_dir = NULL; err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir); if (err != EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */ return; diff --git a/src/boot/util.c b/src/boot/util.c index f5f748bc6c..395c1f2abc 100644 --- a/src/boot/util.c +++ b/src/boot/util.c @@ -172,7 +172,7 @@ EFI_STATUS file_read( assert(name); assert(ret); - _cleanup_(file_closep) EFI_FILE *handle = NULL; + _cleanup_file_close_ EFI_FILE *handle = NULL; err = dir->Open(dir, &handle, (char16_t*) name, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) return err; @@ -280,8 +280,7 @@ EFI_STATUS readdir( if (sz == 0) { /* End of directory */ - free(*buffer); - *buffer = NULL; + *buffer = mfree(*buffer); *buffer_size = 0; } @@ -306,8 +305,7 @@ char16_t **strv_free(char16_t **v) { for (char16_t **i = v; *i; i++) free(*i); - free(v); - return NULL; + return mfree(v); } EFI_STATUS open_directory( @@ -315,7 +313,7 @@ EFI_STATUS open_directory( const char16_t *path, EFI_FILE **ret) { - _cleanup_(file_closep) EFI_FILE *dir = NULL; + _cleanup_file_close_ EFI_FILE *dir = NULL; _cleanup_free_ EFI_FILE_INFO *file_info = NULL; EFI_STATUS err; diff --git a/src/boot/util.h b/src/boot/util.h index 90da8ad69d..49ba119613 100644 --- a/src/boot/util.h +++ b/src/boot/util.h @@ -140,6 +140,8 @@ static inline void file_closep(EFI_FILE **handle) { (*handle)->Close(*handle); } +#define _cleanup_file_close_ _cleanup_(file_closep) + static inline void unload_imagep(EFI_HANDLE *image) { if (*image) (void) BS->UnloadImage(*image); @@ -167,6 +169,8 @@ static inline void strv_freep(char16_t ***p) { strv_free(*p); } +#define _cleanup_strv_free_ _cleanup_(strv_freep) + EFI_STATUS open_directory(EFI_FILE *root_dir, const char16_t *path, EFI_FILE **ret); /* Conversion between EFI_PHYSICAL_ADDRESS and pointers is not obvious. The former is always 64-bit, even on diff --git a/src/boot/vmm.c b/src/boot/vmm.c index fea4c446c0..29dbd6ffdb 100644 --- a/src/boot/vmm.c +++ b/src/boot/vmm.c @@ -85,7 +85,7 @@ EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { dp_err = efivar_get_raw(MAKE_GUID_PTR(VMM_BOOT_ORDER), order_str, (void**) &dp, NULL); for (size_t i = 0; i < n_handles; i++) { - _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL; + _cleanup_file_close_ EFI_FILE *root_dir = NULL, *efi_dir = NULL; EFI_DEVICE_PATH *fs; err = BS->HandleProtocol( diff --git a/src/bootctl/bootctl-reboot-to-firmware.c b/src/bootctl/bootctl-reboot-to-firmware.c index 0bedd230bb..e24234aa90 100644 --- a/src/bootctl/bootctl-reboot-to-firmware.c +++ b/src/bootctl/bootctl-reboot-to-firmware.c @@ -62,8 +62,9 @@ int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *paramete int vl_method_get_reboot_to_firmware(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { int r; - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; r = efi_get_reboot_to_firmware(); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 6bcb348935..40642d3c1c 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -568,24 +568,24 @@ int verb_status(int argc, char *argv[], void *userdata) { return r; } -static int ref_file(Hashmap *known_files, const char *fn, int increment) { +static int ref_file(Hashmap **known_files, const char *fn, int increment) { char *k = NULL; int n, r; assert(known_files); - /* just gracefully ignore this. This way the caller doesn't - have to verify whether the bootloader entry is relevant */ + /* just gracefully ignore this. This way the caller doesn't have to verify whether the bootloader + * entry is relevant. */ if (!fn) return 0; - n = PTR_TO_INT(hashmap_get2(known_files, fn, (void**)&k)); + n = PTR_TO_INT(hashmap_get2(*known_files, fn, (void**)&k)); n += increment; assert(n >= 0); if (n == 0) { - (void) hashmap_remove(known_files, fn); + (void) hashmap_remove(*known_files, fn); free(k); } else if (!k) { _cleanup_free_ char *t = NULL; @@ -593,12 +593,12 @@ static int ref_file(Hashmap *known_files, const char *fn, int increment) { t = strdup(fn); if (!t) return -ENOMEM; - r = hashmap_put(known_files, t, INT_TO_PTR(n)); + r = hashmap_ensure_put(known_files, &path_hash_ops_free, t, INT_TO_PTR(n)); if (r < 0) return r; TAKE_PTR(t); } else { - r = hashmap_update(known_files, fn, INT_TO_PTR(n)); + r = hashmap_update(*known_files, fn, INT_TO_PTR(n)); if (r < 0) return r; } @@ -606,7 +606,7 @@ static int ref_file(Hashmap *known_files, const char *fn, int increment) { return n; } -static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *root) { +static void deref_unlink_file(Hashmap **known_files, const char *fn, const char *root) { _cleanup_free_ char *path = NULL; int r; @@ -647,38 +647,34 @@ static void deref_unlink_file(Hashmap *known_files, const char *fn, const char * } static int count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files) { - _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL; + _cleanup_hashmap_free_ Hashmap *known_files = NULL; int r; assert(config); assert(ret_known_files); - known_files = hashmap_new(&path_hash_ops); - if (!known_files) - return -ENOMEM; - for (size_t i = 0; i < config->n_entries; i++) { const BootEntry *e = config->entries + i; if (!path_equal(e->root, root)) continue; - r = ref_file(known_files, e->kernel, +1); + r = ref_file(&known_files, e->kernel, +1); if (r < 0) return r; - r = ref_file(known_files, e->efi, +1); + r = ref_file(&known_files, e->efi, +1); if (r < 0) return r; STRV_FOREACH(s, e->initrd) { - r = ref_file(known_files, *s, +1); + r = ref_file(&known_files, *s, +1); if (r < 0) return r; } - r = ref_file(known_files, e->device_tree, +1); + r = ref_file(&known_files, e->device_tree, +1); if (r < 0) return r; STRV_FOREACH(s, e->device_tree_overlay) { - r = ref_file(known_files, *s, +1); + r = ref_file(&known_files, *s, +1); if (r < 0) return r; } @@ -704,7 +700,7 @@ static int boot_config_find_in(const BootConfig *config, const char *root, const } static int unlink_entry(const BootConfig *config, const char *root, const char *id) { - _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL; + _cleanup_hashmap_free_ Hashmap *known_files = NULL; const BootEntry *e = NULL; int r; @@ -725,13 +721,13 @@ static int unlink_entry(const BootConfig *config, const char *root, const char * e = &config->entries[r]; - deref_unlink_file(known_files, e->kernel, e->root); - deref_unlink_file(known_files, e->efi, e->root); + deref_unlink_file(&known_files, e->kernel, e->root); + deref_unlink_file(&known_files, e->efi, e->root); STRV_FOREACH(s, e->initrd) - deref_unlink_file(known_files, *s, e->root); - deref_unlink_file(known_files, e->device_tree, e->root); + deref_unlink_file(&known_files, *s, e->root); + deref_unlink_file(&known_files, e->device_tree, e->root); STRV_FOREACH(s, e->device_tree_overlay) - deref_unlink_file(known_files, *s, e->root); + deref_unlink_file(&known_files, *s, e->root); if (arg_dry_run) log_info("Would remove \"%s\"", e->path); @@ -758,7 +754,6 @@ static int list_remove_orphaned_file( Hashmap *known_files = userdata; assert(path); - assert(known_files); if (event != RECURSE_DIR_ENTRY) return RECURSE_DIR_CONTINUE; @@ -780,7 +775,7 @@ static int cleanup_orphaned_files( const BootConfig *config, const char *root) { - _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL; + _cleanup_hashmap_free_ Hashmap *known_files = NULL; _cleanup_free_ char *full = NULL, *p = NULL; _cleanup_close_ int dir_fd = -EBADF; int r; @@ -876,8 +871,9 @@ int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, s assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index e959b307c6..8e197a0303 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -4482,7 +4482,7 @@ Unit *manager_get_unit_by_pidref_watching(Manager *m, const PidRef *pid) { return NULL; } -Unit *manager_get_unit_by_pidref(Manager *m, const PidRef *pid) { +Unit* manager_get_unit_by_pidref(Manager *m, PidRef *pid) { Unit *u; assert(m); diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 807e56c621..0828916319 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -466,7 +466,7 @@ unsigned manager_dispatch_cgroup_realize_queue(Manager *m); Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); Unit *manager_get_unit_by_pidref_cgroup(Manager *m, const PidRef *pid); Unit *manager_get_unit_by_pidref_watching(Manager *m, const PidRef *pid); -Unit* manager_get_unit_by_pidref(Manager *m, const PidRef *pid); +Unit* manager_get_unit_by_pidref(Manager *m, PidRef *pid); Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); uint64_t unit_get_ancestor_memory_min(Unit *u); diff --git a/src/core/core-varlink.c b/src/core/core-varlink.c index 4f0563a1c0..985bd702b8 100644 --- a/src/core/core-varlink.c +++ b/src/core/core-varlink.c @@ -10,6 +10,7 @@ #include "varlink-internal.h" #include "varlink-io.systemd.UserDatabase.h" #include "varlink-io.systemd.ManagedOOM.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" typedef struct LookupParameters { @@ -324,8 +325,9 @@ static int vl_method_subscribe_managed_oom_cgroups( if (!streq(u->id, "systemd-oomd.service")) return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; /* We only take one subscriber for this method so return an error if there's already an existing one. * This shouldn't happen since systemd-oomd is the only client of this method. */ @@ -589,7 +591,8 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_add_interface_many( s, &vl_interface_io_systemd_UserDatabase, - &vl_interface_io_systemd_ManagedOOM); + &vl_interface_io_systemd_ManagedOOM, + &vl_interface_io_systemd_service); if (r < 0) return log_debug_errno(r, "Failed to add interfaces to varlink server: %m"); @@ -598,7 +601,9 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record, "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record, "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships, - "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups); + "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_debug_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 4b61002eac..b59f9dec76 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -971,7 +971,7 @@ static int transient_unit_from_message( Manager *m, sd_bus_message *message, const char *name, - Unit **unit, + Unit **ret_unit, sd_bus_error *error) { UnitType t; @@ -1022,7 +1022,8 @@ static int transient_unit_from_message( unit_add_to_load_queue(u); manager_dispatch_load_queue(m); - *unit = u; + if (ret_unit) + *ret_unit = u; return 0; } @@ -1042,14 +1043,13 @@ static int transient_aux_units_from_message( return r; while ((r = sd_bus_message_enter_container(message, 'r', "sa(sv)")) > 0) { - const char *name = NULL; - Unit *u; + const char *name; r = sd_bus_message_read(message, "s", &name); if (r < 0) return r; - r = transient_unit_from_message(m, message, name, &u, error); + r = transient_unit_from_message(m, message, name, /* unit = */ NULL, error); if (r < 0) return r; diff --git a/src/core/device.c b/src/core/device.c index e201d09976..15746094a4 100644 --- a/src/core/device.c +++ b/src/core/device.c @@ -558,7 +558,7 @@ static int device_add_udev_wants(Unit *u, sd_device *dev) { for (;;) { _cleanup_free_ char *word = NULL, *k = NULL; - r = extract_first_word(&wants, &word, NULL, EXTRACT_UNQUOTE); + r = extract_first_word(&wants, &word, NULL, EXTRACT_UNQUOTE | EXTRACT_RETAIN_ESCAPE); if (r == 0) break; if (r == -ENOMEM) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index c58698e564..24e700a289 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -21,6 +21,7 @@ #include "apparmor-util.h" #endif #include "argv-util.h" +#include "ask-password-api.h" #include "barrier.h" #include "bitfield.h" #include "bpf-dlopen.h" @@ -1048,15 +1049,114 @@ static int enforce_user( #if HAVE_PAM -static int null_conv( +static void pam_response_free_array(struct pam_response *responses, size_t n_responses) { + assert(responses || n_responses == 0); + + FOREACH_ARRAY(resp, responses, n_responses) + erase_and_free(resp->resp); + + free(responses); +} + +typedef struct AskPasswordConvData { + const ExecContext *context; + const ExecParameters *params; +} AskPasswordConvData; + +static int ask_password_conv( int num_msg, - const struct pam_message **msg, - struct pam_response **resp, - void *appdata_ptr) { + const struct pam_message *msg[], + struct pam_response **ret, + void *userdata) { + + AskPasswordConvData *data = ASSERT_PTR(userdata); + bool set_credential_env_var = false; + int r; + + assert(num_msg >= 0); + assert(msg); + assert(data->context); + assert(data->params); + + size_t n = num_msg; + struct pam_response *responses = new0(struct pam_response, n); + if (!responses) + return PAM_BUF_ERR; + CLEANUP_ARRAY(responses, n, pam_response_free_array); + + for (size_t i = 0; i < n; i++) { + const struct pam_message *mi = *msg + i; + + switch (mi->msg_style) { + + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: { + + /* Locally set the $CREDENTIALS_DIRECTORY to the credentials directory we just populated */ + if (!set_credential_env_var) { + _cleanup_free_ char *creds_dir = NULL; + r = exec_context_get_credential_directory(data->context, data->params, data->params->unit_id, &creds_dir); + if (r < 0) + return log_exec_error_errno(data->context, data->params, r, "Failed to determine credentials directory: %m"); + + if (creds_dir) { + if (setenv("CREDENTIALS_DIRECTORY", creds_dir, /* overwrite= */ true) < 0) + return log_exec_error_errno(data->context, data->params, r, "Failed to set $CREDENTIALS_DIRECTORY: %m"); + } else + (void) unsetenv("CREDENTIALS_DIRECTORY"); + + set_credential_env_var = true; + } + + _cleanup_free_ char *credential_name = strjoin("pam.authtok.", data->context->pam_name); + if (!credential_name) + return log_oom(); + + AskPasswordRequest req = { + .message = mi->msg, + .credential = credential_name, + .tty_fd = -EBADF, + .hup_fd = -EBADF, + .until = usec_add(now(CLOCK_MONOTONIC), 15 * USEC_PER_SEC), + }; - /* We don't support conversations */ + _cleanup_strv_free_erase_ char **acquired = NULL; + r = ask_password_auto( + &req, + ASK_PASSWORD_ACCEPT_CACHED| + ASK_PASSWORD_NO_TTY| + (mi->msg_style == PAM_PROMPT_ECHO_ON ? ASK_PASSWORD_ECHO : 0), + &acquired); + if (r < 0) { + log_exec_error_errno(data->context, data->params, r, "Failed to query for password: %m"); + return PAM_CONV_ERR; + } + + responses[i].resp = strdup(ASSERT_PTR(acquired[0])); + if (!responses[i].resp) { + log_oom(); + return PAM_BUF_ERR; + } + break; + } + + case PAM_ERROR_MSG: + log_exec_error(data->context, data->params, "PAM: %s", mi->msg); + break; + + case PAM_TEXT_INFO: + log_exec_info(data->context, data->params, "PAM: %s", mi->msg); + break; + + default: + return PAM_CONV_ERR; + } + } - return PAM_CONV_ERR; + *ret = TAKE_PTR(responses); + n = 0; + + return PAM_SUCCESS; } static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int flags) { @@ -1074,24 +1174,27 @@ static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int fl return r != PAM_SUCCESS ? r : s; } - #endif static int setup_pam( - const char *name, + const ExecContext *context, + ExecParameters *params, const char *user, uid_t uid, gid_t gid, - const char *tty, char ***env, /* updated on success */ const int fds[], size_t n_fds, int exec_fd) { #if HAVE_PAM + AskPasswordConvData conv_data = { + .context = context, + .params = params, + }; - static const struct pam_conv conv = { - .conv = null_conv, - .appdata_ptr = NULL + const struct pam_conv conv = { + .conv = ask_password_conv, + .appdata_ptr = &conv_data, }; _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; @@ -1103,8 +1206,12 @@ static int setup_pam( pid_t parent_pid; int flags = 0; - assert(name); + assert(context); + assert(params); assert(user); + assert(uid_is_valid(uid)); + assert(gid_is_valid(gid)); + assert(fds || n_fds == 0); assert(env); /* We set up PAM in the parent process, then fork. The child @@ -1121,12 +1228,13 @@ static int setup_pam( if (log_get_max_level() < LOG_DEBUG) flags |= PAM_SILENT; - pam_code = pam_start(name, user, &conv, &handle); + pam_code = pam_start(context->pam_name, user, &conv, &handle); if (pam_code != PAM_SUCCESS) { handle = NULL; goto fail; } + const char *tty = context->tty_path; if (!tty) { _cleanup_free_ char *q = NULL; @@ -4976,7 +5084,7 @@ int exec_invoke( * wins here. (See above.) */ /* All fds passed in the fds array will be closed in the pam child process. */ - r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds, params->exec_fd); + r = setup_pam(context, params, username, uid, gid, &accum_env, params->fds, n_fds, params->exec_fd); if (r < 0) { *exit_status = EXIT_PAM; return log_exec_error_errno(context, params, r, "Failed to set up PAM session: %m"); @@ -5459,7 +5567,7 @@ int exec_invoke( * * Hence there is no security impact to raise it in the effective set before execve */ - r = capability_gain_cap_setpcap(/* return_caps= */ NULL); + r = capability_gain_cap_setpcap(/* ret_before_caps = */ NULL); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return log_exec_error_errno(context, params, r, "Failed to gain CAP_SETPCAP for setting secure bits"); diff --git a/src/core/execute.c b/src/core/execute.c index 2ce67f9dbd..6811bf301c 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -776,7 +776,7 @@ int exec_context_destroy_mount_ns_dir(Unit *u) { if (!p) return -ENOMEM; - /* This is only filled transiently (see mount_in_namespace()), should be empty or even non-existent*/ + /* This is only filled transiently (see mount_in_namespace()), should be empty or even non-existent. */ if (rmdir(p) < 0 && errno != ENOENT) log_unit_debug_errno(u, errno, "Unable to remove propagation dir '%s', ignoring: %m", p); diff --git a/src/core/job.c b/src/core/job.c index 8fbbe757fe..f674a7845f 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -448,9 +448,8 @@ bool job_type_is_redundant(JobType a, UnitActiveState b) { return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING, UNIT_REFRESHING); case JOB_RELOAD: - return - b == UNIT_RELOADING; - + /* Reload jobs are never considered redundant/duplicate. Refer to jobs_may_late_merge() for + * a detailed justification. */ case JOB_RESTART: /* Restart jobs must always be kept. * @@ -1651,20 +1650,6 @@ static const char* const job_type_table[_JOB_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); -static const char* const job_mode_table[_JOB_MODE_MAX] = { - [JOB_FAIL] = "fail", - [JOB_REPLACE] = "replace", - [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", - [JOB_ISOLATE] = "isolate", - [JOB_FLUSH] = "flush", - [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", - [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", - [JOB_TRIGGERING] = "triggering", - [JOB_RESTART_DEPENDENCIES] = "restart-dependencies", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); - static const char* const job_result_table[_JOB_RESULT_MAX] = { [JOB_DONE] = "done", [JOB_CANCELED] = "canceled", diff --git a/src/core/job.h b/src/core/job.h index f3daf9d094..406eab74ae 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -14,7 +14,6 @@ typedef struct Job Job; typedef struct JobDependency JobDependency; typedef enum JobType JobType; typedef enum JobState JobState; -typedef enum JobMode JobMode; typedef enum JobResult JobResult; typedef struct Manager Manager; typedef struct Unit Unit; @@ -71,20 +70,6 @@ enum JobState { _JOB_STATE_INVALID = -EINVAL, }; -enum JobMode { - JOB_FAIL, /* Fail if a conflicting job is already queued */ - JOB_REPLACE, /* Replace an existing conflicting job */ - JOB_REPLACE_IRREVERSIBLY,/* Like JOB_REPLACE + produce irreversible jobs */ - JOB_ISOLATE, /* Start a unit, and stop all others */ - JOB_FLUSH, /* Flush out all other queued jobs when queueing this one */ - JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ - JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ - JOB_TRIGGERING, /* Adds TRIGGERED_BY dependencies to the same transaction */ - JOB_RESTART_DEPENDENCIES,/* A "start" job for the specified unit becomes "restart" for depending units */ - _JOB_MODE_MAX, - _JOB_MODE_INVALID = -EINVAL, -}; - enum JobResult { JOB_DONE, /* Job completed successfully (or skipped due to an unmet ConditionXYZ=) */ JOB_CANCELED, /* Job canceled by a conflicting job installation or by explicit cancel request */ @@ -243,9 +228,6 @@ JobType job_type_from_string(const char *s) _pure_; const char* job_state_to_string(JobState t) _const_; JobState job_state_from_string(const char *s) _pure_; -const char* job_mode_to_string(JobMode t) _const_; -JobMode job_mode_from_string(const char *s) _pure_; - const char* job_result_to_string(JobResult t) _const_; JobResult job_result_from_string(const char *s) _pure_; diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c index c39b136e04..5dc429a07d 100644 --- a/src/core/kmod-setup.c +++ b/src/core/kmod-setup.c @@ -124,7 +124,7 @@ int kmod_setup(void) { { "virtio_rng", NULL, false, false, has_virtio_rng }, /* we want early logging to hvc consoles if possible, and make sure systemd-getty-generator - * can rely on all consoles being probed already.*/ + * can rely on all consoles being probed already. */ { "virtio_console", NULL, false, false, has_virtio_console }, /* Make sure we can send sd-notify messages over vsock as early as possible. */ diff --git a/src/core/mount.c b/src/core/mount.c index 073e9c7193..6fff94d19b 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -1927,7 +1927,7 @@ static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { assert(m); - r = libmount_parse(NULL, NULL, &table, &iter); + r = libmount_parse_with_utab(&table, &iter); if (r < 0) return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); diff --git a/src/core/service.c b/src/core/service.c index e5d23f87dd..626a47f7a8 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -36,6 +36,7 @@ #include "open-file.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "random-util.h" #include "selinux-util.h" @@ -1769,7 +1770,7 @@ static int service_spawn_internal( if (r < 0) return r; - our_env = new0(char*, 13); + our_env = new0(char*, 15); if (!our_env) return -ENOMEM; @@ -1781,14 +1782,25 @@ static int service_spawn_internal( return -ENOMEM; } - if (pidref_is_set(&s->main_pid)) + if (pidref_is_set(&s->main_pid)) { if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid.pid) < 0) return -ENOMEM; - if (MANAGER_IS_USER(UNIT(s)->manager)) + if (pidref_acquire_pidfd_id(&s->main_pid) >= 0) + if (asprintf(our_env + n_env++, "MAINPIDFDID=%" PRIu64, s->main_pid.fd_id) < 0) + return -ENOMEM; + } + + if (MANAGER_IS_USER(UNIT(s)->manager)) { if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid_cached()) < 0) return -ENOMEM; + uint64_t pidfdid; + if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) + if (asprintf(our_env + n_env++, "MANAGERPIDFDID=%" PRIu64, pidfdid) < 0) + return -ENOMEM; + } + if (s->pid_file) if (asprintf(our_env + n_env++, "PIDFILE=%s", s->pid_file) < 0) return -ENOMEM; diff --git a/src/core/unit.c b/src/core/unit.c index 99e36fe455..7685ab1996 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -6007,7 +6007,7 @@ bool unit_needs_console(Unit *u) { return exec_context_may_touch_console(ec); } -int unit_pid_attachable(Unit *u, const PidRef *pid, sd_bus_error *error) { +int unit_pid_attachable(Unit *u, PidRef *pid, sd_bus_error *error) { int r; assert(u); diff --git a/src/core/unit.h b/src/core/unit.h index 8a3b812a4b..45b7d72b7a 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -1004,7 +1004,7 @@ int unit_warn_leftover_processes(Unit *u, bool start); bool unit_needs_console(Unit *u); -int unit_pid_attachable(Unit *unit, const PidRef *pid, sd_bus_error *error); +int unit_pid_attachable(Unit *unit, PidRef *pid, sd_bus_error *error); static inline bool unit_has_job_type(Unit *u, JobType type) { return u && u->job && u->job->type == type; diff --git a/src/creds/creds.c b/src/creds/creds.c index bf76e4acf0..cd53c90ce3 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -65,6 +65,7 @@ static bool arg_quiet = false; static bool arg_varlink = false; static uid_t arg_uid = UID_INVALID; static bool arg_allow_null = false; +static bool arg_ask_password = true; STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); @@ -586,16 +587,18 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid."); - if (geteuid() != 0) + if (geteuid() != 0) { + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = ipc_encrypt_credential( name, timestamp, arg_not_after, arg_uid, &plaintext, - /* flags= */ 0, + arg_ask_password ? CREDENTIAL_IPC_ALLOW_INTERACTIVE : 0, &output); - else + } else r = encrypt_credential_and_warn( arg_with_key, name, @@ -690,15 +693,17 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME); - if (geteuid() != 0) + if (geteuid() != 0) { + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = ipc_decrypt_credential( name, timestamp, arg_uid, &input, - /* flags= */ 0, + arg_ask_password ? CREDENTIAL_IPC_ALLOW_INTERACTIVE : 0, &plaintext); - else + } else r = decrypt_credential_and_warn( name, timestamp, @@ -832,6 +837,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_USER, ARG_UID, ARG_ALLOW_NULL, + ARG_NO_ASK_PASSWORD, }; static const struct option options[] = { @@ -857,6 +863,7 @@ static int parse_argv(int argc, char *argv[]) { { "user", no_argument, NULL, ARG_USER }, { "uid", required_argument, NULL, ARG_UID }, { "allow-null", no_argument, NULL, ARG_ALLOW_NULL }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, {} }; @@ -1052,6 +1059,10 @@ static int parse_argv(int argc, char *argv[]) { arg_allow_null = true; break; + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + case 'q': arg_quiet = true; break; @@ -1425,7 +1436,7 @@ static int run(int argc, char *argv[]) { if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; - _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; /* Invocation as Varlink service */ diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 12b6044d8c..466a5f02df 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -6,6 +6,7 @@ #include <sys/types.h> #include "alloc-util.h" +#include "cryptsetup-util.h" #include "dropin.h" #include "escape.h" #include "fd-util.h" @@ -232,7 +233,7 @@ static int print_dependencies(FILE *f, const char* device_path, const char* time assert(f); assert(device_path); - if (STR_IN_SET(device_path, "-", "none")) + if (!mangle_none(device_path)) /* None, nothing to do */ return 0; @@ -795,7 +796,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } -static int add_crypttab_device(const char *name, const char *device, const char *keyspec, const char *options) { +static int add_crypttab_device(const char *name, const char *device, const char *keyspec, const char *options) { _cleanup_free_ char *keyfile = NULL, *keydev = NULL, *headerdev = NULL, *filtered_header = NULL; crypto_device *d = NULL; char *uuid; diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index e5f21bbce7..04ff160057 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -46,6 +46,7 @@ #include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "verbs.h" /* internal helper */ #define ANY_LUKS "LUKS" @@ -2388,294 +2389,296 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke return r; } -static int run(int argc, char *argv[]) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - const char *verb; + _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; + crypt_status_info status; + uint32_t flags = 0; + unsigned tries; + usec_t until; + PassphraseType passphrase_type = PASSPHRASE_NONE; int r; - log_setup(); + /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] */ - umask(0022); + assert(argc >= 3 && argc <= 5); - r = parse_argv(argc, argv); - if (r <= 0) - return r; + const char *volume = ASSERT_PTR(argv[1]), + *source = ASSERT_PTR(argv[2]), + *key_file = argc >= 4 ? mangle_none(argv[3]) : NULL, + *config = argc >= 5 ? mangle_none(argv[4]) : NULL; - cryptsetup_enable_logging(NULL); + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - if (argc - optind < 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This program requires at least two arguments."); - verb = ASSERT_PTR(argv[optind]); - - if (streq(verb, "attach")) { - _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; - crypt_status_info status; - uint32_t flags = 0; - unsigned tries; - usec_t until; - PassphraseType passphrase_type = PASSPHRASE_NONE; - - /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] */ - - if (argc - optind < 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least two arguments."); - if (argc - optind >= 6) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach does not accept more than four arguments."); - - const char *volume = ASSERT_PTR(argv[optind + 1]), - *source = ASSERT_PTR(argv[optind + 2]), - *key_file = argc - optind >= 4 ? mangle_none(argv[optind + 3]) : NULL, - *config = argc - optind >= 5 ? mangle_none(argv[optind + 4]) : NULL; - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - if (key_file && !path_is_absolute(key_file)) { - log_warning("Password file path '%s' is not absolute. Ignoring.", key_file); - key_file = NULL; - } + if (key_file && !path_is_absolute(key_file)) { + log_warning("Password file path '%s' is not absolute. Ignoring.", key_file); + key_file = NULL; + } - if (config) { - r = parse_crypt_config(config); - if (r < 0) - return r; - } + if (config) { + r = parse_crypt_config(config); + if (r < 0) + return r; + } - log_debug("%s %s ← %s type=%s cipher=%s", __func__, - volume, source, strempty(arg_type), strempty(arg_cipher)); + log_debug("%s %s ← %s type=%s cipher=%s", __func__, + volume, source, strempty(arg_type), strempty(arg_cipher)); - /* A delicious drop of snake oil */ - (void) mlockall(MCL_FUTURE); + /* A delicious drop of snake oil */ + (void) mlockall(MCL_FUTURE); - if (key_file && arg_keyfile_erase) - destroy_key_file = key_file; /* let's get this baby erased when we leave */ + if (key_file && arg_keyfile_erase) + destroy_key_file = key_file; /* let's get this baby erased when we leave */ - if (arg_header) { - if (streq_ptr(arg_type, CRYPT_TCRYPT)){ - log_debug("tcrypt header: %s", arg_header); - r = crypt_init_data_device(&cd, arg_header, source); - } else { - log_debug("LUKS header: %s", arg_header); - r = crypt_init(&cd, arg_header); - } - } else - r = crypt_init(&cd, source); - if (r < 0) - return log_error_errno(r, "crypt_init() failed: %m"); + if (arg_header) { + if (streq_ptr(arg_type, CRYPT_TCRYPT)){ + log_debug("tcrypt header: %s", arg_header); + r = crypt_init_data_device(&cd, arg_header, source); + } else { + log_debug("LUKS header: %s", arg_header); + r = crypt_init(&cd, arg_header); + } + } else + r = crypt_init(&cd, source); + if (r < 0) + return log_error_errno(r, "crypt_init() failed: %m"); - cryptsetup_enable_logging(cd); + cryptsetup_enable_logging(cd); - status = crypt_status(cd, volume); - if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { - log_info("Volume %s already active.", volume); - return 0; - } + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } - flags = determine_flags(); + flags = determine_flags(); - until = usec_add(now(CLOCK_MONOTONIC), arg_timeout); - if (until == USEC_INFINITY) - until = 0; + until = usec_add(now(CLOCK_MONOTONIC), arg_timeout); + if (until == USEC_INFINITY) + until = 0; - if (arg_key_size == 0) - arg_key_size = 256U / 8U; + if (arg_key_size == 0) + arg_key_size = 256U / 8U; - if (key_file) { - struct stat st; + if (key_file) { + struct stat st; - /* Ideally we'd do this on the open fd, but since this is just a - * warning it's OK to do this in two steps. */ - if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) - log_warning("Key file %s is world-readable. This is not a good idea!", key_file); - } + /* Ideally we'd do this on the open fd, but since this is just a warning it's OK to do this + * in two steps. */ + if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) + log_warning("Key file %s is world-readable. This is not a good idea!", key_file); + } - if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) { - r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); + if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) { + r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); /* since cryptsetup 2.7.0 (Jan 2024) */ #if HAVE_CRYPT_SET_KEYRING_TO_LINK - if (arg_link_key_description) { - r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); - if (r < 0) - log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); - } + if (arg_link_key_description) { + r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); + if (r < 0) + log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); + } #endif - if (arg_header) { - r = crypt_set_data_device(cd, source); - if (r < 0) - return log_error_errno(r, "Failed to set LUKS data device %s: %m", source); - } - - /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ - if (!key_file && use_token_plugins()) { - r = crypt_activate_by_token_pin_ask_password( - cd, - volume, - /* type= */ NULL, - until, - /* userdata= */ NULL, - flags, - "Please enter LUKS2 token PIN:", - "luks2-pin", - "cryptsetup.luks2-pin"); - if (r >= 0) { - log_debug("Volume %s activated with a LUKS token.", volume); - return 0; - } + if (arg_header) { + r = crypt_set_data_device(cd, source); + if (r < 0) + return log_error_errno(r, "Failed to set LUKS data device %s: %m", source); + } - log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd)); + /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ + if (!key_file && use_token_plugins()) { + r = crypt_activate_by_token_pin_ask_password( + cd, + volume, + /* type= */ NULL, + until, + /* userdata= */ NULL, + flags, + "Please enter LUKS2 token PIN:", + "luks2-pin", + "cryptsetup.luks2-pin"); + if (r >= 0) { + log_debug("Volume %s activated with a LUKS token.", volume); + return 0; } + + log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd)); } + } /* since cryptsetup 2.3.0 (Feb 2020) */ #ifdef CRYPT_BITLK - if (streq_ptr(arg_type, CRYPT_BITLK)) { - r = crypt_load(cd, CRYPT_BITLK, NULL); + if (streq_ptr(arg_type, CRYPT_BITLK)) { + r = crypt_load(cd, CRYPT_BITLK, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd)); + } +#endif + + bool use_cached_passphrase = true, try_discover_key = !key_file; + const char *discovered_key_fn = strjoina(volume, ".key"); + _cleanup_strv_free_erase_ char **passwords = NULL; + for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { + _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {}; + const struct iovec *key_data = NULL; + TokenType token_type = determine_token_type(); + + log_debug("Beginning attempt %u to unlock.", tries); + + /* When we were able to acquire multiple keys, let's always process them in this order: + * + * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip + * 2. The configured or discovered key, of which both are exclusive and optional + * 3. The empty password, in case arg_try_empty_password is set + * 4. We enquire the user for a password + */ + + if (try_discover_key) { + r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data); if (r < 0) - return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd)); + return r; + if (r > 0) + key_data = &discovered_key_data; } -#endif - bool use_cached_passphrase = true, try_discover_key = !key_file; - const char *discovered_key_fn = strjoina(volume, ".key"); - _cleanup_strv_free_erase_ char **passwords = NULL; - for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { - _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {}; - const struct iovec *key_data = NULL; - TokenType token_type = determine_token_type(); - - log_debug("Beginning attempt %u to unlock.", tries); - - /* When we were able to acquire multiple keys, let's always process them in this order: - * - * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip - * 2. The configured or discovered key, of which both are exclusive and optional - * 3. The empty password, in case arg_try_empty_password is set - * 4. We enquire the user for a password - */ - - if (try_discover_key) { - r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data); - if (r < 0) - return r; - if (r > 0) - key_data = &discovered_key_data; - } + if (token_type < 0 && !key_file && !key_data && !passwords) { + + /* If we have nothing to try anymore, then acquire a new password */ - if (token_type < 0 && !key_file && !key_data && !passwords) { - - /* If we have nothing to try anymore, then acquire a new password */ - - if (arg_try_empty_password) { - /* Hmm, let's try an empty password now, but only once */ - arg_try_empty_password = false; - key_data = &iovec_empty; - } else { - /* Ask the user for a passphrase or recovery key only as last resort, if we have - * nothing else to check for */ - if (passphrase_type == PASSPHRASE_NONE) { - passphrase_type = check_registered_passwords(cd); - if (passphrase_type == PASSPHRASE_NONE) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); - } - - r = get_password( - volume, - source, - until, - /* ignore_cached= */ !use_cached_passphrase || arg_verify, - passphrase_type, - &passwords); - use_cached_passphrase = false; - if (r == -EAGAIN) - continue; - if (r < 0) - return r; + if (arg_try_empty_password) { + /* Hmm, let's try an empty password now, but only once */ + arg_try_empty_password = false; + key_data = &iovec_empty; + } else { + /* Ask the user for a passphrase or recovery key only as last resort, if we + * have nothing else to check for */ + if (passphrase_type == PASSPHRASE_NONE) { + passphrase_type = check_registered_passwords(cd); + if (passphrase_type == PASSPHRASE_NONE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); } - } - if (streq_ptr(arg_type, CRYPT_TCRYPT)) - r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags); - else - r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until); - if (r >= 0) - break; - if (r != -EAGAIN) - return r; + r = get_password( + volume, + source, + until, + /* ignore_cached= */ !use_cached_passphrase || arg_verify, + passphrase_type, + &passwords); + use_cached_passphrase = false; + if (r == -EAGAIN) + continue; + if (r < 0) + return r; + } + } - /* Key not correct? Let's try again, but let's invalidate one of the passed fields, - * so that we fall back to the next best thing. */ + if (streq_ptr(arg_type, CRYPT_TCRYPT)) + r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags); + else + r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until); + if (r >= 0) + break; + if (r != -EAGAIN) + return r; - if (token_type == TOKEN_TPM2) { - arg_tpm2_device = mfree(arg_tpm2_device); - arg_tpm2_device_auto = false; - continue; - } + /* Key not correct? Let's try again, but let's invalidate one of the passed fields, so that + * we fall back to the next best thing. */ - if (token_type == TOKEN_FIDO2) { - arg_fido2_device = mfree(arg_fido2_device); - arg_fido2_device_auto = false; - continue; - } + if (token_type == TOKEN_TPM2) { + arg_tpm2_device = mfree(arg_tpm2_device); + arg_tpm2_device_auto = false; + continue; + } - if (token_type == TOKEN_PKCS11) { - arg_pkcs11_uri = mfree(arg_pkcs11_uri); - arg_pkcs11_uri_auto = false; - continue; - } + if (token_type == TOKEN_FIDO2) { + arg_fido2_device = mfree(arg_fido2_device); + arg_fido2_device_auto = false; + continue; + } - if (try_discover_key) { - try_discover_key = false; - continue; - } + if (token_type == TOKEN_PKCS11) { + arg_pkcs11_uri = mfree(arg_pkcs11_uri); + arg_pkcs11_uri_auto = false; + continue; + } - if (key_file) { - key_file = NULL; - continue; - } + if (try_discover_key) { + try_discover_key = false; + continue; + } - if (passwords) { - passwords = strv_free_erase(passwords); - continue; - } + if (key_file) { + key_file = NULL; + continue; + } - log_debug("Prepared for next attempt to unlock."); + if (passwords) { + passwords = strv_free_erase(passwords); + continue; } - if (arg_tries != 0 && tries >= arg_tries) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up."); + log_debug("Prepared for next attempt to unlock."); + } - } else if (streq(verb, "detach")) { - const char *volume = ASSERT_PTR(argv[optind + 1]); + if (arg_tries != 0 && tries >= arg_tries) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up."); - if (argc - optind >= 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach does not accept more than one argument."); + return 0; +} - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); +static int verb_detach(int argc, char *argv[], void *userdata) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + const char *volume = ASSERT_PTR(argv[1]); + int r; - r = crypt_init_by_name(&cd, volume); - if (r == -ENODEV) { - log_info("Volume %s already inactive.", volume); - return 0; - } - if (r < 0) - return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); + assert(argc == 2); - cryptsetup_enable_logging(cd); + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_deactivate(cd, volume); - if (r < 0) - return log_error_errno(r, "Failed to deactivate '%s': %m", volume); + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s already inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate '%s': %m", volume); return 0; } +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + cryptsetup_enable_logging(NULL); + + static const Verb verbs[] = { + { "attach", 3, 5, 0, verb_attach }, + { "detach", 2, 2, 0, verb_detach }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + DEFINE_MAIN_FUNCTION(run); diff --git a/src/delta/delta.c b/src/delta/delta.c index 3433250549..62f4be578f 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -184,10 +184,15 @@ static int found_override(const char *top, const char *bottom) { return r; } +DEFINE_PRIVATE_HASH_OPS_FULL( + drop_hash_ops, + char, string_hash_func, string_compare_func, free, + OrderedHashmap, ordered_hashmap_free); + static int enumerate_dir_d( - OrderedHashmap *top, - OrderedHashmap *bottom, - OrderedHashmap *drops, + OrderedHashmap **top, + OrderedHashmap **bottom, + OrderedHashmap **drops, const char *toppath, const char *drop) { _cleanup_free_ char *unit = NULL; @@ -221,7 +226,6 @@ static int enumerate_dir_d( STRV_FOREACH(file, list) { OrderedHashmap *h; - int k; char *p; char *d; @@ -234,31 +238,35 @@ static int enumerate_dir_d( d = p + strlen(toppath) + 1; log_debug("Adding at top: %s %s %s", d, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), p); - k = ordered_hashmap_put(top, d, p); - if (k >= 0) { + r = ordered_hashmap_ensure_put(top, &string_hash_ops_value_free, d, p); + if (r >= 0) { p = strdup(p); if (!p) return -ENOMEM; d = p + strlen(toppath) + 1; - } else if (k != -EEXIST) { + } else if (r != -EEXIST) { free(p); - return k; + return r; } log_debug("Adding at bottom: %s %s %s", d, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), p); - free(ordered_hashmap_remove(bottom, d)); - k = ordered_hashmap_put(bottom, d, p); - if (k < 0) { + free(ordered_hashmap_remove(*bottom, d)); + r = ordered_hashmap_ensure_put(bottom, &string_hash_ops_value_free, d, p); + if (r < 0) { free(p); - return k; + return r; } - h = ordered_hashmap_get(drops, unit); + h = ordered_hashmap_get(*drops, unit); if (!h) { - h = ordered_hashmap_new(&string_hash_ops); + h = ordered_hashmap_new(&string_hash_ops_value_free); if (!h) return -ENOMEM; - ordered_hashmap_put(drops, unit, h); + r = ordered_hashmap_ensure_put(drops, &drop_hash_ops, unit, h); + if (r < 0) { + ordered_hashmap_free(h); + return r; + } unit = strdup(unit); if (!unit) return -ENOMEM; @@ -270,20 +278,20 @@ static int enumerate_dir_d( log_debug("Adding to drops: %s %s %s %s %s", unit, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), basename(p), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), p); - k = ordered_hashmap_put(h, basename(p), p); - if (k < 0) { + r = ordered_hashmap_put(h, basename(p), p); + if (r < 0) { free(p); - if (k != -EEXIST) - return k; + if (r != -EEXIST) + return r; } } return 0; } static int enumerate_dir( - OrderedHashmap *top, - OrderedHashmap *bottom, - OrderedHashmap *drops, + OrderedHashmap **top, + OrderedHashmap **bottom, + OrderedHashmap **drops, const char *path, bool dropins) { _cleanup_closedir_ DIR *d = NULL; @@ -346,7 +354,7 @@ static int enumerate_dir( return -ENOMEM; log_debug("Adding at top: %s %s %s", basename(p), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), p); - r = ordered_hashmap_put(top, basename(p), p); + r = ordered_hashmap_ensure_put(top, &string_hash_ops_value_free, basename(p), p); if (r >= 0) { p = strdup(p); if (!p) @@ -355,8 +363,8 @@ static int enumerate_dir( return r; log_debug("Adding at bottom: %s %s %s", basename(p), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), p); - free(ordered_hashmap_remove(bottom, basename(p))); - r = ordered_hashmap_put(bottom, basename(p), p); + free(ordered_hashmap_remove(*bottom, basename(p))); + r = ordered_hashmap_ensure_put(bottom, &string_hash_ops_value_free, basename(p), p); if (r < 0) return r; p = NULL; @@ -366,39 +374,27 @@ static int enumerate_dir( } static int process_suffix(const char *suffix, const char *onlyprefix) { - char *f, *key; - OrderedHashmap *top, *bottom, *drops, *h; - int r = 0, k, n_found = 0; - bool dropins; + int r, ret = 0; assert(suffix); assert(!startswith(suffix, "/")); assert(!strstr(suffix, "//")); - dropins = nulstr_contains(have_dropins, suffix); - - top = ordered_hashmap_new(&string_hash_ops); - bottom = ordered_hashmap_new(&string_hash_ops); - drops = ordered_hashmap_new(&string_hash_ops); - if (!top || !bottom || !drops) { - r = -ENOMEM; - goto finish; - } + bool dropins = nulstr_contains(have_dropins, suffix); + _cleanup_ordered_hashmap_free_ OrderedHashmap *top = NULL, *bottom = NULL, *drops = NULL; NULSTR_FOREACH(p, prefixes) { _cleanup_free_ char *t = NULL; t = path_join(p, suffix); - if (!t) { - r = -ENOMEM; - goto finish; - } + if (!t) + return -ENOMEM; - k = enumerate_dir(top, bottom, drops, t, dropins); - if (r == 0) - r = k; + RET_GATHER(ret, enumerate_dir(&top, &bottom, &drops, t, dropins)); } + int n_found = 0; + char *f, *key; ORDERED_HASHMAP_FOREACH_KEY(f, key, top) { char *o; @@ -409,33 +405,22 @@ static int process_suffix(const char *suffix, const char *onlyprefix) { if (path_equal(o, f)) { notify_override_unchanged(f); } else { - k = found_override(f, o); - if (k < 0) - r = k; + r = found_override(f, o); + if (r < 0) + RET_GATHER(ret, r); else - n_found += k; + n_found += r; } } - h = ordered_hashmap_get(drops, key); + OrderedHashmap *h = ordered_hashmap_get(drops, key); if (h) ORDERED_HASHMAP_FOREACH(o, h) if (!onlyprefix || startswith(o, onlyprefix)) n_found += notify_override_extended(f, o); } -finish: - ordered_hashmap_free_free(top); - ordered_hashmap_free_free(bottom); - - ORDERED_HASHMAP_FOREACH_KEY(h, key, drops) { - ordered_hashmap_free_free(ordered_hashmap_remove(drops, key)); - ordered_hashmap_remove(drops, key); - free(key); - } - ordered_hashmap_free(drops); - - return r < 0 ? r : n_found; + return ret < 0 ? ret : n_found; } static int process_suffixes(const char *onlyprefix) { diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 33e651c6c1..956cac6708 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1762,7 +1762,7 @@ static int action_umount(const char *path) { if (fd < 0) return log_error_errno(fd, "Failed to resolve path '%s': %m", path); - r = fd_is_mount_point(fd, NULL, 0); + r = is_mount_point_at(fd, NULL, 0); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical); if (r < 0) diff --git a/src/fuzz/fuzz-catalog.c b/src/fuzz/fuzz-catalog.c index f9561f24c4..cbe8463a89 100644 --- a/src/fuzz/fuzz-catalog.c +++ b/src/fuzz/fuzz-catalog.c @@ -9,17 +9,15 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_(unlink_tempfilep) char name[] = "/tmp/fuzz-catalog.XXXXXX"; _cleanup_close_ int fd = -EBADF; - _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; fuzz_setup_logging(); - assert_se(h = ordered_hashmap_new(&catalog_hash_ops)); - fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, data, size) == (ssize_t) size); - (void) catalog_import_file(h, name); + (void) catalog_import_file(&h, name); return 0; } diff --git a/src/getty-generator/getty-generator.c b/src/getty-generator/getty-generator.c index 9ad5597b05..e71b5ec101 100644 --- a/src/getty-generator/getty-generator.c +++ b/src/getty-generator/getty-generator.c @@ -25,72 +25,53 @@ static const char *arg_dest = NULL; static bool arg_enabled = true; -static int add_symlink(const char *fservice, const char *tservice) { - const char *from, *to; - - assert(fservice); - assert(tservice); - - from = strjoina(SYSTEM_DATA_UNIT_DIR "/", fservice); - to = strjoina(arg_dest, "/getty.target.wants/", tservice); - - (void) mkdir_parents_label(to, 0755); - - if (symlink(from, to) < 0) { - /* In case console=hvc0 is passed this will very likely result in EEXIST */ - if (errno == EEXIST) - return 0; - - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - } - - return 0; -} - static int add_serial_getty(const char *tty) { - _cleanup_free_ char *n = NULL; + _cleanup_free_ char *instance = NULL; int r; assert(tty); + tty = skip_dev_prefix(tty); + log_debug("Automatically adding serial getty for /dev/%s.", tty); - r = unit_name_from_path_instance("serial-getty", tty, ".service", &n); + r = unit_name_path_escape(tty, &instance); if (r < 0) - return log_error_errno(r, "Failed to generate service name: %m"); + return log_error_errno(r, "Failed to escape tty path: %m"); - return add_symlink("serial-getty@.service", n); + return generator_add_symlink_full(arg_dest, + "getty.target", "wants", + SYSTEM_DATA_UNIT_DIR "/serial-getty@.service", instance); } static int add_container_getty(const char *tty) { - _cleanup_free_ char *n = NULL; + _cleanup_free_ char *instance = NULL; int r; assert(tty); + assert(!path_startswith(tty, "/dev/")); log_debug("Automatically adding container getty for /dev/pts/%s.", tty); - r = unit_name_from_path_instance("container-getty", tty, ".service", &n); + r = unit_name_path_escape(tty, &instance); if (r < 0) - return log_error_errno(r, "Failed to generate service name: %m"); + return log_error_errno(r, "Failed to escape tty path: %m"); - return add_symlink("container-getty@.service", n); + return generator_add_symlink_full(arg_dest, + "getty.target", "wants", + SYSTEM_DATA_UNIT_DIR "/container-getty@.service", instance); } -static int verify_tty(const char *name) { +static int verify_tty(const char *path) { _cleanup_close_ int fd = -EBADF; - const char *p; - /* Some TTYs are weird and have been enumerated but don't work - * when you try to use them, such as classic ttyS0 and - * friends. Let's check that and open the device and run - * isatty() on it. */ + assert(path); - p = strjoina("/dev/", name); + /* Some TTYs are weird and have been enumerated but don't work when you try to use them, such as + * classic ttyS0 and friends. Let's check that and open the device and run isatty() on it. */ - /* O_NONBLOCK is essential here, to make sure we don't wait - * for DCD */ - fd = open(p, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW); + /* O_NONBLOCK is essential here, to make sure we don't wait for DCD */ + fd = open(path, O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW); if (fd < 0) return -errno; @@ -101,18 +82,17 @@ static int verify_tty(const char *name) { } static int run_container(void) { - _cleanup_free_ char *container_ttys = NULL; int r; log_debug("Automatically adding console shell."); - r = add_symlink("console-getty.service", "console-getty.service"); + r = generator_add_symlink(arg_dest, "getty.target", "wants", SYSTEM_DATA_UNIT_DIR "/console-getty.service"); if (r < 0) return r; /* When $container_ttys is set for PID 1, spawn gettys on all ptys named therein. * Note that despite the variable name we only support ptys here. */ - + _cleanup_free_ char *container_ttys = NULL; (void) getenv_for_pid(1, "container_ttys", &container_ttys); for (const char *p = container_ttys;;) { @@ -124,10 +104,8 @@ static int run_container(void) { if (r == 0) return 0; - const char *tty = word; - /* First strip off /dev/ if it is specified */ - tty = path_startswith(tty, "/dev/") ?: tty; + const char *tty = skip_dev_prefix(word); /* Then, make sure it's actually a pty */ tty = path_startswith(tty, "pts/"); @@ -245,30 +223,24 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) return run_container(); /* Automatically add in a serial getty on all active kernel consoles */ - _cleanup_free_ char *active = NULL; - (void) read_one_line_file("/sys/class/tty/console/active", &active); - for (const char *p = active;;) { - _cleanup_free_ char *tty = NULL; - - r = extract_first_word(&p, &tty, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to parse /sys/class/tty/console/active: %m"); - if (r == 0) - break; - - /* We assume that gettys on virtual terminals are started via manual configuration and do - * this magic only for non-VC terminals. */ - - if (isempty(tty) || tty_is_vc(tty)) - continue; + _cleanup_strv_free_ char **consoles = NULL; + r = get_kernel_consoles(&consoles); + if (r < 0) + log_warning_errno(r, "Failed to get active kernel consoles, ignoring: %m"); + else if (r > 0) + STRV_FOREACH(i, consoles) { + /* We assume that gettys on virtual terminals are started via manual configuration + * and do this magic only for non-VC terminals. */ + if (tty_is_vc(*i)) + continue; - if (verify_tty(tty) < 0) - continue; + if (verify_tty(*i) < 0) + continue; - r = add_serial_getty(tty); - if (r < 0) - return r; - } + r = add_serial_getty(*i); + if (r < 0) + return r; + } /* Automatically add in a serial getty on the first virtualizer console */ FOREACH_STRING(j, diff --git a/src/home/homectl.c b/src/home/homectl.c index 104c35454b..825db583b0 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -750,17 +750,9 @@ static int inspect_home(int argc, char *argv[], void *userdata) { uid_t uid; r = parse_uid(*i, &uid); - if (r < 0) { - if (!valid_user_group_name(*i, 0)) { - log_error("Invalid user name '%s'.", *i); - if (ret == 0) - ret = -EINVAL; - - continue; - } - + if (r < 0) r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", *i); - } else + else r = bus_call_method(bus, bus_mgr, "GetUserRecordByUID", &error, &reply, "u", (uint32_t) uid); if (r < 0) { log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r)); @@ -2764,6 +2756,7 @@ static int help(int argc, char *argv[], void *userdata) { "\n%4$sGeneral User Record Properties:%5$s\n" " -c --real-name=REALNAME Real name for user\n" " --realm=REALM Realm to create user in\n" + " --alias=ALIAS Define alias usernames for this account\n" " --email-address=EMAIL Email address for user\n" " --location=LOCATION Set location of user on earth\n" " --icon-name=NAME Icon name for user\n" @@ -2774,11 +2767,14 @@ static int help(int argc, char *argv[], void *userdata) { " Bounding POSIX capability set\n" " --capability-ambient-set=CAPS\n" " Ambient POSIX capability set\n" + " --access-mode=MODE User home directory access mode\n" + " --umask=MODE Umask for user when logging in\n" " --skel=PATH Skeleton directory to use\n" " --shell=PATH Shell for account\n" " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" " --timezone=TIMEZONE Set a time-zone\n" " --language=LOCALE Set preferred languages\n" + "\n%4$sAuthentication User Record Properties:%5$s\n" " --ssh-authorized-keys=KEYS\n" " Specify SSH public keys\n" " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" @@ -2827,8 +2823,6 @@ static int help(int argc, char *argv[], void *userdata) { " How much time to block password after expiry\n" "\n%4$sResource Management User Record Properties:%5$s\n" " --disk-size=BYTES Size to assign the user on disk\n" - " --access-mode=MODE User home directory access mode\n" - " --umask=MODE Umask for user when logging in\n" " --nice=NICE Nice level for user\n" " --rlimit=LIMIT=VALUE[:VALUE]\n" " Set resource limits\n" @@ -2837,6 +2831,9 @@ static int help(int argc, char *argv[], void *userdata) { " --memory-max=BYTES Set maximum memory limit\n" " --cpu-weight=WEIGHT Set CPU weight\n" " --io-weight=WEIGHT Set IO weight\n" + " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n" + " --dev-shm-limit=BYTES|PERCENT\n" + " Set limit on /dev/shm/\n" "\n%4$sStorage User Record Properties:%5$s\n" " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" " subvolume, cifs)\n" @@ -2908,6 +2905,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_ASK_PASSWORD, ARG_OFFLINE, ARG_REALM, + ARG_ALIAS, ARG_EMAIL_ADDRESS, ARG_DISK_SIZE, ARG_ACCESS_MODE, @@ -2984,6 +2982,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_PROMPT_NEW_USER, ARG_AVATAR, ARG_LOGIN_BACKGROUND, + ARG_TMP_LIMIT, + ARG_DEV_SHM_LIMIT, }; static const struct option options[] = { @@ -2999,6 +2999,7 @@ static int parse_argv(int argc, char *argv[]) { { "real-name", required_argument, NULL, 'c' }, { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ { "realm", required_argument, NULL, ARG_REALM }, + { "alias", required_argument, NULL, ARG_ALIAS }, { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS }, { "location", required_argument, NULL, ARG_LOCATION }, { "password-hint", required_argument, NULL, ARG_PASSWORD_HINT }, @@ -3083,6 +3084,8 @@ static int parse_argv(int argc, char *argv[]) { { "blob", required_argument, NULL, 'b' }, { "avatar", required_argument, NULL, ARG_AVATAR }, { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, + { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT }, + { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT }, {} }; @@ -3154,6 +3157,53 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_ALIAS: { + if (isempty(optarg)) { + r = drop_from_identity("aliases"); + if (r < 0) + return r; + break; + } + + for (const char *p = optarg;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse alias list: %m"); + if (r == 0) + break; + + if (!valid_user_group_name(word, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid alias user name %s.", word); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *av = + sd_json_variant_ref(sd_json_variant_by_key(arg_identity_extra, "aliases")); + + _cleanup_strv_free_ char **list = NULL; + r = sd_json_variant_strv(av, &list); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); + + r = strv_extend(&list, word); + if (r < 0) + return log_oom(); + + strv_sort_uniq(list); + + av = sd_json_variant_unref(av); + r = sd_json_variant_new_array_strv(&av, list); + if (r < 0) + return log_error_errno(r, "Failed to create alias list JSON: %m"); + + r = sd_json_variant_set_field(&arg_identity_extra, "aliases", av); + if (r < 0) + return log_error_errno(r, "Failed to update alias list: %m"); + } + + break; + } + case 'd': { _cleanup_free_ char *hd = NULL; @@ -4469,6 +4519,56 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_TMP_LIMIT: + case ARG_DEV_SHM_LIMIT: { + const char *field = + c == ARG_TMP_LIMIT ? "tmpLimit" : + c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : NULL; + const char *field_scale = + c == ARG_TMP_LIMIT ? "tmpLimitScale" : + c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : NULL; + + assert(field); + assert(field_scale); + + if (isempty(optarg)) { + r = drop_from_identity(field); + if (r < 0) + return r; + r = drop_from_identity(field_scale); + if (r < 0) + return r; + break; + } + + r = parse_permyriad(optarg); + if (r < 0) { + uint64_t u; + + r = parse_size(optarg, 1024, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, optarg); + + r = sd_json_variant_set_field_unsigned(&arg_identity_extra, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + + r = drop_from_identity(field_scale); + if (r < 0) + return r; + } else { + r = sd_json_variant_set_field_unsigned(&arg_identity_extra, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field_scale); + + r = drop_from_identity(field); + if (r < 0) + return r; + } + + break; + } + case '?': return -EINVAL; diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index 80e2773447..a3e6a32162 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -799,8 +799,11 @@ static int bus_home_object_find( if (parse_uid(e, &uid) >= 0) h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); - else - h = hashmap_get(m->homes_by_name, e); + else { + r = manager_get_home_by_name(m, e, &h); + if (r < 0) + return r; + } if (!h) return 0; diff --git a/src/home/homed-home.c b/src/home/homed-home.c index a6f99713dd..96477b3061 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -101,6 +101,7 @@ static int suitable_home_record(UserRecord *hr) { int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { _cleanup_(home_freep) Home *home = NULL; _cleanup_free_ char *nm = NULL, *ns = NULL, *blob = NULL; + _cleanup_strv_free_ char **aliases = NULL; int r; assert(m); @@ -113,19 +114,29 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { if (hashmap_contains(m->homes_by_name, hr->user_name)) return -EBUSY; + STRV_FOREACH(a, hr->aliases) + if (hashmap_contains(m->homes_by_name, *a)) + return -EBUSY; + if (hashmap_contains(m->homes_by_uid, UID_TO_PTR(hr->uid))) return -EBUSY; if (sysfs && hashmap_contains(m->homes_by_sysfs, sysfs)) return -EBUSY; - if (hashmap_size(m->homes_by_name) >= HOME_USERS_MAX) + if (hashmap_size(m->homes_by_uid) >= HOME_USERS_MAX) return -EUSERS; nm = strdup(hr->user_name); if (!nm) return -ENOMEM; + if (!strv_isempty(hr->aliases)) { + aliases = strv_copy(hr->aliases); + if (!aliases) + return -ENOMEM; + } + if (sysfs) { ns = strdup(sysfs); if (!ns) @@ -139,6 +150,7 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { *home = (Home) { .manager = m, .user_name = TAKE_PTR(nm), + .aliases = TAKE_PTR(aliases), .uid = hr->uid, .state = _HOME_STATE_INVALID, .worker_stdout_fd = -EBADF, @@ -152,6 +164,12 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { if (r < 0) return r; + STRV_FOREACH(a, home->aliases) { + r = hashmap_put(m->homes_by_name, *a, home); + if (r < 0) + return r; + } + r = hashmap_put(m->homes_by_uid, UID_TO_PTR(home->uid), home); if (r < 0) return r; @@ -197,6 +215,9 @@ Home *home_free(Home *h) { if (h->user_name) (void) hashmap_remove_value(h->manager->homes_by_name, h->user_name, h); + STRV_FOREACH(a, h->aliases) + (void) hashmap_remove_value(h->manager->homes_by_name, *a, h); + if (uid_is_valid(h->uid)) (void) hashmap_remove_value(h->manager->homes_by_uid, UID_TO_PTR(h->uid), h); @@ -218,6 +239,7 @@ Home *home_free(Home *h) { h->worker_event_source = sd_event_source_disable_unref(h->worker_event_source); safe_close(h->worker_stdout_fd); free(h->user_name); + strv_free(h->aliases); free(h->sysfs); h->ref_event_source_please_suspend = sd_event_source_disable_unref(h->ref_event_source_please_suspend); @@ -257,6 +279,10 @@ int home_set_record(Home *h, UserRecord *hr) { if (!user_record_compatible(h->record, hr)) return -EREMCHG; + /* For now do not allow changing list of aliases */ + if (!strv_equal_ignore_order(h->aliases, hr->aliases)) + return -EREMCHG; + if (!FLAGS_SET(hr->mask, USER_RECORD_REGULAR) || FLAGS_SET(hr->mask, USER_RECORD_SECRET)) return -EINVAL; diff --git a/src/home/homed-home.h b/src/home/homed-home.h index 8c92e39fe5..93689563d3 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -109,7 +109,12 @@ static inline bool HOME_STATE_MAY_RETRY_DEACTIVATE(HomeState state) { struct Home { Manager *manager; + + /* The fields this record can be looked up by. This is kinda redundant, as the same information is + * available in the .record field, but we keep separate copies of these keys to make memory + * management for the hashmaps easier. */ char *user_name; + char **aliases; uid_t uid; char *sysfs; /* When found via plugged in device, the sysfs path to it */ diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index 08c917aee2..a08cc3803c 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -37,7 +37,7 @@ static int property_get_auto_login( if (r < 0) return r; - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { _cleanup_strv_free_ char **seats = NULL; _cleanup_free_ char *home_path = NULL; @@ -97,11 +97,9 @@ static int lookup_user_name( return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "Client's UID " UID_FMT " not managed.", uid); } else { - - if (!valid_user_group_name(user_name, 0)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); - - h = hashmap_get(m->homes_by_name, user_name); + r = manager_get_home_by_name(m, user_name, &h); + if (r < 0) + return r; if (!h) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); } @@ -342,6 +340,31 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu return generic_home_method(userdata, message, bus_home_method_deactivate, error); } +static int check_for_conflicts(Manager *m, const char *name, sd_bus_error *error) { + int r; + + assert(m); + assert(name); + + Home *other = hashmap_get(m->homes_by_name, name); + if (other) + return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", name); + + r = getpwnam_malloc(name, /* ret= */ NULL); + if (r >= 0) + return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", name); + if (r != -ESRCH) + return r; + + r = getgrnam_malloc(name, /* ret= */ NULL); + if (r >= 0) + return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", name); + if (r != -ESRCH) + return r; + + return 0; +} + static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL; bool signed_locally; @@ -356,21 +379,32 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs if (r < 0) return r; - other = hashmap_get(m->homes_by_name, hr->user_name); - if (other) - return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name); - - r = getpwnam_malloc(hr->user_name, /* ret= */ NULL); - if (r >= 0) - return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name); - if (r != -ESRCH) + r = check_for_conflicts(m, hr->user_name, error); + if (r < 0) return r; - r = getgrnam_malloc(hr->user_name, /* ret= */ NULL); - if (r >= 0) - return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name); - if (r != -ESRCH) - return r; + if (hr->realm) { + r = check_for_conflicts(m, user_record_user_name_and_realm(hr), error); + if (r < 0) + return r; + } + + STRV_FOREACH(a, hr->aliases) { + r = check_for_conflicts(m, *a, error); + if (r < 0) + return r; + + if (hr->realm) { + _cleanup_free_ char *alias_with_realm = NULL; + alias_with_realm = strjoin(*a, "@", hr->realm); + if (!alias_with_realm) + return -ENOMEM; + + r = check_for_conflicts(m, alias_with_realm, error); + if (r < 0) + return r; + } + } if (blobs) { const char *failed = NULL; @@ -637,7 +671,7 @@ static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus * for every suitable home we have and only when all of them completed we send a reply indicating * completion. */ - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { if (!home_shall_suspend(h)) continue; @@ -676,7 +710,7 @@ static int method_deactivate_all_homes(sd_bus_message *message, void *userdata, * systemd-homed.service itself since we want to allow restarting of it without tearing down all home * directories. */ - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { if (!o) { o = operation_new(OPERATION_DEACTIVATE_ALL, message); diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index bb4ad1f99f..447d8949cc 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -54,6 +54,7 @@ #include "user-record-util.h" #include "user-record.h" #include "user-util.h" +#include "varlink-io.systemd.service.h" #include "varlink-io.systemd.UserDatabase.h" #include "varlink-util.h" @@ -75,7 +76,6 @@ static bool uid_is_home(uid_t uid) { #define UID_CLAMP_INTO_HOME_RANGE(rnd) (((uid_t) (rnd) % (HOME_UID_MAX - HOME_UID_MIN + 1)) + HOME_UID_MIN) DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_uid_hash_ops, void, trivial_hash_func, trivial_compare_func, Home, home_free); -DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_name_hash_ops, char, string_hash_func, string_compare_func, Home, home_free); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_worker_pid_hash_ops, void, trivial_hash_func, trivial_compare_func, Home, home_free); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_sysfs_hash_ops, char, path_hash_func, path_compare, Home, home_free); @@ -191,7 +191,7 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event log_debug("%s has been moved away, revalidating.", j); h = hashmap_get(m->homes_by_name, n); - if (h) { + if (h && streq(h->user_name, n)) { manager_revalidate_image(m, h); (void) bus_manager_emit_auto_login_changed(m); } @@ -242,7 +242,7 @@ int manager_new(Manager **ret) { if (!m->homes_by_uid) return -ENOMEM; - m->homes_by_name = hashmap_new(&homes_by_name_hash_ops); + m->homes_by_name = hashmap_new(&string_hash_ops); if (!m->homes_by_name) return -ENOMEM; @@ -697,6 +697,11 @@ static int manager_add_home_by_image( if (h) { bool same; + if (!streq(h->user_name, user_name)) { + log_debug("Found an image for user %s which already is an alias for another user, skipping.", user_name); + return 0; /* Ignore images that would synthesize a user that conflicts with an alias of another user */ + } + if (h->state != HOME_UNFIXATED) { log_debug("Found an image for user %s which already has a record, skipping.", user_name); return 0; /* ignore images that synthesize a user we already have a record for */ @@ -1012,7 +1017,10 @@ static int manager_bind_varlink(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to allocate varlink server: %m"); - r = sd_varlink_server_add_interface(m->varlink_server, &vl_interface_io_systemd_UserDatabase); + r = sd_varlink_server_add_interface_many( + m->varlink_server, + &vl_interface_io_systemd_UserDatabase, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); @@ -1020,7 +1028,10 @@ static int manager_bind_varlink(Manager *m) { m->varlink_server, "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record, "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record, - "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships); + "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); @@ -1714,7 +1725,7 @@ int manager_gc_images(Manager *m) { } else { /* Gc all */ - HASHMAP_FOREACH(h, m->homes_by_name) + HASHMAP_FOREACH(h, m->homes_by_uid) manager_revalidate_image(m, h); } @@ -1734,12 +1745,14 @@ static int manager_gc_blob(Manager *m) { return log_error_errno(errno, "Failed to open %s: %m", home_system_blob_dir()); } - FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m")) - if (!hashmap_contains(m->homes_by_name, de->d_name)) { + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m")) { + Home *found = hashmap_get(m->homes_by_name, de->d_name); + if (!found || !streq(found->user_name, de->d_name)) { r = rm_rf_at(dirfd(d), de->d_name, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); if (r < 0) log_warning_errno(r, "Failed to delete blob dir for missing user '%s', ignoring: %m", de->d_name); } + } return 0; } @@ -1834,7 +1847,7 @@ static bool manager_shall_rebalance(Manager *m) { if (IN_SET(m->rebalance_state, REBALANCE_PENDING, REBALANCE_SHRINKING, REBALANCE_GROWING)) return true; - HASHMAP_FOREACH(h, m->homes_by_name) + HASHMAP_FOREACH(h, m->homes_by_uid) if (home_shall_rebalance(h)) return true; @@ -1880,7 +1893,7 @@ static int manager_rebalance_calculate(Manager *m) { * (home dirs get 100 by default, i.e. 5x more). This weight * is not configurable, the per-home weights are. */ - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { statfs_f_type_t fstype; h->rebalance_pending = false; /* First, reset the flag, we only want it to be true for the * homes that qualify for rebalancing */ @@ -2017,7 +2030,7 @@ static int manager_rebalance_apply(Manager *m) { assert(m); - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; if (!h->rebalance_pending) @@ -2258,3 +2271,29 @@ int manager_reschedule_rebalance(Manager *m) { return 1; } + +int manager_get_home_by_name(Manager *m, const char *user_name, Home **ret) { + assert(m); + assert(user_name); + + Home *h = hashmap_get(m->homes_by_name, user_name); + if (!h) { + /* Also search by username and realm. For that simply chop off realm, then look for the home, and verify it afterwards. */ + const char *realm = strrchr(user_name, '@'); + if (realm) { + _cleanup_free_ char *prefix = strndup(user_name, realm - user_name); + if (!prefix) + return -ENOMEM; + + Home *j; + j = hashmap_get(m->homes_by_name, prefix); + if (j && user_record_matches_user_name(j->record, user_name)) + h = j; + } + } + + if (ret) + *ret = h; + + return !!h; +} diff --git a/src/home/homed-manager.h b/src/home/homed-manager.h index eadb0b155d..9fea621031 100644 --- a/src/home/homed-manager.h +++ b/src/home/homed-manager.h @@ -91,3 +91,5 @@ int manager_acquire_key_pair(Manager *m); int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus_error *error); int bus_manager_emit_auto_login_changed(Manager *m); + +int manager_get_home_by_name(Manager *m, const char *user_name, Home **ret); diff --git a/src/home/homed-operation.h b/src/home/homed-operation.h index af165bb4a5..e190e8e970 100644 --- a/src/home/homed-operation.h +++ b/src/home/homed-operation.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include <sd-bus.h> +#include "sd-bus.h" #include "user-record.h" diff --git a/src/home/homed-varlink.c b/src/home/homed-varlink.c index f6dd27594f..ef30ea7eaf 100644 --- a/src/home/homed-varlink.c +++ b/src/home/homed-varlink.c @@ -62,7 +62,7 @@ static bool home_user_match_lookup_parameters(LookupParameters *p, Home *h) { assert(p); assert(h); - if (p->user_name && !streq(p->user_name, h->user_name)) + if (p->user_name && !user_record_matches_user_name(h->record, p->user_name)) return false; if (uid_is_valid(p->uid) && h->uid != p->uid) @@ -100,15 +100,17 @@ int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_ if (uid_is_valid(p.uid)) h = hashmap_get(m->homes_by_uid, UID_TO_PTR(p.uid)); - else if (p.user_name) - h = hashmap_get(m->homes_by_name, p.user_name); - else { + else if (p.user_name) { + r = manager_get_home_by_name(m, p.user_name, &h); + if (r < 0) + return r; + } else { /* If neither UID nor name was specified, then dump all homes. Do so with varlink_notify() * for all entries but the last, so that clients can stream the results, and easily process * them piecemeal. */ - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { if (!home_user_match_lookup_parameters(&p, h)) continue; @@ -175,7 +177,7 @@ static bool home_group_match_lookup_parameters(LookupParameters *p, Home *h) { assert(p); assert(h); - if (p->group_name && !streq(h->user_name, p->group_name)) + if (p->group_name && !user_record_matches_user_name(h->record, p->group_name)) return false; if (gid_is_valid(p->gid) && h->uid != (uid_t) p->gid) @@ -212,11 +214,13 @@ int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd if (gid_is_valid(p.gid)) h = hashmap_get(m->homes_by_uid, UID_TO_PTR((uid_t) p.gid)); - else if (p.group_name) - h = hashmap_get(m->homes_by_name, p.group_name); - else { + else if (p.group_name) { + r = manager_get_home_by_name(m, p.group_name, &h); + if (r < 0) + return r; + } else { - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { if (!home_group_match_lookup_parameters(&p, h)) continue; @@ -279,7 +283,9 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_ if (p.user_name) { const char *last = NULL; - h = hashmap_get(m->homes_by_name, p.user_name); + r = manager_get_home_by_name(m, p.user_name, &h); + if (r < 0) + return r; if (!h) return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); @@ -315,7 +321,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_ } else if (p.group_name) { const char *last = NULL; - HASHMAP_FOREACH(h, m->homes_by_name) { + HASHMAP_FOREACH(h, m->homes_by_uid) { if (!strv_contains(h->record->member_of, p.group_name)) continue; @@ -340,7 +346,7 @@ int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_ } else { const char *last_user_name = NULL, *last_group_name = NULL; - HASHMAP_FOREACH(h, m->homes_by_name) + HASHMAP_FOREACH(h, m->homes_by_uid) STRV_FOREACH(j, h->record->member_of) { if (last_user_name) { diff --git a/src/home/homework-mount.c b/src/home/homework-mount.c index 0907a96e47..8d32adb270 100644 --- a/src/home/homework-mount.c +++ b/src/home/homework-mount.c @@ -220,6 +220,11 @@ static int make_home_userns(uid_t stored_uid, uid_t exposed_uid) { if (r < 0) return log_oom(); + /* Map the foreign range 1:1. After all what is foreign should remain foreign. */ + r = append_identity_range(&text, FOREIGN_UID_MIN, FOREIGN_UID_MAX+1, stored_uid); + if (r < 0) + return log_oom(); + /* Map nspawn's mapped root UID as identity mapping so that people can run nspawn uidmap mounted * containers off $HOME, if they want. */ r = strextendf(&text, UID_FMT " " UID_FMT " " UID_FMT "\n", UID_MAPPED_ROOT, UID_MAPPED_ROOT, 1u); diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 624f1ced88..95f719d912 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -102,11 +102,6 @@ static int acquire_user_record( UserRecord **ret_record, PamBusData **bus_data) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - _cleanup_free_ char *homed_field = NULL; - const char *json = NULL; int r; assert(handle); @@ -115,7 +110,6 @@ static int acquire_user_record( r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); - if (isempty(username)) return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set."); } @@ -125,13 +119,19 @@ static int acquire_user_record( if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username, 0)) return PAM_USER_UNKNOWN; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + const char *json = NULL; + bool fresh_data; + /* We cache the user record in the PAM context. We use a field name that includes the username, since * clients might change the user name associated with a PAM context underneath us. Notably, 'sudo' * creates a single PAM context and first authenticates it with the user set to the originating user, * then updates the user for the destination user and issues the session stack with the same PAM * context. We thus must be prepared that the user record changes between calls and we keep any * caching separate. */ - homed_field = strjoin("systemd-home-user-record-", username); + _cleanup_free_ char *homed_field = strjoin("systemd-home-user-record-", username); if (!homed_field) return pam_log_oom(handle); @@ -144,9 +144,10 @@ static int acquire_user_record( * negative cache indicator) */ if (json == POINTER_MAX) return PAM_USER_UNKNOWN; + + fresh_data = false; } else { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *generic_field = NULL, *json_copy = NULL; _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; r = pam_acquire_bus_connection(handle, "pam-systemd-home", debug, &bus, bus_data); @@ -178,9 +179,42 @@ static int acquire_user_record( if (r < 0) return pam_bus_log_parse_error(handle, r); + fresh_data = true; + } + + r = sd_json_parse(json, /* flags= */ 0, &v, NULL, NULL); + if (r < 0) + return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m"); + + ur = user_record_new(); + if (!ur) + return pam_log_oom(handle); + + r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); + if (r < 0) + return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); + + /* Safety check if cached record actually matches what we are looking for */ + if (!user_record_matches_user_name(ur, username)) + return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, + "Acquired user record does not match user name."); + + /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is + * going to invalidate the old version after all */ + username = ur->user_name; + + /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary, + * normalized name of the user record (i.e. not an alias or so). */ + r = pam_set_item(handle, PAM_USER, ur->user_name); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, + "Failed to update username PAM item to '%s': @PAMERR@", ur->user_name); + + /* Everything seems to be good, let's cache this data now */ + if (fresh_data) { /* First copy: for the homed-specific data field, i.e. where we know the user record is from * homed */ - json_copy = strdup(json); + _cleanup_free_ char *json_copy = strdup(json); if (!json_copy) return pam_log_oom(handle); @@ -196,35 +230,18 @@ static int acquire_user_record( if (!json_copy) return pam_log_oom(handle); - generic_field = strjoin("systemd-user-record-", username); + _cleanup_free_ char *generic_field = strjoin("systemd-user-record-", username); if (!generic_field) return pam_log_oom(handle); r = pam_set_data(handle, generic_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, - "Failed to set PAM user record data '%s': @PAMERR@", homed_field); + "Failed to set PAM user record data '%s': @PAMERR@", generic_field); TAKE_PTR(json_copy); } - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); - if (r < 0) - return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m"); - - ur = user_record_new(); - if (!ur) - return pam_log_oom(handle); - - r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); - if (r < 0) - return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); - - /* Safety check if cached record actually matches what we are looking for */ - if (!streq_ptr(username, ur->user_name)) - return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, - "Acquired user record does not match user name."); - if (ret_record) *ret_record = TAKE_PTR(ur); @@ -535,7 +552,6 @@ static int acquire_home( r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); - if (isempty(username)) return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set."); @@ -879,7 +895,6 @@ _public_ PAM_EXTERN int pam_sm_close_session( r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); - if (isempty(username)) return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set."); @@ -949,7 +964,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( if (r != PAM_SUCCESS) return r; - r = acquire_user_record(handle, NULL, debug, &ur, NULL); + r = acquire_user_record(handle, /* username= */ NULL, debug, &ur, /* bus_data= */ NULL); if (r != PAM_SUCCESS) return r; @@ -1057,7 +1072,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( pam_debug_syslog(handle, debug, "pam-systemd-homed account management"); - r = acquire_user_record(handle, NULL, debug, &ur, NULL); + r = acquire_user_record(handle, /* username= */ NULL, debug, &ur, /* bus_data= */ NULL); if (r != PAM_SUCCESS) return r; diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 2af9c68d98..a4549da826 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -42,6 +42,7 @@ #include "user-util.h" #include "utf8.h" #include "varlink-io.systemd.Hostname.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" #include "virt.h" @@ -1771,13 +1772,19 @@ static int connect_varlink(Context *c) { if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); - r = sd_varlink_server_add_interface(c->varlink_server, &vl_interface_io_systemd_Hostname); + r = sd_varlink_server_add_interface_many( + c->varlink_server, + &vl_interface_io_systemd_Hostname, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Hostname interface to Varlink server: %m"); r = sd_varlink_server_bind_method_many( c->varlink_server, - "io.systemd.Hostname.Describe", vl_method_describe); + "io.systemd.Hostname.Describe", vl_method_describe, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to bind Varlink method calls: %m"); diff --git a/src/import/importd.c b/src/import/importd.c index ea8257a833..414994ee30 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -42,6 +42,7 @@ #include "syslog-util.h" #include "user-util.h" #include "varlink-io.systemd.Import.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" #include "web-util.h" @@ -1370,7 +1371,7 @@ static int method_list_images(sd_bus_message *msg, void *userdata, sd_bus_error class < 0 ? (c < _IMAGE_CLASS_MAX) : (c == class); c++) { - _cleanup_(hashmap_freep) Hashmap *h = NULL; + _cleanup_hashmap_free_ Hashmap *h = NULL; h = hashmap_new(&image_hash_ops); if (!h) @@ -1979,14 +1980,20 @@ static int manager_connect_varlink(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); - r = sd_varlink_server_add_interface(m->varlink_server, &vl_interface_io_systemd_Import); + r = sd_varlink_server_add_interface_many( + m->varlink_server, + &vl_interface_io_systemd_Import, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Import interface to varlink server: %m"); r = sd_varlink_server_bind_method_many( m->varlink_server, - "io.systemd.Import.ListTransfers", vl_method_list_transfers, - "io.systemd.Import.Pull", vl_method_pull); + "io.systemd.Import.ListTransfers", vl_method_list_transfers, + "io.systemd.Import.Pull", vl_method_pull, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to bind Varlink method calls: %m"); diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index a602886cb3..56c02d4ad8 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -18,6 +18,7 @@ #include "process-util.h" #include "string-util.h" #include "terminal-util.h" +#include "verbs.h" static uint32_t arg_activate_flags; static int arg_percent; @@ -86,118 +87,118 @@ static const char *integrity_algorithm_select(const void *key_file_buf) { return "crc32c"; } -static int run(int argc, char *argv[]) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - char *verb, *volume; + crypt_status_info status; + _cleanup_(erase_and_freep) void *key_buf = NULL; + size_t key_buf_size = 0; int r; - if (argv_looks_like_help(argc, argv)) - return help(); + /* attach name device optional_key_file optional_options */ - if (argc < 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); + assert(argc >= 3 && argc <= 5); - verb = argv[1]; - volume = argv[2]; + const char *volume = argv[1], + *device = argv[2], + *key_file = mangle_none(argc > 3 ? argv[3] : NULL), + *options = mangle_none(argc > 4 ? argv[4] : NULL); - log_setup(); + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - cryptsetup_enable_logging(NULL); + if (key_file) { + r = load_key_file(key_file, &key_buf, &key_buf_size); + if (r < 0) + return r; + } - umask(0022); + if (options) { + r = parse_integrity_options(options, &arg_activate_flags, &arg_percent, + &arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm); + if (r < 0) + return r; + } - if (streq(verb, "attach")) { - /* attach name device optional_key_file optional_options */ + r = crypt_init(&cd, device); + if (r < 0) + return log_error_errno(r, "Failed to open integrity device %s: %m", device); - crypt_status_info status; - _cleanup_(erase_and_freep) void *key_buf = NULL; - const char *device, *key_file, *options; - size_t key_buf_size = 0; + cryptsetup_enable_logging(cd); - if (argc < 4) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least three arguments."); + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } - if (argc > 6) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach has a maximum of five arguments."); + r = crypt_load(cd, + CRYPT_INTEGRITY, + &(struct crypt_params_integrity) { + .journal_watermark = arg_percent, + .journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC), + .integrity = integrity_algorithm_select(key_buf), + }); + if (r < 0) + return log_error_errno(r, "Failed to load integrity superblock: %m"); - device = argv[3]; - key_file = mangle_none(argc > 4 ? argv[4] : NULL); - options = mangle_none(argc > 5 ? argv[5] : NULL); + if (!isempty(arg_existing_data_device)) { + r = crypt_set_data_device(cd, arg_existing_data_device); + if (r < 0) + return log_error_errno(r, "Failed to add separate data device: %m"); + } - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); + if (r < 0) + return log_error_errno(r, "Failed to set up integrity device: %m"); - if (key_file) { - r = load_key_file(key_file, &key_buf, &key_buf_size); - if (r < 0) - return r; - } + return 0; +} - if (options) { - r = parse_integrity_options(options, &arg_activate_flags, &arg_percent, - &arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm); - if (r < 0) - return r; - } +static int verb_detach(int argc, char *argv[], void *userdata) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + int r; - r = crypt_init(&cd, device); - if (r < 0) - return log_error_errno(r, "Failed to open integrity device %s: %m", device); - - cryptsetup_enable_logging(cd); - - status = crypt_status(cd, volume); - if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { - log_info("Volume %s already active.", volume); - return 0; - } - - r = crypt_load(cd, - CRYPT_INTEGRITY, - &(struct crypt_params_integrity) { - .journal_watermark = arg_percent, - .journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC), - .integrity = integrity_algorithm_select(key_buf), - }); - if (r < 0) - return log_error_errno(r, "Failed to load integrity superblock: %m"); + assert(argc == 2); - if (!isempty(arg_existing_data_device)) { - r = crypt_set_data_device(cd, arg_existing_data_device); - if (r < 0) - return log_error_errno(r, "Failed to add separate data device: %m"); - } + const char *volume = argv[1]; - r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); - if (r < 0) - return log_error_errno(r, "Failed to set up integrity device: %m"); + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - } else if (streq(verb, "detach")) { + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s already inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() failed: %m"); - if (argc > 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach has a maximum of two arguments."); + cryptsetup_enable_logging(cd); - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate: %m"); - r = crypt_init_by_name(&cd, volume); - if (r == -ENODEV) { - log_info("Volume %s already inactive.", volume); - return 0; - } - if (r < 0) - return log_error_errno(r, "crypt_init_by_name() failed: %m"); + return 0; +} - cryptsetup_enable_logging(cd); +static int run(int argc, char *argv[]) { + if (argv_looks_like_help(argc, argv)) + return help(); - r = crypt_deactivate(cd, volume); - if (r < 0) - return log_error_errno(r, "Failed to deactivate: %m"); + log_setup(); - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); + cryptsetup_enable_logging(NULL); - return 0; + umask(0022); + + static const Verb verbs[] = { + { "attach", 3, 5, 0, verb_attach }, + { "detach", 2, 2, 0, verb_detach }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 73b22170e7..646253e1a5 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -816,7 +816,7 @@ static int request_handler_machine( _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL; RequestMeta *m = ASSERT_PTR(connection_cls); int r; - _cleanup_free_ char* hostname = NULL, *pretty_name = NULL, *os_name = NULL; + _cleanup_free_ char *hostname = NULL, *pretty_name = NULL, *os_name = NULL; uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; sd_id128_t mid, bid; _cleanup_free_ char *v = NULL, *json = NULL; diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 7156bc7727..c48b7df201 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -32,16 +32,16 @@ #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" -static const char* arg_url = NULL; -static const char* arg_getter = NULL; -static const char* arg_listen_raw = NULL; -static const char* arg_listen_http = NULL; -static const char* arg_listen_https = NULL; -static char** arg_files = NULL; /* Do not free this. */ +static const char *arg_url = NULL; +static const char *arg_getter = NULL; +static const char *arg_listen_raw = NULL; +static const char *arg_listen_http = NULL; +static const char *arg_listen_https = NULL; +static char **arg_files = NULL; /* Do not free this. */ static bool arg_compress = true; static bool arg_seal = false; static int http_socket = -1, https_socket = -1; -static char** arg_gnutls_log = NULL; +static char **arg_gnutls_log = NULL; static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID; static char *arg_output = NULL; @@ -78,7 +78,7 @@ static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, journal_write_spl ********************************************************************** **********************************************************************/ -static int spawn_child(const char* child, char** argv) { +static int spawn_child(const char *child, char **argv) { pid_t child_pid; int fd[2], r; @@ -110,7 +110,7 @@ static int spawn_child(const char* child, char** argv) { return fd[0]; } -static int spawn_curl(const char* url) { +static int spawn_curl(const char *url) { char **argv = STRV_MAKE("curl", "-HAccept: application/vnd.fdo.journal", "--silent", @@ -544,9 +544,9 @@ static int setup_raw_socket(RemoteServer *s, const char *address) { static int create_remoteserver( RemoteServer *s, - const char* key, - const char* cert, - const char* trust) { + const char *key, + const char *cert, + const char *trust) { int r, n, fd; @@ -937,7 +937,7 @@ static int parse_argv(int argc, char *argv[]) { case ARG_GNUTLS_LOG: #if HAVE_GNUTLS - for (const char* p = optarg;;) { + for (const char *p = optarg;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 92187b7420..c92651b409 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -44,7 +44,7 @@ MHDDaemonWrapper *MHDDaemonWrapper_free(MHDDaemonWrapper *d) { } #endif -static int open_output(RemoteServer *s, Writer *w, const char* host) { +static int open_output(RemoteServer *s, Writer *w, const char *host) { _cleanup_free_ char *_filename = NULL; const char *filename; int r; @@ -233,7 +233,7 @@ static int remove_source(RemoteServer *s, int fd) { return 0; } -int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name) { +int journal_remote_add_source(RemoteServer *s, int fd, char *name, bool own_name) { RemoteSource *source = NULL; int r; @@ -492,7 +492,7 @@ static int dispatch_blocking_source_event(sd_event_source *event, } static int accept_connection( - const char* type, + const char *type, int fd, SocketAddress *addr, char **hostname) { diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h index f1ec5d9a54..5c1d4863b9 100644 --- a/src/journal-remote/journal-remote.h +++ b/src/journal-remote/journal-remote.h @@ -59,7 +59,7 @@ int journal_remote_server_init( int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer); -int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name); +int journal_remote_add_source(RemoteServer *s, int fd, char *name, bool own_name); int journal_remote_add_raw_socket(RemoteServer *s, int fd); int journal_remote_handle_raw_source( sd_event_source *event, diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index eb36d03130..c702b00806 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -42,7 +42,7 @@ #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem" #define DEFAULT_PORT 19532 -static const char* arg_url = NULL; +static const char *arg_url = NULL; static const char *arg_key = NULL; static const char *arg_cert = NULL; static const char *arg_trust = NULL; diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h index 5ba3c4f1a0..fe6abb75a9 100644 --- a/src/journal-remote/journal-upload.h +++ b/src/journal-remote/journal-upload.h @@ -40,7 +40,7 @@ typedef struct Uploader { int input; /* journal stuff */ - sd_journal* journal; + sd_journal *journal; entry_state entry_state; const void *field_data; diff --git a/src/journal/journalctl-catalog.c b/src/journal/journalctl-catalog.c index 116e152be0..41ea3816e7 100644 --- a/src/journal/journalctl-catalog.c +++ b/src/journal/journalctl-catalog.c @@ -19,7 +19,7 @@ int action_update_catalog(void) { e = secure_getenv("SYSTEMD_CATALOG_SOURCES"); r = catalog_update(database, arg_root, - e ? STRV_MAKE_CONST(e) : catalog_file_dirs); + e ? STRV_MAKE_CONST(e) : NULL); if (r < 0) return log_error_errno(r, "Failed to update catalog: %m"); @@ -41,9 +41,9 @@ int action_list_catalog(char **items) { pager_open(arg_pager_flags); if (items) - r = catalog_list_items(stdout, database, oneline, items); + r = catalog_list_items(/* f = */ NULL, database, oneline, items); else - r = catalog_list(stdout, database, oneline); + r = catalog_list(/* f = */ NULL, database, oneline); if (r < 0) return log_error_errno(r, "Failed to list catalog: %m"); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 8fcfa0c57b..16fb88324f 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -64,6 +64,7 @@ #include "uid-classification.h" #include "user-util.h" #include "varlink-io.systemd.Journal.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" #define USER_JOURNALS_MAX 1024 @@ -2222,8 +2223,9 @@ static int vl_method_synchronize(sd_varlink *link, sd_json_variant *parameters, assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; log_info("Received client request to sync journal."); @@ -2258,11 +2260,13 @@ static int vl_method_synchronize(sd_varlink *link, sd_json_variant *parameters, static int vl_method_rotate(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Server *s = ASSERT_PTR(userdata); + int r; assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; log_info("Received client request to rotate journal, rotating."); server_full_rotate(s); @@ -2272,11 +2276,14 @@ static int vl_method_rotate(sd_varlink *link, sd_json_variant *parameters, sd_va static int vl_method_flush_to_var(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Server *s = ASSERT_PTR(userdata); + int r; assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; + if (s->namespace) return sd_varlink_error(link, "io.systemd.Journal.NotSupportedByNamespaces", NULL); @@ -2288,11 +2295,14 @@ static int vl_method_flush_to_var(sd_varlink *link, sd_json_variant *parameters, static int vl_method_relinquish_var(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Server *s = ASSERT_PTR(userdata); + int r; assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; + if (s->namespace) return sd_varlink_error(link, "io.systemd.Journal.NotSupportedByNamespaces", NULL); @@ -2334,16 +2344,22 @@ static int server_open_varlink(Server *s, const char *socket, int fd) { if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); - r = sd_varlink_server_add_interface(s->varlink_server, &vl_interface_io_systemd_Journal); + r = sd_varlink_server_add_interface_many( + s->varlink_server, + &vl_interface_io_systemd_Journal, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Journal interface to varlink server: %m"); r = sd_varlink_server_bind_method_many( s->varlink_server, - "io.systemd.Journal.Synchronize", vl_method_synchronize, - "io.systemd.Journal.Rotate", vl_method_rotate, - "io.systemd.Journal.FlushToVar", vl_method_flush_to_var, - "io.systemd.Journal.RelinquishVar", vl_method_relinquish_var); + "io.systemd.Journal.Synchronize", vl_method_synchronize, + "io.systemd.Journal.Rotate", vl_method_rotate, + "io.systemd.Journal.FlushToVar", vl_method_flush_to_var, + "io.systemd.Journal.RelinquishVar", vl_method_relinquish_var, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return r; diff --git a/src/libsystemd-network/sd-dns-resolver.c b/src/libsystemd-network/sd-dns-resolver.c index 548672cd33..5b4ed6e23a 100644 --- a/src/libsystemd-network/sd-dns-resolver.c +++ b/src/libsystemd-network/sd-dns-resolver.c @@ -234,7 +234,7 @@ int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *res /* Mandatory keys must be understood by the client, otherwise the record should be discarded. * Automatic mandatory keys must not appear in the mandatory parameter, so these are all * supplementary. We don't understand any supplementary keys, so if the mandatory parameter - * is present, we cannot use this record.*/ + * is present, we cannot use this record. */ case DNS_SVC_PARAM_KEY_MANDATORY: if (plen > 0) return -EBADMSG; diff --git a/src/libsystemd-network/sd-dns-resolver.h b/src/libsystemd-network/sd-dns-resolver.h index 79d00ba9bc..5acd4fe4f6 100644 --- a/src/libsystemd-network/sd-dns-resolver.h +++ b/src/libsystemd-network/sd-dns-resolver.h @@ -16,7 +16,7 @@ typedef struct sd_dns_resolver sd_dns_resolver; typedef enum sd_dns_alpn_flags { /* There isn't really an alpn reserved for Do53 service, but designated resolvers may or may not offer * Do53 service, so we should probably have a flag to represent this capability. Unfortunately DNR - * does not indicate the status to us.*/ + * does not indicate the status to us. */ SD_DNS_ALPN_DO53 = 1 << 0, /* SD_DNS_ALPN_HTTP_1_1, "http/1.1" [RFC9112] */ SD_DNS_ALPN_HTTP_2_TLS = 1 << 1, /* "h2" [RFC9113] [RFC9461] */ diff --git a/src/libsystemd/libsystemd.pc.in b/src/libsystemd/libsystemd.pc.in index 3a43ef6071..8932eee8a3 100644 --- a/src/libsystemd/libsystemd.pc.in +++ b/src/libsystemd/libsystemd.pc.in @@ -18,3 +18,4 @@ URL: {{PROJECT_URL}} Version: {{PROJECT_VERSION}} Libs: -L${libdir} -lsystemd Cflags: -I${includedir} +Requires.private: libcap diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index c953085ef9..726a6bce12 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1065,7 +1065,9 @@ global: sd_device_enumerator_add_all_parents; sd_json_variant_type_from_string; sd_json_variant_type_to_string; + sd_json_variant_unset_field; sd_varlink_get_current_method; + sd_varlink_get_description; sd_varlink_get_input_fd; sd_varlink_get_output_fd; sd_varlink_reset_fds; diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 5b3cae759b..25fe4bd956 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -240,8 +240,8 @@ struct sd_bus { LIST_HEAD(struct filter_callback, filter_callbacks); Hashmap *nodes; - Hashmap *vtable_methods; - Hashmap *vtable_properties; + Set *vtable_methods; + Set *vtable_properties; union sockaddr_union sockaddr; socklen_t sockaddr_size; diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index e528987a6d..af79bbc5f0 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -220,7 +220,7 @@ static int get_child_nodes( OrderedSet **ret, sd_bus_error *error) { - _cleanup_ordered_set_free_free_ OrderedSet *s = NULL; + _cleanup_ordered_set_free_ OrderedSet *s = NULL; int r; assert(bus); @@ -228,7 +228,7 @@ static int get_child_nodes( assert(n); assert(ret); - s = ordered_set_new(&string_hash_ops); + s = ordered_set_new(&string_hash_ops_free); if (!s) return -ENOMEM; @@ -926,7 +926,7 @@ int introspect_path( char **ret, sd_bus_error *error) { - _cleanup_ordered_set_free_free_ OrderedSet *s = NULL; + _cleanup_ordered_set_free_ OrderedSet *s = NULL; _cleanup_(introspect_done) struct introspect intro = {}; bool empty; int r; @@ -1229,7 +1229,7 @@ static int process_get_managed_objects( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_ordered_set_free_free_ OrderedSet *s = NULL; + _cleanup_ordered_set_free_ OrderedSet *s = NULL; char *path; int r; @@ -1314,7 +1314,7 @@ static int object_find_and_run( vtable_key.interface = m->interface; vtable_key.member = m->member; - v = hashmap_get(bus->vtable_methods, &vtable_key); + v = set_get(bus->vtable_methods, &vtable_key); if (v) { r = method_callbacks_run(bus, m, v, require_fallback, found_object); if (r != 0) @@ -1341,7 +1341,7 @@ static int object_find_and_run( if (r < 0) return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters"); - v = hashmap_get(bus->vtable_properties, &vtable_key); + v = set_get(bus->vtable_properties, &vtable_key); if (v) { r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object); if (r != 0) @@ -1686,7 +1686,9 @@ static int vtable_member_compare_func(const struct vtable_member *x, const struc return strcmp(x->member, y->member); } -DEFINE_PRIVATE_HASH_OPS(vtable_member_hash_ops, struct vtable_member, vtable_member_hash_func, vtable_member_compare_func); +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + vtable_member_hash_ops, + struct vtable_member, vtable_member_hash_func, vtable_member_compare_func, free); typedef enum { NAMES_FIRST_PART = 1 << 0, /* first part of argument name list (input names). It is reset by names_are_valid() */ @@ -1812,14 +1814,6 @@ static int add_object_vtable_internal( !streq(interface, "org.freedesktop.DBus.Peer") && !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL); - r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops); - if (r < 0) - return r; - n = bus_node_allocate(bus, path); if (!n) return -ENOMEM; @@ -1892,7 +1886,9 @@ static int add_object_vtable_internal( m->member = v->x.method.member; m->vtable = v; - r = hashmap_put(bus->vtable_methods, m, m); + r = set_ensure_put(&bus->vtable_methods, &vtable_member_hash_ops, m); + if (r == 0) + r = -EEXIST; if (r < 0) { free(m); goto fail; @@ -1940,7 +1936,9 @@ static int add_object_vtable_internal( m->member = v->x.property.member; m->vtable = v; - r = hashmap_put(bus->vtable_properties, m, m); + r = set_ensure_put(&bus->vtable_properties, &vtable_member_hash_ops, m); + if (r == 0) + r = -EEXIST; if (r < 0) { free(m); goto fail; @@ -2128,7 +2126,7 @@ static int emit_properties_changed_on_interface( assert_return(member_name_is_valid(*property), -EINVAL); key.member = *property; - v = hashmap_get(bus->vtable_properties, &key); + v = set_get(bus->vtable_properties, &key); if (!v) return -ENOENT; @@ -2222,7 +2220,7 @@ static int emit_properties_changed_on_interface( struct vtable_member *v; key.member = *property; - assert_se(v = hashmap_get(bus->vtable_properties, &key)); + assert_se(v = set_get(bus->vtable_properties, &key)); assert(c == v->parent); if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) diff --git a/src/libsystemd/sd-bus/bus-slot.c b/src/libsystemd/sd-bus/bus-slot.c index 9f289575ef..8238e31c90 100644 --- a/src/libsystemd/sd-bus/bus-slot.c +++ b/src/libsystemd/sd-bus/bus-slot.c @@ -129,7 +129,7 @@ void bus_slot_disconnect(sd_bus_slot *slot, bool unref) { key.interface = slot->node_vtable.interface; key.member = v->x.method.member; - x = hashmap_remove(slot->bus->vtable_methods, &key); + x = set_remove(slot->bus->vtable_methods, &key); break; } @@ -141,7 +141,7 @@ void bus_slot_disconnect(sd_bus_slot *slot, bool unref) { key.interface = slot->node_vtable.interface; key.member = v->x.method.member; - x = hashmap_remove(slot->bus->vtable_properties, &key); + x = set_remove(slot->bus->vtable_properties, &key); break; }} diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index ecaf5b33b4..d5098ba650 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -1010,7 +1010,7 @@ static int bind_description(sd_bus *b, int fd, int family) { } static int connect_as(int fd, const struct sockaddr *sa, socklen_t salen, uid_t uid, gid_t gid) { - _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; + _cleanup_close_pair_ int pfd[2] = EBADF_PAIR; ssize_t n; int r; diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 699761fc31..c14d144753 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -220,14 +220,14 @@ static sd_bus* bus_free(sd_bus *b) { bus_reset_queues(b); - ordered_hashmap_free_free(b->reply_callbacks); + ordered_hashmap_free(b->reply_callbacks); prioq_free(b->reply_callbacks_prioq); assert(b->match_callbacks.type == BUS_MATCH_ROOT); bus_match_free(&b->match_callbacks); - hashmap_free_free(b->vtable_methods); - hashmap_free_free(b->vtable_properties); + set_free(b->vtable_methods); + set_free(b->vtable_properties); assert(hashmap_isempty(b->nodes)); hashmap_free(b->nodes); @@ -2332,10 +2332,6 @@ _public_ int sd_bus_call_async( if (!callback && !slot && !m->sealed) m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - r = ordered_hashmap_ensure_allocated(&bus->reply_callbacks, &uint64_hash_ops); - if (r < 0) - return r; - r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare); if (r < 0) return r; @@ -2356,7 +2352,7 @@ _public_ int sd_bus_call_async( s->reply_callback.callback = callback; s->reply_callback.cookie = BUS_MESSAGE_COOKIE(m); - r = ordered_hashmap_put(bus->reply_callbacks, &s->reply_callback.cookie, &s->reply_callback); + r = ordered_hashmap_ensure_put(&bus->reply_callbacks, &uint64_hash_ops_value_free, &s->reply_callback.cookie, &s->reply_callback); if (r < 0) { s->reply_callback.cookie = 0; return r; diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h index a465eb25fd..c79181d5bb 100644 --- a/src/libsystemd/sd-device/device-internal.h +++ b/src/libsystemd/sd-device/device-internal.h @@ -106,6 +106,8 @@ static inline int device_add_property_internal(sd_device *device, const char *ke int device_set_syspath(sd_device *device, const char *_syspath, bool verify); int device_set_ifindex(sd_device *device, const char *ifindex); +int device_set_devuid(sd_device *device, const char *uid); +int device_set_devgid(sd_device *device, const char *gid); int device_set_devmode(sd_device *device, const char *devmode); int device_set_devname(sd_device *device, const char *devname); int device_set_devtype(sd_device *device, const char *devtype); diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c index 1c148b8573..463eb7ea9e 100644 --- a/src/libsystemd/sd-device/device-private.c +++ b/src/libsystemd/sd-device/device-private.c @@ -116,6 +116,10 @@ int device_get_devnode_mode(sd_device *device, mode_t *ret) { assert(device); + r = device_read_uevent_file(device); + if (r < 0) + return r; + r = device_read_db(device); if (r < 0) return r; @@ -134,6 +138,10 @@ int device_get_devnode_uid(sd_device *device, uid_t *ret) { assert(device); + r = device_read_uevent_file(device); + if (r < 0) + return r; + r = device_read_db(device); if (r < 0) return r; @@ -147,7 +155,7 @@ int device_get_devnode_uid(sd_device *device, uid_t *ret) { return 0; } -static int device_set_devuid(sd_device *device, const char *uid) { +int device_set_devuid(sd_device *device, const char *uid) { uid_t u; int r; @@ -172,6 +180,10 @@ int device_get_devnode_gid(sd_device *device, gid_t *ret) { assert(device); + r = device_read_uevent_file(device); + if (r < 0) + return r; + r = device_read_db(device); if (r < 0) return r; @@ -185,7 +197,7 @@ int device_get_devnode_gid(sd_device *device, gid_t *ret) { return 0; } -static int device_set_devgid(sd_device *device, const char *gid) { +int device_set_devgid(sd_device *device, const char *gid) { gid_t g; int r; @@ -429,10 +441,11 @@ static int device_verify(sd_device *device) { return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), "sd-device: Device created from strv or nulstr lacks devpath, subsystem, action or seqnum."); - if (streq(device->subsystem, "drivers")) { + if (device_in_subsystem(device, "drivers")) { r = device_set_drivers_subsystem(device); if (r < 0) - return r; + return log_device_debug_errno(device, r, + "sd-device: Failed to set driver subsystem: %m"); } device->sealed = true; diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index eab54203f0..4165c85023 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -9,6 +9,7 @@ #include "sd-device.h" +#include "chase.h" #include "macro.h" int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum); @@ -31,9 +32,9 @@ int device_get_devnode_mode(sd_device *device, mode_t *ret); int device_get_devnode_uid(sd_device *device, uid_t *ret); int device_get_devnode_gid(sd_device *device, gid_t *ret); +int device_chase(sd_device *device, const char *path, ChaseFlags flags, char **ret_resolved, int *ret_fd); void device_clear_sysattr_cache(sd_device *device); -int device_cache_sysattr_value(sd_device *device, const char *key, char *value); -int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value); +int device_cache_sysattr_value(sd_device *device, char *key, char *value, int error); void device_seal(sd_device *device); void device_set_is_initialized(sd_device *device); diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index a608ae326f..033a9eb875 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -766,16 +766,24 @@ static int handle_uevent_line( assert(major); assert(minor); + if (streq(key, "SUBSYSTEM")) + return device_set_subsystem(device, value); if (streq(key, "DEVTYPE")) return device_set_devtype(device, value); if (streq(key, "IFINDEX")) return device_set_ifindex(device, value); if (streq(key, "DEVNAME")) return device_set_devname(device, value); + if (streq(key, "DEVUID")) + return device_set_devuid(device, value); + if (streq(key, "DEVGID")) + return device_set_devgid(device, value); if (streq(key, "DEVMODE")) return device_set_devmode(device, value); if (streq(key, "DISKSEQ")) return device_set_diskseq(device, value); + if (streq(key, "DRIVER")) + return device_set_driver(device, value); if (streq(key, "MAJOR")) *major = value; else if (streq(key, "MINOR")) @@ -787,89 +795,59 @@ static int handle_uevent_line( } int device_read_uevent_file(sd_device *device) { - _cleanup_free_ char *uevent = NULL; - const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; - char *path; - size_t uevent_len; int r; - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - assert(device); if (device->uevent_loaded || device->sealed) return 0; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - device->uevent_loaded = true; - path = strjoina(syspath, "/uevent"); - - r = read_full_virtual_file(path, &uevent, &uevent_len); - if (r == -EACCES || ERRNO_IS_NEG_DEVICE_ABSENT(r)) + const char *uevent; + r = sd_device_get_sysattr_value(device, "uevent", &uevent); + if (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* The uevent files may be write-only, the device may be already removed, or the device * may not have the uevent file. */ return 0; if (r < 0) - return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file '%s': %m", path); - - for (size_t i = 0; i < uevent_len; i++) - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, uevent[i])) { - key = &uevent[i]; - - state = KEY; - } - - break; - case KEY: - if (uevent[i] == '=') { - uevent[i] = '\0'; - - state = PRE_VALUE; - } else if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - log_device_debug(device, "sd-device: Invalid uevent line '%s', ignoring", key); - - state = PRE_KEY; - } + return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file: %m"); - break; - case PRE_VALUE: - value = &uevent[i]; - state = VALUE; - - _fallthrough_; /* to handle empty property */ - case VALUE: - if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; + _cleanup_strv_free_ char **v = NULL; + r = strv_split_newlines_full(&v, uevent, EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to parse uevent file: %m"); - r = handle_uevent_line(device, key, value, &major, &minor); - if (r < 0) - log_device_debug_errno(device, r, "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", key, value); + const char *major = NULL, *minor = NULL; + STRV_FOREACH(s, v) { + char *eq = strchr(*s, '='); + if (!eq) { + log_device_debug(device, "sd-device: Invalid uevent line, ignoring: %s", *s); + continue; + } - state = PRE_KEY; - } + *eq = '\0'; - break; - default: - assert_not_reached(); - } + r = handle_uevent_line(device, *s, eq + 1, &major, &minor); + if (r < 0) + log_device_debug_errno(device, r, + "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", + *s, eq + 1); + } if (major) { r = device_set_devnum(device, major, minor); if (r < 0) - log_device_debug_errno(device, r, "sd-device: Failed to set 'MAJOR=%s' or 'MINOR=%s' from '%s', ignoring: %m", major, strna(minor), path); + log_device_debug_errno(device, r, + "sd-device: Failed to set 'MAJOR=%s' and/or 'MINOR=%s' from uevent, ignoring: %m", + major, strna(minor)); + } + + if (device_in_subsystem(device, "drivers")) { + r = device_set_drivers_subsystem(device); + if (r < 0) + log_device_debug_errno(device, r, + "sd-device: Failed to set driver subsystem, ignoring: %m"); } return 0; @@ -1214,36 +1192,29 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { assert_return(device, -EINVAL); - if (!device->subsystem_set) { - _cleanup_free_ char *subsystem = NULL; - const char *syspath; - char *path; + r = device_read_uevent_file(device); + if (r < 0) + return r; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; + if (!device->subsystem_set) { + const char *subsystem; - /* read 'subsystem' link */ - path = strjoina(syspath, "/subsystem"); - r = readlink_value(path, &subsystem); + r = sd_device_get_sysattr_value(device, "subsystem", &subsystem); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to read subsystem for %s: %m", device->devpath); - - if (subsystem) + if (r >= 0) r = device_set_subsystem(device, subsystem); /* use implicit names */ else if (!isempty(path_startswith(device->devpath, "/module/"))) r = device_set_subsystem(device, "module"); - else if (strstr(syspath, "/drivers/") || endswith(syspath, "/drivers")) + else if (strstr(device->devpath, "/drivers/") || endswith(device->devpath, "/drivers")) r = device_set_drivers_subsystem(device); else if (!isempty(PATH_STARTSWITH_SET(device->devpath, "/class/", "/bus/"))) r = device_set_subsystem(device, "subsystem"); - else { - device->subsystem_set = true; - r = 0; - } + else + r = device_set_subsystem(device, NULL); if (r < 0) return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem for %s: %m", @@ -1352,23 +1323,21 @@ int device_set_driver(sd_device *device, const char *driver) { } _public_ int sd_device_get_driver(sd_device *device, const char **ret) { + int r; + assert_return(device, -EINVAL); - if (!device->driver_set) { - _cleanup_free_ char *driver = NULL; - const char *syspath; - char *path; - int r; + r = device_read_uevent_file(device); + if (r < 0) + return r; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; + if (!device->driver_set) { + const char *driver = NULL; - path = strjoina(syspath, "/driver"); - r = readlink_value(path, &driver); + r = sd_device_get_sysattr_value(device, "driver", &driver); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, - "sd-device: readlink(\"%s\") failed: %m", path); + "sd-device: Failed to read driver: %m"); r = device_set_driver(device, driver); if (r < 0) @@ -1702,18 +1671,13 @@ _public_ int sd_device_get_device_id(sd_device *device, const char **ret) { if (!device->device_id) { _cleanup_free_ char *id = NULL; - const char *subsystem; dev_t devnum; int ifindex, r; - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return r; - if (sd_device_get_devnum(device, &devnum) >= 0) { /* use dev_t — b259:131072, c254:0 */ if (asprintf(&id, "%c" DEVNUM_FORMAT_STR, - streq(subsystem, "block") ? 'b' : 'c', + device_in_subsystem(device, "block") ? 'b' : 'c', DEVNUM_FORMAT_VAL(devnum)) < 0) return -ENOMEM; } else if (sd_device_get_ifindex(device, &ifindex) >= 0) { @@ -1731,13 +1695,18 @@ _public_ int sd_device_get_device_id(sd_device *device, const char **ret) { if (r == O_DIRECTORY) return -EINVAL; - if (streq(subsystem, "drivers")) { + if (device_in_subsystem(device, "drivers")) /* the 'drivers' pseudo-subsystem is special, and needs the real * subsystem encoded as well */ - assert(device->driver_subsystem); - id = strjoin("+drivers:", device->driver_subsystem, ":", sysname); - } else + id = strjoin("+drivers:", ASSERT_PTR(device->driver_subsystem), ":", sysname); + else { + const char *subsystem; + r = sd_device_get_subsystem(device, &subsystem); + if (r < 0) + return r; + id = strjoin("+", subsystem, ":", sysname); + } if (!id) return -ENOMEM; } @@ -2326,134 +2295,224 @@ void device_clear_sysattr_cache(sd_device *device) { device->sysattr_values = hashmap_free(device->sysattr_values); } -int device_cache_sysattr_value(sd_device *device, const char *key, char *value) { - _unused_ _cleanup_free_ char *old_value = NULL; - _cleanup_free_ char *new_key = NULL; +typedef struct SysAttrCacheEntry { + char *key; + char *value; + int error; +} SysAttrCacheEntry; + +static SysAttrCacheEntry* sysattr_cache_entry_free(SysAttrCacheEntry *p) { + if (!p) + return NULL; + + free(p->key); + free(p->value); + return mfree(p); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + sysattr_cache_hash_ops, + char, path_hash_func, path_compare, + SysAttrCacheEntry, sysattr_cache_entry_free); + +static int device_cache_sysattr_value_full(sd_device *device, char *key, char *value, int error, bool ignore_uevent) { int r; assert(device); assert(key); + assert(value || error > 0); - /* This takes the reference of the input value. The input value may be NULL. - * This replaces the value if it already exists. */ + /* This takes the reference of the input arguments when cached, hence the caller must not free them + * when a positive return value is returned. The input value may be NULL. This replaces an already + * existing entry. */ - /* First, remove the old cache entry. So, we do not need to clear cache on error. */ - old_value = hashmap_remove2(device->sysattr_values, key, (void **) &new_key); - if (!new_key) { - new_key = strdup(key); - if (!new_key) - return -ENOMEM; - } + if (ignore_uevent && streq(last_path_component(key), "uevent")) + return 0; /* not cached */ + + /* Remove the old cache entry. So, we do not need to clear cache on error. */ + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, key)); - r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value); + /* We use ENOANO as a recognizable error code when we have not read the attribute. */ + if (error == ENOANO) + error = ESTALE; + + _cleanup_free_ SysAttrCacheEntry *entry = new(SysAttrCacheEntry, 1); + if (!entry) + return -ENOMEM; + + *entry = (SysAttrCacheEntry) { + .key = key, + .value = value, + .error = error, + }; + + r = hashmap_ensure_put(&device->sysattr_values, &sysattr_cache_hash_ops, entry->key, entry); if (r < 0) return r; - TAKE_PTR(new_key); + TAKE_PTR(entry); + return 1; /* cached */ +} - return 0; +int device_cache_sysattr_value(sd_device *device, char *key, char *value, int error) { + return device_cache_sysattr_value_full(device, key, value, error, /* ignore_uevent = */ true); } -int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) { - const char *k = NULL, *value; +static int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) { + SysAttrCacheEntry *entry; assert(device); assert(key); - value = hashmap_get2(device->sysattr_values, key, (void **) &k); - if (!k) - return -ESTALE; /* We have not read the attribute. */ - if (!value) - return -ENOENT; /* We have looked up the attribute before and it did not exist. */ + entry = hashmap_get(device->sysattr_values, key); + if (!entry) + return -ENOANO; /* We have not read the attribute. */ + if (!entry->value) { + /* We have looked up the attribute before and failed. Return the cached error code. */ + assert(entry->error > 0); + return -entry->error; + } if (ret_value) - *ret_value = value; + *ret_value = entry->value; return 0; } -/* We cache all sysattr lookups. If an attribute does not exist, it is stored - * with a NULL value in the cache, otherwise the returned string is stored */ -_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) { - _cleanup_free_ char *value = NULL, *path = NULL; - const char *syspath; - struct stat statbuf; +int device_chase(sd_device *device, const char *path, ChaseFlags flags, char **ret_resolved, int *ret_fd) { int r; - assert_return(device, -EINVAL); - assert_return(sysattr, -EINVAL); + assert(device); + assert(path); - /* look for possibly already cached result */ - r = device_get_cached_sysattr_value(device, sysattr, ret_value); - if (r != -ESTALE) + const char *syspath; + r = sd_device_get_syspath(device, &syspath); + if (r < 0) return r; - r = sd_device_get_syspath(device, &syspath); + /* Here, CHASE_PREFIX_ROOT is borrowed. If the flag is set or the specified path is relative, then + * the path will be prefixed with the syspath. Note, we do not pass CHASE_PREFIX_ROOT flag with + * syspath as root to chase(), but we manually concatenate the specified path with syspath before + * calling chase(). Otherwise, we cannot set/get attributes of parent or sibling devices. */ + _cleanup_free_ char *prefixed = NULL; + if (FLAGS_SET(flags, CHASE_PREFIX_ROOT) || !path_is_absolute(path)) { + prefixed = path_join(syspath, path); + if (!prefixed) + return -ENOMEM; + path = prefixed; + flags &= ~CHASE_PREFIX_ROOT; + } + + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = -EBADF; + r = chase(path, /* root = */ NULL, CHASE_NO_AUTOFS | flags, &resolved, ret_fd ? &fd : NULL); if (r < 0) return r; - path = path_join(syspath, sysattr); - if (!path) - return -ENOMEM; + /* Refuse to reading/writing files outside of sysfs. */ + if (!path_startswith(resolved, "/sys/")) + return -EINVAL; - if (lstat(path, &statbuf) < 0) { - int k; + if (ret_resolved) { + /* Always return relative path. */ + r = path_make_relative(syspath, resolved, ret_resolved); + if (r < 0) + return r; + } - r = -errno; + if (ret_fd) + *ret_fd = TAKE_FD(fd); - /* remember that we could not access the sysattr */ - k = device_cache_sysattr_value(device, sysattr, NULL); - if (k < 0) - log_device_debug_errno(device, k, - "sd-device: failed to cache attribute '%s' with NULL, ignoring: %m", - sysattr); + return 0; +} + +_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) { + _cleanup_free_ char *resolved = NULL, *value = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + /* Look for possibly already cached result. */ + r = device_get_cached_sysattr_value(device, sysattr, ret_value); + if (r != -ENOANO) return r; - } else if (S_ISLNK(statbuf.st_mode)) { - /* Some core links return only the last element of the target path, - * these are just values, the paths should not be exposed. */ - if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { - r = readlink_value(path, &value); - if (r < 0) - return r; - } else - return -EINVAL; - } else if (S_ISDIR(statbuf.st_mode)) - /* skip directories */ - return -EISDIR; - else if (!(statbuf.st_mode & S_IRUSR)) - /* skip non-readable files */ - return -EPERM; - else { - size_t size; - - /* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to - * also get the size of the result. See issue #20025. */ - r = read_full_virtual_file(path, &value, &size); + + /* Special cases: read the symlink and return the last component of the value. Some core links return + * only the last element of the target path, these are just values, the paths should not be exposed. */ + if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { + _cleanup_free_ char *prefixed = NULL; + const char *syspath; + + r = sd_device_get_syspath(device, &syspath); if (r < 0) return r; - /* drop trailing newlines */ - while (size > 0 && strchr(NEWLINE, value[--size])) - value[size] = '\0'; + prefixed = path_join(syspath, sysattr); + if (!prefixed) + return -ENOMEM; + + r = readlink_value(prefixed, &value); + if (r != -EINVAL) /* -EINVAL means the path is not a symlink. */ + goto cache_result; } - /* Unfortunately, we need to return 'const char*' instead of 'char*'. Hence, failure in caching - * sysattr value is critical unlike the other places. */ - r = device_cache_sysattr_value(device, sysattr, value); - if (r < 0) { - log_device_debug_errno(device, r, - "sd-device: failed to cache attribute '%s' with '%s'%s: %m", - sysattr, value, ret_value ? "" : ", ignoring"); - if (ret_value) - return r; + r = device_chase(device, sysattr, CHASE_PREFIX_ROOT, &resolved, &fd); + if (r < 0) + goto cache_result; - return 0; + /* Look for cached result again with the resolved path. */ + r = device_get_cached_sysattr_value(device, resolved, ret_value); + if (r != -ENOANO) + return r; + + /* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to also get the + * size of the result. See issue #20025. */ + size_t size; + r = read_virtual_file_fd(fd, SIZE_MAX, &value, &size); + if (r < 0) + goto cache_result; + + delete_trailing_chars(value, NEWLINE); + r = 0; + +cache_result: + if (r == -ENOMEM) + return r; /* Do not cache -ENOMEM, as the failure may be transient. */ + + if (!resolved) { + /* If we have not or could not chase the path, assume 'sysattr' is normalized. */ + resolved = strdup(sysattr); + if (!resolved) + return RET_GATHER(r, -ENOMEM); } - if (ret_value) + int k = device_cache_sysattr_value_full(device, resolved, value, -r, /* ignore_uevent = */ false); + if (k < 0) { + if (r < 0) + log_device_debug_errno(device, k, + "sd-device: failed to cache error code (%i) in reading attribute '%s', ignoring: %m", + -r, resolved); + else { + /* Unfortunately, we need to return 'const char*' instead of 'char*'. Hence, failure in caching + * sysattr value is critical unlike the other places. */ + log_device_debug_errno(device, k, + "sd-device: failed to cache attribute '%s' with '%s'%s: %m", + resolved, value, ret_value ? "" : ", ignoring"); + if (ret_value) + return k; + } + + return r; + } + assert(k > 0); + + if (ret_value && r >= 0) *ret_value = value; + /* device_cache_sysattr_value_full() takes 'resolved' and 'value' on success. */ + TAKE_PTR(resolved); TAKE_PTR(value); - return 0; + return r; } int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value) { @@ -2527,19 +2586,22 @@ int device_get_sysattr_bool(sd_device *device, const char *sysattr) { return parse_boolean(value); } -static void device_remove_cached_sysattr_value(sd_device *device, const char *_key) { - _cleanup_free_ char *key = NULL; +static int device_remove_cached_sysattr_value(sd_device *device, const char *sysattr) { + int r; assert(device); - assert(_key); + assert(sysattr); + + _cleanup_free_ char *resolved = NULL; + r = device_chase(device, sysattr, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &resolved, /* ret_fd = */ NULL); + if (r < 0) + return r; - free(hashmap_remove2(device->sysattr_values, _key, (void **) &key)); + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, resolved)); + return 0; } -_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *_value) { - _cleanup_free_ char *value = NULL, *path = NULL; - const char *syspath; - size_t len; +_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *value) { int r; assert_return(device, -EINVAL); @@ -2547,52 +2609,43 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, /* Set the attribute and save it in the cache. */ - if (!_value) { + if (!value) /* If input value is NULL, then clear cache and not write anything. */ - device_remove_cached_sysattr_value(device, sysattr); - return 0; - } + return device_remove_cached_sysattr_value(device, sysattr); - r = sd_device_get_syspath(device, &syspath); - if (r < 0) + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = -EBADF; + r = device_chase(device, sysattr, CHASE_PREFIX_ROOT, &resolved, &fd); + if (r < 0) { + /* On failure, clear cache entry, hopefully, 'sysattr' is normalized. */ + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, sysattr)); return r; + } - path = path_join(syspath, sysattr); - if (!path) + /* value length is limited to 4k */ + _cleanup_free_ char *copied = strndup(value, 4096); + if (!copied) return -ENOMEM; - len = strlen(_value); - /* drop trailing newlines */ - while (len > 0 && strchr(NEWLINE, _value[len - 1])) - len--; + delete_trailing_chars(copied, NEWLINE); - /* value length is limited to 4k */ - if (len > 4096) - return -EINVAL; - - value = strndup(_value, len); - if (!value) - return -ENOMEM; - - r = write_string_file(path, value, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_NOFOLLOW); + r = write_string_file_fd(fd, copied, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_AVOID_NEWLINE); if (r < 0) { /* On failure, clear cache entry, as we do not know how it fails. */ - device_remove_cached_sysattr_value(device, sysattr); + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, resolved)); return r; } - /* Do not cache action string written into uevent file. */ - if (streq(sysattr, "uevent")) - return 0; - - r = device_cache_sysattr_value(device, sysattr, value); + r = device_cache_sysattr_value(device, resolved, copied, 0); if (r < 0) log_device_debug_errno(device, r, - "sd-device: failed to cache attribute '%s' with '%s', ignoring: %m", - sysattr, value); - else - TAKE_PTR(value); + "sd-device: failed to cache written attribute '%s' with '%s', ignoring: %m", + resolved, copied); + else if (r > 0) { + TAKE_PTR(resolved); + TAKE_PTR(copied); + } return 0; } @@ -2605,10 +2658,8 @@ _public_ int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr assert_return(device, -EINVAL); assert_return(sysattr, -EINVAL); - if (!format) { - device_remove_cached_sysattr_value(device, sysattr); - return 0; - } + if (!format) + return device_remove_cached_sysattr_value(device, sysattr); va_start(ap, format); r = vasprintf(&value, format, ap); diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index 7dcc35d8d5..4e3733ce2e 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -55,15 +55,20 @@ typedef struct CatalogItem { } CatalogItem; static void catalog_hash_func(const CatalogItem *i, struct siphash *state) { + assert(i); + assert(state); + siphash24_compress_typesafe(i->id, state); siphash24_compress_string(i->language, state); } static int catalog_compare_func(const CatalogItem *a, const CatalogItem *b) { - unsigned k; int r; - for (k = 0; k < ELEMENTSOF(b->id.bytes); k++) { + assert(a); + assert(b); + + for (size_t k = 0; k < ELEMENTSOF(b->id.bytes); k++) { r = CMP(a->id.bytes[k], b->id.bytes[k]); if (r != 0) return r; @@ -72,11 +77,17 @@ static int catalog_compare_func(const CatalogItem *a, const CatalogItem *b) { return strcmp(a->language, b->language); } -DEFINE_HASH_OPS(catalog_hash_ops, CatalogItem, catalog_hash_func, catalog_compare_func); +DEFINE_PRIVATE_HASH_OPS_FULL( + catalog_hash_ops, + CatalogItem, catalog_hash_func, catalog_compare_func, free, + void, free); static bool next_header(const char **s) { const char *e; + assert(s); + assert(*s); + e = strchr(*s, '\n'); /* Unexpected end */ @@ -91,17 +102,22 @@ static bool next_header(const char **s) { return true; } -static const char *skip_header(const char *s) { +static const char* skip_header(const char *s) { + assert(s); + while (next_header(&s)) ; return s; } -static char *combine_entries(const char *one, const char *two) { +static char* combine_entries(const char *one, const char *two) { const char *b1, *b2; size_t l1, l2, n; char *dest, *p; + assert(one); + assert(two); + /* Find split point of headers to body */ b1 = skip_header(one); b2 = skip_header(two); @@ -140,10 +156,11 @@ static char *combine_entries(const char *one, const char *two) { } static int finish_item( - OrderedHashmap *h, + OrderedHashmap **h, sd_id128_t id, const char *language, - char *payload, size_t payload_size) { + const char *payload, + size_t payload_size) { _cleanup_free_ CatalogItem *i = NULL; _cleanup_free_ char *combined = NULL; @@ -164,14 +181,14 @@ static int finish_item( strcpy(i->language, language); } - prev = ordered_hashmap_get(h, i); + prev = ordered_hashmap_get(*h, i); if (prev) { /* Already have such an item, combine them */ combined = combine_entries(payload, prev); if (!combined) return log_oom(); - r = ordered_hashmap_update(h, i, combined); + r = ordered_hashmap_update(*h, i, combined); if (r < 0) return log_error_errno(r, "Failed to update catalog item: %m"); @@ -183,7 +200,7 @@ static int finish_item( if (!combined) return log_oom(); - r = ordered_hashmap_put(h, i, combined); + r = ordered_hashmap_ensure_put(h, &catalog_hash_ops, i, combined); if (r < 0) return log_error_errno(r, "Failed to insert catalog item: %m"); @@ -194,8 +211,11 @@ static int finish_item( return 0; } -int catalog_file_lang(const char* filename, char **lang) { - char *beg, *end, *_lang; +int catalog_file_lang(const char *filename, char **ret) { + char *beg, *end, *lang; + + assert(filename); + assert(ret); end = endswith(filename, ".catalog"); if (!end) @@ -208,21 +228,23 @@ int catalog_file_lang(const char* filename, char **lang) { if (*beg != '.' || end <= beg + 1) return 0; - _lang = strndup(beg + 1, end - beg - 1); - if (!_lang) + lang = strndup(beg + 1, end - beg - 1); + if (!lang) return -ENOMEM; - *lang = _lang; + *ret = lang; return 1; } static int catalog_entry_lang( - const char* filename, + const char *filename, unsigned line, - const char* t, - const char* deflang, + const char *t, + const char *deflang, char **ret) { + assert(t); + size_t c = strlen(t); if (c < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -243,7 +265,7 @@ static int catalog_entry_lang( return strdup_to(ret, t); } -int catalog_import_file(OrderedHashmap *h, const char *path) { +int catalog_import_file(OrderedHashmap **h, const char *path) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *payload = NULL; size_t payload_size = 0; @@ -371,14 +393,19 @@ int catalog_import_file(OrderedHashmap *h, const char *path) { static int64_t write_catalog( const char *database, - struct strbuf *sb, - CatalogItem *items, - size_t n) { + const struct strbuf *sb, + const CatalogItem *items, + size_t n_items) { _cleanup_(unlink_and_freep) char *p = NULL; _cleanup_fclose_ FILE *w = NULL; int r; + assert(database); + assert(sb); + assert(items); + assert(n_items > 0); + r = mkdir_parents(database, 0755); if (r < 0) return log_error_errno(r, "Failed to create parent directories of %s: %m", database); @@ -391,13 +418,13 @@ static int64_t write_catalog( .signature = CATALOG_SIGNATURE, .header_size = htole64(CONST_ALIGN_TO(sizeof(CatalogHeader), 8)), .catalog_item_size = htole64(sizeof(CatalogItem)), - .n_items = htole64(n), + .n_items = htole64(n_items), }; if (fwrite(&header, sizeof(header), 1, w) != 1) return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write header.", p); - if (fwrite(items, sizeof(CatalogItem), n, w) != n) + if (fwrite(items, sizeof(CatalogItem), n_items, w) != n_items) return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write database.", p); if (fwrite(sb->buf, sb->len, 1, w) != 1) @@ -416,30 +443,23 @@ static int64_t write_catalog( return ftello(w); } -int catalog_update(const char* database, const char* root, const char* const* dirs) { - _cleanup_strv_free_ char **files = NULL; - _cleanup_(strbuf_freep) struct strbuf *sb = NULL; - _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; - _cleanup_free_ CatalogItem *items = NULL; - ssize_t offset; - char *payload; - CatalogItem *i; - unsigned n; +int catalog_update(const char *database, const char *root, const char* const *dirs) { int r; - int64_t sz; - h = ordered_hashmap_new(&catalog_hash_ops); - sb = strbuf_new(); - if (!h || !sb) - return log_oom(); + assert(database); + + if (!dirs) + dirs = catalog_file_dirs; + _cleanup_strv_free_ char **files = NULL; r = conf_files_list_strv(&files, ".catalog", root, 0, dirs); if (r < 0) return log_error_errno(r, "Failed to get catalog files: %m"); + _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; STRV_FOREACH(f, files) { log_debug("Reading file '%s'", *f); - r = catalog_import_file(h, *f); + r = catalog_import_file(&h, *f); if (r < 0) return log_error_errno(r, "Failed to import file '%s': %m", *f); } @@ -451,17 +471,23 @@ int catalog_update(const char* database, const char* root, const char* const* di log_debug("Found %u items in catalog.", ordered_hashmap_size(h)); - items = new(CatalogItem, ordered_hashmap_size(h)); + _cleanup_free_ CatalogItem *items = new(CatalogItem, ordered_hashmap_size(h)); if (!items) return log_oom(); - n = 0; + _cleanup_(strbuf_freep) struct strbuf *sb = strbuf_new(); + if (!sb) + return log_oom(); + + unsigned n = 0; + char *payload; + CatalogItem *i; ORDERED_HASHMAP_FOREACH_KEY(payload, i, h) { log_trace("Found " SD_ID128_FORMAT_STR ", language %s", SD_ID128_FORMAT_VAL(i->id), isempty(i->language) ? "C" : i->language); - offset = strbuf_add_string(sb, payload); + ssize_t offset = strbuf_add_string(sb, payload); if (offset < 0) return log_oom(); @@ -474,7 +500,7 @@ int catalog_update(const char* database, const char* root, const char* const* di strbuf_complete(sb); - sz = write_catalog(database, sb, items, n); + int64_t sz = write_catalog(database, sb, items, n); if (sz < 0) return log_error_errno(sz, "Failed to write %s: %m", database); @@ -483,31 +509,28 @@ int catalog_update(const char* database, const char* root, const char* const* di return 0; } -static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p) { - _cleanup_close_ int fd = -EBADF; - const CatalogHeader *h; - struct stat st; - void *p; - - assert(_fd); - assert(_st); - assert(_p); +static int open_mmap(const char *database, int *ret_fd, struct stat *ret_st, void **ret_map) { + assert(database); + assert(ret_fd); + assert(ret_st); + assert(ret_map); - fd = open(database, O_RDONLY|O_CLOEXEC); + _cleanup_close_ int fd = open(database, O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + struct stat st; if (fstat(fd, &st) < 0) return -errno; if (st.st_size < (off_t) sizeof(CatalogHeader) || file_offset_beyond_memory_size(st.st_size)) return -EINVAL; - p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + void *p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) return -errno; - h = p; + const CatalogHeader *h = p; if (memcmp(h->signature, (const uint8_t[]) CATALOG_SIGNATURE, sizeof(h->signature)) != 0 || le64toh(h->header_size) < sizeof(CatalogHeader) || le64toh(h->catalog_item_size) < sizeof(CatalogItem) || @@ -518,16 +541,15 @@ static int open_mmap(const char *database, int *_fd, struct stat *_st, void **_p return -EBADMSG; } - *_fd = TAKE_FD(fd); - *_st = st; - *_p = p; - + *ret_fd = TAKE_FD(fd); + *ret_st = st; + *ret_map = p; return 0; } -static const char *find_id(void *p, sd_id128_t id) { +static const char* find_id(const void *p, sd_id128_t id) { CatalogItem *f = NULL, key = { .id = id }; - const CatalogHeader *h = p; + const CatalogHeader *h = ASSERT_PTR(p); const char *loc; loc = setlocale(LC_MESSAGES, NULL); @@ -580,33 +602,33 @@ static const char *find_id(void *p, sd_id128_t id) { le64toh(f->offset); } -int catalog_get(const char* database, sd_id128_t id, char **ret_text) { - _cleanup_close_ int fd = -EBADF; - void *p = NULL; - struct stat st; +int catalog_get(const char *database, sd_id128_t id, char **ret_text) { int r; - const char *s; + assert(database); assert(ret_text); + _cleanup_close_ int fd = -EBADF; + struct stat st; + void *p; r = open_mmap(database, &fd, &st, &p); if (r < 0) return r; - s = find_id(p, id); - if (!s) { + const char *s = find_id(p, id); + if (!s) r = -ENOENT; - goto finish; - } + else + r = strdup_to(ret_text, s); - r = strdup_to(ret_text, s); -finish: (void) munmap(p, st.st_size); - return r; } -static char *find_header(const char *s, const char *header) { +static char* find_header(const char *s, const char *header) { + + assert(s); + assert(header); for (;;) { const char *v; @@ -623,6 +645,11 @@ static char *find_header(const char *s, const char *header) { } static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneline) { + assert(s); + + if (!f) + f = stdout; + if (oneline) { _cleanup_free_ char *subject = NULL, *defined_by = NULL; @@ -638,24 +665,23 @@ static void dump_catalog_entry(FILE *f, sd_id128_t id, const char *s, bool oneli } int catalog_list(FILE *f, const char *database, bool oneline) { - _cleanup_close_ int fd = -EBADF; - void *p = NULL; - struct stat st; - const CatalogHeader *h; - const CatalogItem *items; int r; - unsigned n; - sd_id128_t last_id; - bool last_id_set = false; + assert(database); + + _cleanup_close_ int fd = -EBADF; + struct stat st; + void *p; r = open_mmap(database, &fd, &st, &p); if (r < 0) return r; - h = p; - items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size)); + const CatalogHeader *h = p; + const CatalogItem *items = (const CatalogItem*) ((const uint8_t*) p + le64toh(h->header_size)); - for (n = 0; n < le64toh(h->n_items); n++) { + sd_id128_t last_id; + bool last_id_set = false; + for (size_t n = 0; n < le64toh(h->n_items); n++) { const char *s; if (last_id_set && sd_id128_equal(last_id, items[n].id)) @@ -669,38 +695,33 @@ int catalog_list(FILE *f, const char *database, bool oneline) { last_id = items[n].id; } - munmap(p, st.st_size); - + (void) munmap(p, st.st_size); return 0; } int catalog_list_items(FILE *f, const char *database, bool oneline, char **items) { - int r = 0; + int r, ret = 0; + + assert(database); STRV_FOREACH(item, items) { sd_id128_t id; - int k; - _cleanup_free_ char *msg = NULL; - - k = sd_id128_from_string(*item, &id); - if (k < 0) { - log_error_errno(k, "Failed to parse id128 '%s': %m", *item); - if (r == 0) - r = k; + r = sd_id128_from_string(*item, &id); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to parse id128 '%s': %m", *item)); continue; } - k = catalog_get(database, id, &msg); - if (k < 0) { - log_full_errno(k == -ENOENT ? LOG_NOTICE : LOG_ERR, k, - "Failed to retrieve catalog entry for '%s': %m", *item); - if (r == 0) - r = k; + _cleanup_free_ char *msg = NULL; + r = catalog_get(database, id, &msg); + if (r < 0) { + RET_GATHER(ret, log_full_errno(r == -ENOENT ? LOG_NOTICE : LOG_ERR, r, + "Failed to retrieve catalog entry for '%s': %m", *item)); continue; } dump_catalog_entry(f, id, msg, oneline); } - return r; + return ret; } diff --git a/src/libsystemd/sd-journal/catalog.h b/src/libsystemd/sd-journal/catalog.h index b5a97fa6fb..e6a8c9b8db 100644 --- a/src/libsystemd/sd-journal/catalog.h +++ b/src/libsystemd/sd-journal/catalog.h @@ -7,13 +7,10 @@ #include "sd-id128.h" #include "hashmap.h" -#include "strbuf.h" -int catalog_import_file(OrderedHashmap *h, const char *path); -int catalog_update(const char* database, const char* root, const char* const* dirs); -int catalog_get(const char* database, sd_id128_t id, char **ret_text); -int catalog_list(FILE *f, const char* database, bool oneline); -int catalog_list_items(FILE *f, const char* database, bool oneline, char **items); -int catalog_file_lang(const char *filename, char **lang); -extern const char * const catalog_file_dirs[]; -extern const struct hash_ops catalog_hash_ops; +int catalog_import_file(OrderedHashmap **h, const char *path); +int catalog_update(const char *database, const char *root, const char* const *dirs); +int catalog_get(const char *database, sd_id128_t id, char **ret_text); +int catalog_list(FILE *f, const char *database, bool oneline); +int catalog_list_items(FILE *f, const char *database, bool oneline, char **items); +int catalog_file_lang(const char *filename, char **ret); diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 7e941edb19..508b47cbe7 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -291,7 +291,7 @@ JournalFile* journal_file_close(JournalFile *f) { safe_close(f->fd); free(f->path); - ordered_hashmap_free_free(f->chain_cache); + ordered_hashmap_free(f->chain_cache); #if HAVE_COMPRESSION free(f->compress_buffer); @@ -2341,7 +2341,7 @@ static int journal_file_append_entry_internal( if (sd_id128_is_null(*seqnum_id)) *seqnum_id = f->header->seqnum_id; /* Caller has none assigned, then copy the one from the file */ else if (!sd_id128_equal(*seqnum_id, f->header->seqnum_id)) { - /* Different seqnum IDs? We can't allow entries from multiple IDs end up in the same journal.*/ + /* Different seqnum IDs? We can't allow entries from multiple IDs end up in the same journal. */ if (le64toh(f->header->n_entries) == 0) f->header->seqnum_id = *seqnum_id; /* Caller has one, and file so far has no entries, then copy the one from the caller */ else @@ -2646,7 +2646,7 @@ typedef struct ChainCacheItem { } ChainCacheItem; static void chain_cache_put( - OrderedHashmap *h, + JournalFile *f, ChainCacheItem *ci, uint64_t first, uint64_t array, @@ -2654,7 +2654,7 @@ static void chain_cache_put( uint64_t total, uint64_t last_index) { - assert(h); + assert(f); if (!ci) { /* If the chain item to cache for this chain is the @@ -2662,8 +2662,8 @@ static void chain_cache_put( if (array == first) return; - if (ordered_hashmap_size(h) >= CHAIN_CACHE_MAX) { - ci = ordered_hashmap_steal_first(h); + if (ordered_hashmap_size(f->chain_cache) >= CHAIN_CACHE_MAX) { + ci = ordered_hashmap_steal_first(f->chain_cache); assert(ci); } else { ci = new(ChainCacheItem, 1); @@ -2673,7 +2673,7 @@ static void chain_cache_put( ci->first = first; - if (ordered_hashmap_put(h, &ci->first, ci) < 0) { + if (ordered_hashmap_ensure_put(&f->chain_cache, &uint64_hash_ops_value_free, &ci->first, ci) < 0) { free(ci); return; } @@ -2847,7 +2847,7 @@ static int generic_array_get( r = journal_file_move_to_object(f, OBJECT_ENTRY, p, ret_object); if (r >= 0) { /* Let's cache this item for the next invocation */ - chain_cache_put(f->chain_cache, ci, first, a, journal_file_entry_array_item(f, o, 0), t, i); + chain_cache_put(f, ci, first, a, journal_file_entry_array_item(f, o, 0), t, i); if (ret_offset) *ret_offset = p; @@ -3187,7 +3187,7 @@ found: return -EBADMSG; /* Let's cache this item for the next invocation */ - chain_cache_put(f->chain_cache, ci, first, a, p, t, i); + chain_cache_put(f, ci, first, a, p, t, i); p = journal_file_entry_array_item(f, array, i); if (p == 0) @@ -4115,12 +4115,6 @@ int journal_file_open( } } - f->chain_cache = ordered_hashmap_new(&uint64_hash_ops); - if (!f->chain_cache) { - r = -ENOMEM; - goto fail; - } - if (f->fd < 0) { /* We pass O_NONBLOCK here, so that in case somebody pointed us to some character device node or FIFO * or so, we likely fail quickly than block for long. For regular files O_NONBLOCK has no effect, hence diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 858aeca45e..83f77206c3 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -83,7 +83,7 @@ static int journal_put_error(sd_journal *j, int r, const char *path) { return -ENOMEM; } - r = hashmap_ensure_put(&j->errors, NULL, INT_TO_PTR(r), copy); + r = hashmap_ensure_put(&j->errors, &trivial_hash_ops_value_free, INT_TO_PTR(r), copy); if (r == -EEXIST) return 0; if (r < 0) @@ -2560,7 +2560,7 @@ _public_ void sd_journal_close(sd_journal *j) { if (j->mmap) mmap_cache_unref(j->mmap); - hashmap_free_free(j->errors); + hashmap_free(j->errors); set_free(j->exclude_syslog_identifiers); diff --git a/src/libsystemd/sd-journal/test-catalog.c b/src/libsystemd/sd-journal/test-catalog.c index 603952e904..ee8bd4e0ec 100644 --- a/src/libsystemd/sd-journal/test-catalog.c +++ b/src/libsystemd/sd-journal/test-catalog.c @@ -28,31 +28,29 @@ static const char *no_catalog_dirs[] = { static OrderedHashmap* test_import(const char* contents, ssize_t size, int code) { _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-catalog.XXXXXX"; _cleanup_close_ int fd = -EBADF; - OrderedHashmap *h; + OrderedHashmap *h = NULL; if (size < 0) size = strlen(contents); - assert_se(h = ordered_hashmap_new(&catalog_hash_ops)); - fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se(write(fd, contents, size) == size); - assert_se(catalog_import_file(h, name) == code); + assert_se(catalog_import_file(&h, name) == code); return h; } static void test_catalog_import_invalid(void) { - _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; h = test_import("xxx", -1, -EINVAL); assert_se(ordered_hashmap_isempty(h)); } static void test_catalog_import_badid(void) { - _unused_ _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; + _unused_ _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; const char *input = "-- 0027229ca0644181a76c4e92458afaff dededededededededededededededede\n" \ "Subject: message\n" \ @@ -62,7 +60,7 @@ static void test_catalog_import_badid(void) { } static void test_catalog_import_one(void) { - _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; char *payload; const char *input = @@ -86,7 +84,7 @@ static void test_catalog_import_one(void) { } static void test_catalog_import_merge(void) { - _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; char *payload; const char *input = @@ -118,7 +116,7 @@ static void test_catalog_import_merge(void) { } static void test_catalog_import_merge_no_body(void) { - _cleanup_ordered_hashmap_free_free_free_ OrderedHashmap *h = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *h = NULL; char *payload; const char *input = @@ -222,10 +220,10 @@ int main(int argc, char *argv[]) { test_catalog_update(database); - r = catalog_list(stdout, database, true); + r = catalog_list(NULL, database, true); assert_se(r >= 0); - r = catalog_list(stdout, database, false); + r = catalog_list(NULL, database, false); assert_se(r >= 0); assert_se(catalog_get(database, SD_MESSAGE_COREDUMP, &text) >= 0); diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index ea64205c14..f2d306f781 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -13,6 +13,7 @@ #include "path-util.h" #include "process-util.h" #include "string-util.h" +#include "syslog-util.h" #include "user-util.h" int json_dispatch_unbase64_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { @@ -344,8 +345,15 @@ int json_dispatch_ifindex(const char *name, sd_json_variant *variant, sd_json_di int json_dispatch_log_level(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { int *log_level = ASSERT_PTR(userdata), r, t; + /* If SD_JSON_STRICT is set, we'll refuse attempts to set the log level to null. If SD_JSON_RELAX is + * set we'll turn null (and any negative log level) into LOG_NULL (which when used as max log level + * means: no logging). Otherwise we turn null into LOG_INFO (which is typically our default). */ + if (sd_json_variant_is_null(variant)) { - *log_level = -1; + if (FLAGS_SET(flags, SD_JSON_STRICT)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' may not be null.", strna(name)); + + *log_level = FLAGS_SET(flags, SD_JSON_RELAX) ? LOG_NULL : LOG_INFO; return 0; } @@ -353,8 +361,9 @@ int json_dispatch_log_level(const char *name, sd_json_variant *variant, sd_json_ if (r < 0) return r; - /* If SD_JSON_RELAX is set allow a zero interface index, otherwise refuse. */ - if (LOG_PRI(t) != t) + if (FLAGS_SET(flags, SD_JSON_RELAX) && t < 0) + t = LOG_NULL; + else if (!log_level_is_valid(t)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid log level.", strna(name)); *log_level = t; diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 99d941e1ef..c25a2cdbbc 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -2129,6 +2129,56 @@ _public_ int sd_json_variant_set_field_strv(sd_json_variant **v, const char *fie return sd_json_variant_set_field(v, field, m); } +_public_ int sd_json_variant_unset_field(sd_json_variant **v, const char *field) { + int r; + + assert_return(v, -EINVAL); + assert_return(field, -EINVAL); + + if (sd_json_variant_is_blank_object(*v)) + return 0; + if (!sd_json_variant_is_object(*v)) + return -EINVAL; + + _cleanup_free_ sd_json_variant **array = NULL; + size_t k = 0; + for (size_t i = 0, n = sd_json_variant_elements(*v); i < n; i += 2) { + sd_json_variant *p; + + p = sd_json_variant_by_index(*v, i); + if (!sd_json_variant_is_string(p)) + return -EINVAL; + + if (streq(sd_json_variant_string(p), field)) { + if (!array) { + assert(n >= 2); + array = new(sd_json_variant*, n - 2); + if (!array) + return -ENOMEM; + + for (k = 0; k < i; k++) + array[k] = sd_json_variant_by_index(*v, k); + } + } else if (array) { + array[k++] = p; + array[k++] = sd_json_variant_by_index(*v, i + 1); + } + } + + if (!array) + return 0; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; + r = sd_json_variant_new_object(&w, array, k); + if (r < 0) + return r; + + json_variant_propagate_sensitive(*v, w); + JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + + return 1; +} + _public_ int sd_json_variant_merge_object(sd_json_variant **v, sd_json_variant *m) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; _cleanup_free_ sd_json_variant **array = NULL; diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c index 90616094bc..5aa6da7b83 100644 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -95,7 +95,7 @@ int rtnl_resolve_ifname_full( /* This is similar to if_nametoindex(), but also resolves alternative names and decimal formatted * ifindex too. Returns ifindex, and optionally provides the main interface name and alternative - * names.*/ + * names. */ if (!rtnl) rtnl = &our_rtnl; diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 1349fd36ab..c7126ac6e6 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -1607,7 +1607,7 @@ static int varlink_idl_symbol_consistent( const sd_varlink_symbol *symbol, int level) { - _cleanup_(set_freep) Set *input_set = NULL, *output_set = NULL; + _cleanup_set_free_ Set *input_set = NULL, *output_set = NULL; const char *symbol_name; int r; @@ -1654,7 +1654,7 @@ static int varlink_idl_symbol_consistent( } int varlink_idl_consistent(const sd_varlink_interface *interface, int level) { - _cleanup_(set_freep) Set *name_set = NULL; + _cleanup_set_free_ Set *name_set = NULL; int r; assert(interface); diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 8658c81f67..d2ece7b2e6 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1197,8 +1197,9 @@ static int generic_method_get_info( assert(link); assert(link->server); - if (sd_json_variant_elements(parameters) != 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; sd_varlink_interface *interface; HASHMAP_FOREACH(interface, link->server->interfaces) { @@ -2915,6 +2916,12 @@ _public_ int sd_varlink_set_description(sd_varlink *v, const char *description) return free_and_strdup(&v->description, description); } +_public_ const char* sd_varlink_get_description(sd_varlink *v) { + assert_return(v, NULL); + + return v->description; +} + static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { sd_varlink *v = ASSERT_PTR(userdata); diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in index 6541bcb1ab..a3f9f7b4f4 100644 --- a/src/libudev/libudev.pc.in +++ b/src/libudev/libudev.pc.in @@ -18,3 +18,4 @@ Version: {{PROJECT_VERSION}} Libs: -L${libdir} -ludev Libs.private: -lrt -pthread Cflags: -I${includedir} +Requires.private: libcap diff --git a/src/login/logind-session.h b/src/login/logind-session.h index c0cf03ff54..9b32d4bd7c 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -51,7 +51,7 @@ typedef enum SessionClass { /* Which session classes can pin our user tracking? */ #define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY, SESSION_NONE)) -/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/ +/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves) */ #define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_EARLY_LIGHT, SESSION_USER_LIGHT, SESSION_GREETER)) /* Which session classes have a lock screen concept? */ @@ -69,7 +69,7 @@ typedef enum SessionClass { /* Which session classes allow changing session types */ #define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT, SESSION_GREETER, SESSION_LOCK_SCREEN)) -/* Which session classes are taken into acccount when deciding whether shutdown shall be allowed if other users are logged in */ +/* Which session classes are taken into account when deciding whether shutdown shall be allowed if other users are logged in */ #define SESSION_CLASS_IS_INHIBITOR_LIKE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_LIGHT, SESSION_USER_EARLY_LIGHT) typedef enum SessionType { diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index a8a2542e18..a757033a55 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -10,6 +10,7 @@ #include "terminal-util.h" #include "user-util.h" #include "varlink-io.systemd.Login.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" static int manager_varlink_get_session_by_peer( @@ -355,14 +356,20 @@ int manager_varlink_init(Manager *m) { sd_varlink_server_set_userdata(s, m); - r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Login); + r = sd_varlink_server_add_interface_many( + s, + &vl_interface_io_systemd_Login, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Login interface to varlink server: %m"); r = sd_varlink_server_bind_method_many( s, - "io.systemd.Login.CreateSession", vl_method_create_session, - "io.systemd.Login.ReleaseSession", vl_method_release_session); + "io.systemd.Login.CreateSession", vl_method_create_session, + "io.systemd.Login.ReleaseSession", vl_method_release_session, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 893d03cdb6..ffca6045b6 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -177,26 +177,25 @@ static int acquire_user_record( pam_handle_t *handle, UserRecord **ret_record) { - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - const char *username = NULL, *json = NULL; - _cleanup_free_ char *field = NULL; int r; assert(handle); + const char *username = NULL; r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); - if (isempty(username)) return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not valid."); /* If pam_systemd_homed (or some other module) already acquired the user record we can reuse it * here. */ - field = strjoin("systemd-user-record-", username); + _cleanup_free_ char *field = strjoin("systemd-user-record-", username); if (!field) return pam_log_oom(handle); + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + const char *json = NULL; r = pam_get_data(handle, field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); @@ -217,14 +216,14 @@ static int acquire_user_record( return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); /* Safety check if cached record actually matches what we are looking for */ - if (!streq_ptr(username, ur->user_name)) + if (!user_record_matches_user_name(ur, username)) return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "Acquired user record does not match user name."); } else { _cleanup_free_ char *formatted = NULL; /* Request the record ourselves */ - r = userdb_by_name(username, 0, &ur); + r = userdb_by_name(username, /* flags= */ 0, &ur); if (r < 0) { pam_syslog_errno(handle, LOG_ERR, r, "Failed to get user record: %m"); return PAM_USER_UNKNOWN; @@ -349,8 +348,7 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_ v = vtnr_from_tty(tty); if (v < 0) return v; - else if (v == 0) - return -ENOENT; + assert(v > 0); if (seat) *seat = "seat0"; @@ -1468,12 +1466,12 @@ _public_ PAM_EXTERN int pam_sm_close_session( if (parse_argv(handle, argc, argv, - NULL, - NULL, - NULL, + /* class= */ NULL, + /* type= */ NULL, + /* desktop= */ NULL, &debug, - NULL, - NULL) < 0) + /* default_capability_bounding_set */ NULL, + /* default_capability_ambient_set= */ NULL) < 0) return PAM_SESSION_ERR; pam_debug_syslog(handle, debug, "pam-systemd shutting down"); diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 3b11d76453..f62007d155 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -73,7 +73,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( /* Split the key by NUL. Set the last item as authtok. */ - _cleanup_(strv_free_erasep) char **passwords = strv_parse_nulstr(p, n); + _cleanup_strv_free_erase_ char **passwords = strv_parse_nulstr(p, n); if (!passwords) return pam_log_oom(handle); diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index b242f83429..6c2fef95db 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -8,15 +8,20 @@ #include "bus-error.h" #include "bus-locator.h" #include "dev-setup.h" +#include "devnum-util.h" +#include "fd-util.h" #include "format-util.h" #include "fs-util.h" #include "label-util.h" #include "limits-util.h" #include "main-func.h" +#include "missing_magic.h" +#include "missing_syscall.h" #include "mkdir-label.h" #include "mount-util.h" #include "mountpoint-util.h" #include "path-util.h" +#include "quota-util.h" #include "rm-rf.h" #include "selinux-util.h" #include "smack-util.h" @@ -24,6 +29,7 @@ #include "string-util.h" #include "strv.h" #include "user-util.h" +#include "userdb.h" static int acquire_runtime_dir_properties(uint64_t *ret_size, uint64_t *ret_inodes) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -92,39 +98,58 @@ static int user_mkdir_runtime_path( uid, gid, runtime_dir_size, runtime_dir_inodes, mac_smack_use() ? ",smackfsroot=*" : ""); + _cleanup_free_ char *d = strdup(runtime_path); + if (!d) + return log_oom(); + r = mkdir_label(runtime_path, 0700); if (r < 0 && r != -EEXIST) return log_error_errno(r, "Failed to create %s: %m", runtime_path); + _cleanup_(rmdir_and_freep) char *destroy = TAKE_PTR(d); /* auto-destroy */ + r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options); if (r < 0) { - if (!ERRNO_IS_PRIVILEGE(r)) { - log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path); - goto fail; - } + if (!ERRNO_IS_PRIVILEGE(r)) + return log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path); log_debug_errno(r, "Failed to mount per-user tmpfs directory %s.\n" "Assuming containerized execution, ignoring: %m", runtime_path); r = chmod_and_chown(runtime_path, 0700, uid, gid); - if (r < 0) { - log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path); - goto fail; - } + if (r < 0) + return log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path); } + destroy = mfree(destroy); /* deactivate auto-destroy */ + r = label_fix(runtime_path, 0); if (r < 0) log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path); } return 0; +} + +static int do_mount(UserRecord *ur) { + int r; -fail: - /* Try to clean up, but ignore errors */ - (void) rmdir(runtime_path); - return r; + assert(ur); + + if (!uid_is_valid(ur->uid) || !gid_is_valid(ur->gid)) + return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID or GID, refusing.", ur->user_name); + + uint64_t runtime_dir_size, runtime_dir_inodes; + r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes); + if (r < 0) + return r; + + char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; + xsprintf(runtime_path, "/run/user/" UID_FMT, ur->uid); + + log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, ur->uid, ur->gid); + return user_mkdir_runtime_path(runtime_path, ur->uid, ur->gid, runtime_dir_size, runtime_dir_inodes); } static int user_remove_runtime_path(const char *runtime_path) { @@ -139,9 +164,9 @@ static int user_remove_runtime_path(const char *runtime_path) { /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to * mount something */ - r = umount2(runtime_path, MNT_DETACH); - if (r < 0 && !IN_SET(errno, EINVAL, ENOENT)) - log_debug_errno(errno, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path); + r = RET_NERRNO(umount2(runtime_path, MNT_DETACH)); + if (r < 0 && !IN_SET(r, -EINVAL, -ENOENT)) + log_debug_errno(r, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path); r = rm_rf(runtime_path, REMOVE_ROOT); if (r < 0 && r != -ENOENT) @@ -150,31 +175,6 @@ static int user_remove_runtime_path(const char *runtime_path) { return 0; } -static int do_mount(const char *user) { - char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; - uint64_t runtime_dir_size, runtime_dir_inodes; - uid_t uid; - gid_t gid; - int r; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); - if (r < 0) - return log_error_errno(r, - r == -ESRCH ? "No such user \"%s\"" : - r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group" - : "Failed to look up user \"%s\": %m", - user); - - r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes); - if (r < 0) - return r; - - xsprintf(runtime_path, "/run/user/" UID_FMT, uid); - - log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, uid, gid); - return user_mkdir_runtime_path(runtime_path, uid, gid, runtime_dir_size, runtime_dir_inodes); -} - static int do_umount(const char *user) { char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; uid_t uid; @@ -198,6 +198,126 @@ static int do_umount(const char *user) { return user_remove_runtime_path(runtime_path); } +static int apply_tmpfs_quota( + char **paths, + uid_t uid, + uint64_t limit, + uint32_t scale) { + + _cleanup_set_free_ Set *processed = NULL; + int r; + + assert(uid_is_valid(uid)); + + STRV_FOREACH(p, paths) { + _cleanup_close_ int fd = open(*p, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) { + log_warning_errno(errno, "Failed to open '%s' in order to set quota, ignoring: %m", *p); + continue; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + log_warning_errno(errno, "Failed to stat '%s' in order to set quota, ignoring: %m", *p); + continue; + } + + /* Cover for bind mounted or symlinked /var/tmp/ + /tmp/ */ + if (set_contains(processed, DEVNUM_TO_PTR(st.st_dev))) { + log_debug("Not setting quota on '%s', since already processed.", *p); + continue; + } + + /* Remember we already dealt with this fs, even if the subsequent operation fails, since + * there's no point in appyling quota twice, regardless if it succeeds or not. */ + if (set_ensure_put(&processed, /* hash_ops= */ NULL, DEVNUM_TO_PTR(st.st_dev)) < 0) + return log_oom(); + + struct statfs sfs; + if (fstatfs(fd, &sfs) < 0) { + log_warning_errno(errno, "Failed to statfs '%s' in order to set quota, ignoring: %m", *p); + continue; + } + + if (!is_fs_type(&sfs, TMPFS_MAGIC)) { + log_debug("Not setting quota on '%s', since not tmpfs.", *p); + continue; + } + + struct dqblk req; + r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req)); + if (r == -ESRCH) + zero(req); + else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_debug_errno(r, "No UID quota support on %s, not setting quota: %m", *p); + continue; + } else if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_debug_errno(r, "Lacking privileges to query UID quota on %s, not setting quota: %m", *p); + continue; + } else if (r < 0) { + log_warning_errno(r, "Failed to query disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid); + continue; + } + + uint64_t v = + (scale == 0) ? 0 : + (scale == UINT32_MAX) ? UINT64_MAX : + (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) / scale * UINT32_MAX); + + v = MIN(v, limit); + v /= QIF_DQBLKSIZE; + + if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && v == req.dqb_bhardlimit) { + /* Shortcut things if everything is set up properly already */ + log_debug("Configured quota on '%s' already matches the intended setting, not updating quota.", *p); + continue; + } + + req.dqb_valid = QIF_BLIMITS; + req.dqb_bsoftlimit = req.dqb_bhardlimit = v; + + r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), uid, &req)); + if (r == -ESRCH) { + log_debug_errno(r, "Not setting UID quota on %s since UID quota is not supported: %m", *p); + continue; + } else if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_debug_errno(r, "Lacking privileges to set UID quota on %s, skipping: %m", *p); + continue; + } else if (r < 0) { + log_warning_errno(r, "Failed to set disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid); + continue; + } + + log_info("Successfully configured disk quota for UID " UID_FMT " on %s to %s", uid, *p, FORMAT_BYTES(v * QIF_DQBLKSIZE)); + } + + return 0; +} + +static int do_tmpfs_quota(UserRecord *ur) { + int r; + + assert(ur); + + if (user_record_is_root(ur)) { + log_debug("Not applying tmpfs quota to root user."); + return 0; + } + + if (!uid_is_valid(ur->uid)) + return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID, refusing.", ur->user_name); + + r = apply_tmpfs_quota(STRV_MAKE("/tmp", "/var/tmp"), ur->uid, ur->tmp_limit.limit, user_record_tmp_limit_scale(ur)); + if (r < 0) + return r; + + r = apply_tmpfs_quota(STRV_MAKE("/dev/shm"), ur->uid, ur->dev_shm_limit.limit, user_record_dev_shm_limit_scale(ur)); + if (r < 0) + return r; + + return 0; +} + static int run(int argc, char *argv[]) { int r; @@ -206,7 +326,10 @@ static int run(int argc, char *argv[]) { if (argc != 3) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes two arguments."); - if (!STR_IN_SET(argv[1], "start", "stop")) + + const char *verb = argv[1], *user = argv[2]; + + if (!STR_IN_SET(verb, "start", "stop")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "First argument must be either \"start\" or \"stop\"."); @@ -216,10 +339,26 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - if (streq(argv[1], "start")) - return do_mount(argv[2]); - if (streq(argv[1], "stop")) - return do_umount(argv[2]); + if (streq(verb, "start")) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + r = userdb_by_name(user, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur); + if (r == -ESRCH) + return log_error_errno(r, "User '%s' does not exist: %m", user); + if (r < 0) + return log_error_errno(r, "Failed to resolve user '%s': %m", user); + + /* We do two things here: mount the per-user XDG_RUNTIME_DIR, and set up tmpfs quota on /tmp/ + * and /dev/shm/. */ + + r = 0; + RET_GATHER(r, do_mount(ur)); + RET_GATHER(r, do_tmpfs_quota(ur)); + return r; + } + + if (streq(verb, "stop")) + return do_umount(user); + assert_not_reached(); } diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index cfb94841b9..fa4ba6a037 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -36,18 +36,21 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n" - "\n%sInitialize /etc/machine-id from a random source.%s\n\n" + printf("%1$s [OPTIONS...]\n" + "\n%2$sInitialize /etc/machine-id from a random source.%4$s\n" + "\n%3$sCommands:%4$s\n" + " --commit Commit transient ID\n" " -h --help Show this help\n" " --version Show package version\n" + "\n%3$sOptions:%4$s\n" " --root=PATH Operate on an alternate filesystem root\n" " --image=PATH Operate on disk image as filesystem root\n" " --image-policy=POLICY Specify disk image dissection policy\n" - " --commit Commit transient ID\n" " --print Print used machine ID\n" - "\nSee the %s for details.\n", + "\nSee the %5$s for details.\n", program_invocation_short_name, ansi_highlight(), + ansi_underline(), ansi_normal(), link); diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a5240ddccf..56dc0728d6 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -59,6 +59,10 @@ static int machine_leader(const char *name, sd_json_variant *variant, sd_json_di if (temp.pid == 1) /* refuse PID 1 */ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid leader PID.", strna(name)); + /* When both leader and leaderProcessId are specified, they must be consistent with each other. */ + if (pidref_is_set(leader) && !pidref_equal(leader, &temp)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' conflicts with already dispatched leader PID.", strna(name)); + pidref_done(leader); *leader = TAKE_PIDREF(temp); @@ -130,6 +134,7 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, service), 0 }, { "class", SD_JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), SD_JSON_MANDATORY }, { "leader", _SD_JSON_VARIANT_TYPE_INVALID, machine_leader, offsetof(Machine, leader), SD_JSON_STRICT }, + { "leaderProcessId", SD_JSON_VARIANT_OBJECT, machine_leader, offsetof(Machine, leader), SD_JSON_STRICT }, { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, root_directory), 0 }, { "ifIndices", SD_JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, { "vSockCid", _SD_JSON_VARIANT_TYPE_INVALID, machine_cid, offsetof(Machine, vsock_cid), 0 }, diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index dc293f5895..5beec14ccd 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -17,6 +17,7 @@ #include "varlink-io.systemd.Machine.h" #include "varlink-io.systemd.MachineImage.h" #include "varlink-io.systemd.UserDatabase.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" typedef struct LookupParameters { @@ -776,7 +777,8 @@ static int manager_varlink_init_machine(Manager *m) { r = sd_varlink_server_add_interface_many( s, &vl_interface_io_systemd_Machine, - &vl_interface_io_systemd_MachineImage); + &vl_interface_io_systemd_MachineImage, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Machine and MachineImage interfaces to varlink server: %m"); @@ -798,7 +800,10 @@ static int manager_varlink_init_machine(Manager *m) { "io.systemd.MachineImage.Update", vl_method_update_image, "io.systemd.MachineImage.Clone", vl_method_clone_image, "io.systemd.MachineImage.Remove", vl_method_remove_image, - "io.systemd.MachineImage.SetPoolLimit", vl_method_set_pool_limit); + "io.systemd.MachineImage.SetPoolLimit", vl_method_set_pool_limit, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/machine/operation.c b/src/machine/operation.c index 9e4e7e3dce..ef4e178a8c 100644 --- a/src/machine/operation.c +++ b/src/machine/operation.c @@ -50,7 +50,7 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat /* If o->done set, call it. It sends a response, but can return * an error in which case it expect this code to reply. * If o->done is not set, the default action is to simply return - * an error on failure or an empty success message on success.*/ + * an error on failure or an empty success message on success. */ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; if (o->done) @@ -73,7 +73,7 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat /* If o->done set, call it. Unlike o->message case above, this * code expect o->done to reply in all cases. * If o->done is not set, the default action is to simply return - * an error on failure or an empty success message on success.*/ + * an error on failure or an empty success message on success. */ if (o->done) (void) o->done(o, r, /* error = */ NULL); diff --git a/src/measure/measure.c b/src/measure/measure.c index fceca7c4a1..dcae8528e0 100644 --- a/src/measure/measure.c +++ b/src/measure/measure.c @@ -77,6 +77,7 @@ static int help(int argc, char *argv[], void *userdata) { " status Show current PCR values\n" " calculate Calculate expected PCR values\n" " sign Calculate and sign expected PCR values\n" + " policy-digest Calculate expected TPM2 policy digests\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Print version\n" @@ -826,7 +827,7 @@ static int verb_calculate(int argc, char *argv[], void *userdata) { return 0; } -static int verb_sign(int argc, char *argv[], void *userdata) { +static int build_policy_digest(bool sign) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; @@ -839,7 +840,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing."); - if (!arg_private_key) + if (sign && !arg_private_key) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No private key specified, use --private-key=."); @@ -856,7 +857,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { "File '%s' is not a valid JSON object, refusing.", arg_append); } - /* When signing we only support JSON output */ + /* When signing/building digest we only support JSON output */ arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; /* This must be done before openssl_load_private_key() otherwise it will get stuck */ @@ -918,11 +919,11 @@ static int verb_sign(int argc, char *argv[], void *userdata) { SYNTHETIC_ERRNO(EIO), "Failed to extract public key from certificate %s.", arg_certificate); - } else { + } else if (sign) { _cleanup_(memstream_done) MemStream m = {}; FILE *tf; - /* No public key was specified, let's derive it automatically, if we can */ + /* No public key was specified, let's derive it automatically, if we can, when signing */ tf = memstream_init(&m); if (!tf) @@ -978,17 +979,20 @@ static int verb_sign(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Could not calculate PolicyPCR digest: %m"); _cleanup_free_ void *sig = NULL; - size_t ss; - - r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); - if (r < 0) - return log_error_errno(r, "Failed to sign PCR policy: %m"); + size_t ss = 0; + if (privkey) { + r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); + if (r < 0) + return log_error_errno(r, "Failed to sign PCR policy: %m"); + } _cleanup_free_ void *pubkey_fp = NULL; size_t pubkey_fp_size = 0; - r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); - if (r < 0) - return r; + if (pubkey) { + r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); + if (r < 0) + return r; + } _cleanup_(sd_json_variant_unrefp) sd_json_variant *a = NULL; r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM2_PCR_KERNEL_BOOT, &a); @@ -997,10 +1001,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL; r = sd_json_buildo(&bv, - SD_JSON_BUILD_PAIR("pcrs", SD_JSON_BUILD_VARIANT(a)), /* PCR mask */ - SD_JSON_BUILD_PAIR("pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */ - SD_JSON_BUILD_PAIR("pol", SD_JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */ - SD_JSON_BUILD_PAIR("sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */ + SD_JSON_BUILD_PAIR("pcrs", SD_JSON_BUILD_VARIANT(a)), /* PCR mask */ + SD_JSON_BUILD_PAIR_CONDITION(pubkey_fp_size > 0, "pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */ + SD_JSON_BUILD_PAIR("pol", SD_JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */ + SD_JSON_BUILD_PAIR_CONDITION(ss > 0, "sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */ if (r < 0) return log_error_errno(r, "Failed to build JSON object: %m"); @@ -1028,6 +1032,14 @@ static int verb_sign(int argc, char *argv[], void *userdata) { return 0; } +static int verb_sign(int argc, char *argv[], void *userdata) { + return build_policy_digest(/* sign= */ true); +} + +static int verb_policy_digest(int argc, char *argv[], void *userdata) { + return build_policy_digest(/* sign= */ false); +} + static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { _cleanup_free_ char *s = NULL; uint32_t v; @@ -1179,10 +1191,11 @@ static int verb_status(int argc, char *argv[], void *userdata) { static int measure_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "calculate", VERB_ANY, 1, 0, verb_calculate }, - { "sign", VERB_ANY, 1, 0, verb_sign }, + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "calculate", VERB_ANY, 1, 0, verb_calculate }, + { "policy-digest", VERB_ANY, 1, 0, verb_policy_digest }, + { "sign", VERB_ANY, 1, 0, verb_sign }, {} }; diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 5f9cf44b5d..d1b4773ce8 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -805,7 +805,7 @@ static int find_mount_points(const char *what, char ***list) { /* Returns all mount points obtained from /proc/self/mountinfo in *list, * and the number of mount points as return value. */ - r = libmount_parse(NULL, NULL, &table, &iter); + r = libmount_parse_mountinfo(/* source = */ NULL, &table, &iter); if (r < 0) return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); diff --git a/src/mountfsd/io.systemd.mount-file-system.policy b/src/mountfsd/io.systemd.mount-file-system.policy index 6a151eb437..78613bfdaf 100644 --- a/src/mountfsd/io.systemd.mount-file-system.policy +++ b/src/mountfsd/io.systemd.mount-file-system.policy @@ -67,4 +67,53 @@ <annotate key="org.freedesktop.policykit.imply">io.systemd.mount-file-system.mount-image-privately</annotate> </action> + + <!-- Allow mounting directories into the host user namespace --> + <action id="io.systemd.mount-file-system.mount-directory"> + <!-- If the directory is owned by the user (or by the foreign UID range, with a parent + directory owned by the user), make little restrictions --> + <description gettext-domain="systemd">Allow mounting of directory</description> + <message gettext-domain="systemd">Authentication is required for an application to mount directory $(directory).</message> + <defaults> + <allow_any>auth_admin_keep</allow_any> + <allow_inactive>auth_admin_keep</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + </action> + + <action id="io.systemd.mount-file-system.mount-untrusted-directory"> + <!-- If the directory is owned by an other user, require authentication --> + <description gettext-domain="systemd">Allow mounting of untrusted directory</description> + <message gettext-domain="systemd">Authentication is required for an application to mount directory $(directory) which is not owned by the user.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin</allow_active> + </defaults> + + <annotate key="org.freedesktop.policykit.imply">io.systemd.mount-file-system.mount-directory</annotate> + </action> + + <!-- Allow mounting directories into a private user namespace --> + <action id="io.systemd.mount-file-system.mount-directory-privately"> + <description gettext-domain="systemd">Allow private mounting of directory</description> + <message gettext-domain="systemd">Authentication is required for an application to privately mount directory $(directory).</message> + <defaults> + <allow_any>yes</allow_any> + <allow_inactive>yes</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + </action> + + <action id="io.systemd.mount-file-system.mount-untrusted-directory-privately"> + <description gettext-domain="systemd">Allow private mounting of untrusted directory</description> + <message gettext-domain="systemd">Authentication is required for an application to privately mount directory $(directory) which is not owned by the user.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin</allow_active> + </defaults> + + <annotate key="org.freedesktop.policykit.imply">io.systemd.mount-file-system.mount-directory-privately</annotate> + </action> </policyconfig> diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 9b674c83b5..2fd610f5d6 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -1,5 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include <sys/mount.h> +#if WANT_LINUX_FS_H +#include <linux/fs.h> +#endif + #include "sd-daemon.h" #include "sd-varlink.h" @@ -15,12 +20,17 @@ #include "json-util.h" #include "main-func.h" #include "missing_loop.h" +#include "missing_mount.h" +#include "missing_syscall.h" #include "namespace-util.h" #include "nsresource.h" #include "nulstr-util.h" #include "os-util.h" #include "process-util.h" #include "stat-util.h" +#include "string-table.h" +#include "uid-classification.h" +#include "uid-range.h" #include "user-util.h" #include "varlink-io.systemd.MountFileSystem.h" #include "varlink-util.h" @@ -532,6 +542,349 @@ static int vl_method_mount_image( SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(di->image_uuid), "imageUuid", SD_JSON_BUILD_UUID(di->image_uuid))); } +typedef enum MountMapMode { + MOUNT_MAP_AUTO = 0, /* determine automatically from image and caller */ + MOUNT_MAP_ROOT, /* map caller's UID to root in namespace (map 1 UID only) */ + MOUNT_MAP_FOREIGN, /* map foreign UID range to base in namespace (map 64K) */ + MOUNT_MAP_IDENTITY, /* apply identity mapping (map 64K) */ + _MOUNT_MAP_MODE_MAX, + _MOUNT_MAP_MODE_INVALID = -EINVAL, +} MountMapMode; + +static const char *const mount_map_mode_table[_MOUNT_MAP_MODE_MAX] = { + [MOUNT_MAP_AUTO] = "auto", + [MOUNT_MAP_ROOT] = "root", + [MOUNT_MAP_FOREIGN] = "foreign", + [MOUNT_MAP_IDENTITY] = "identity", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(mount_map_mode, MountMapMode); + +typedef struct MountDirectoryParameters { + MountMapMode mode; + unsigned directory_fd_idx; + unsigned userns_fd_idx; + int read_only; +} MountDirectoryParameters; + +typedef enum DirectoryOwnership { + DIRECTORY_IS_ROOT_PEER_OWNED, /* This is returned if the directory is owned by the root user and the peer is root */ + DIRECTORY_IS_ROOT_OWNED, /* This is returned if the directory is owned by the root user (and the peer user is not root) */ + DIRECTORY_IS_PEER_OWNED, /* This is returned if the directory is owned by the peer user (who is not root) */ + DIRECTORY_IS_FOREIGN_OWNED, /* This is returned if the directory is owned by the foreign UID range */ + DIRECTORY_IS_OTHERWISE_OWNED, /* This is returned if the directory is owned by something else */ + _DIRECTORY_OWNERSHIP_MAX, + _DIRECTORY_OWNERSHIP_ERRNO_MAX = -ERRNO_MAX, /* Guarantee the whole negative errno range fits */ +} DirectoryOwnership; + +static MountMapMode default_mount_map_mode(DirectoryOwnership ownership) { + /* Derives a suitable mapping mode from the ownership of the base tree */ + + switch (ownership) { + case DIRECTORY_IS_PEER_OWNED: + return MOUNT_MAP_ROOT; /* Map the peer's UID to root in the container */ + + case DIRECTORY_IS_FOREIGN_OWNED: + return MOUNT_MAP_FOREIGN; /* Map the foreign UID range to the container's UID range */ + + case DIRECTORY_IS_ROOT_PEER_OWNED: + case DIRECTORY_IS_ROOT_OWNED: + case DIRECTORY_IS_OTHERWISE_OWNED: + return MOUNT_MAP_IDENTITY; /* Don't map */ + + default: + return _MOUNT_MAP_MODE_INVALID; + } +} + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_mount_directory_mode, MountMapMode, mount_map_mode_from_string); + +static DirectoryOwnership validate_directory_fd(int fd, uid_t peer_uid) { + int r, fl; + + assert(fd >= 0); + + /* Checks if the specified directory fd looks sane. Returns a DirectoryOwnership that categorizes the + * ownership situation in comparison to the peer's UID. + * + * Note one key difference to image validation (as implemented above): for regular files if the + * client provided us with an open fd it implies the client has access, as well as what kind of + * access (i.e. ro or rw). But for directories this doesn't work the same way, as directories are + * always opened read-only only. Hence we use a different mechanism to validate access to them: we + * check if the directory is owned by the peer UID or by the foreign UID range (in the latter case + * one of the parent directories must be owned by the peer though). */ + + struct stat st; + if (fstat(fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat() directory fd: %m"); + + r = stat_verify_directory(&st); + if (r < 0) + return r; + + fl = fd_verify_safe_flags_full(fd, O_DIRECTORY); + if (fl < 0) + return log_debug_errno(fl, "Directory file descriptor has unsafe flags set: %m"); + + if (st.st_uid == 0) { + if (peer_uid == 0) { + log_debug("Directory file descriptor points to root owned directory, who is also the peer."); + return DIRECTORY_IS_ROOT_PEER_OWNED; + } + log_debug("Directory file descriptor points to root owned directory."); + return DIRECTORY_IS_ROOT_OWNED; + } + if (st.st_uid == peer_uid) { + log_debug("Directory file descriptor points to peer owned directory."); + return DIRECTORY_IS_PEER_OWNED; + } + + /* For bind mounted directories we check if they are either owned by the client's UID, or by the + * foreign UID set, but in that case the parent directory must be owned by the client's UID, or some + * directory iteratively up the chain */ + + _cleanup_close_ int parent_fd = -EBADF; + unsigned n_level; + for (n_level = 0; n_level < 16; n_level++) { + /* Stop iteration if we find a directory up the tree that is neither owned by the user, nor is from the foreign UID range */ + if (!uid_is_foreign(st.st_uid) || !gid_is_foreign(st.st_gid)) { + log_debug("Directory file descriptor points to directory which itself or its parents is neither owned by foreign UID range nor by the user."); + return DIRECTORY_IS_OTHERWISE_OWNED; + } + + /* If the peer is root, then it doesn't matter if we find a parent owned by root, let's shortcut things. */ + if (peer_uid == 0) { + log_debug("Directory file descriptor is owned by foreign UID range, and peer is root."); + return DIRECTORY_IS_FOREIGN_OWNED; + } + + /* Go one level up */ + _cleanup_close_ int new_parent_fd = openat(fd, "..", O_DIRECTORY|O_PATH|O_CLOEXEC); + if (new_parent_fd < 0) + return log_debug_errno(errno, "Failed to open parent directory of directory file descriptor: %m"); + + struct stat new_st; + if (fstat(new_parent_fd, &new_st) < 0) + return log_debug_errno(errno, "Failed to stat parent directory of directory file descriptor: %m"); + + /* Safety check to see if we hit the root dir */ + if (stat_inode_same(&st, &new_st)) { + log_debug("Directory file descriptor is owned by foreign UID range, but didn't find parent directory that is owned by peer among ancestors."); + return DIRECTORY_IS_OTHERWISE_OWNED; + } + + if (new_st.st_uid == peer_uid) { /* Parent inode is owned by the peer. That's good! Everything's fine. */ + log_debug("Directory file descriptor is owned by foreign UID range, and ancestor is owned by peer."); + return DIRECTORY_IS_FOREIGN_OWNED; + } + + close_and_replace(parent_fd, new_parent_fd); + st = new_st; + } + + log_debug("Failed to find peer owned parent directory after %u levels, refusing.", n_level); + return DIRECTORY_IS_OTHERWISE_OWNED; +} + +static int vl_method_mount_directory( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "mode", SD_JSON_VARIANT_STRING, dispatch_mount_directory_mode, offsetof(MountDirectoryParameters, mode), 0 }, + { "directoryFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountDirectoryParameters, directory_fd_idx), SD_JSON_MANDATORY }, + { "userNamespaceFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountDirectoryParameters, userns_fd_idx), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountDirectoryParameters, read_only), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + MountDirectoryParameters p = { + .mode = MOUNT_MAP_AUTO, + .directory_fd_idx = UINT_MAX, + .userns_fd_idx = UINT_MAX, + .read_only = -1, + }; + _cleanup_close_ int directory_fd = -EBADF, userns_fd = -EBADF; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + int r; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.directory_fd_idx == UINT_MAX) + return sd_varlink_error_invalid_parameter_name(link, "directoryFileDescriptor"); + + directory_fd = sd_varlink_peek_dup_fd(link, p.directory_fd_idx); + if (directory_fd < 0) + return log_debug_errno(directory_fd, "Failed to peek directory fd from client: %m"); + + if (p.userns_fd_idx != UINT_MAX) { + userns_fd = sd_varlink_peek_dup_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to peek user namespace fd from client: %m"); + } + + uid_t peer_uid; + r = sd_varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return log_debug_errno(r, "Failed to get client UID: %m"); + + DirectoryOwnership owned_by = validate_directory_fd(directory_fd, peer_uid); + if (owned_by < 0) + return owned_by; + + r = validate_userns(link, &userns_fd); + if (r != 0) + return r; + + /* If no mode is specified, pick sensible default */ + if (p.mode <= 0) { + p.mode = default_mount_map_mode(owned_by); + assert(p.mode > 0); + } + + _cleanup_free_ char *directory_path = NULL; + (void) fd_get_path(directory_fd, &directory_path); + + log_debug("Mounting '%s' with mapping mode: %s", strna(directory_path), mount_map_mode_to_string(p.mode)); + + const char *polkit_details[] = { + "read_only", one_zero(p.read_only > 0), + "directory", strna(directory_path), + NULL, + }; + + const char *polkit_action, *polkit_untrusted_action; + PolkitFlags polkit_flags; + if (userns_fd < 0) { + /* Mount into the host user namespace */ + polkit_action = "io.systemd.mount-file-system.mount-directory"; + polkit_untrusted_action = "io.systemd.mount-file-system.mount-untrusted-directory"; + polkit_flags = 0; + } else { + /* Mount into a private user namespace */ + polkit_action = "io.systemd.mount-file-system.mount-directory-privately"; + polkit_untrusted_action = "io.systemd.mount-file-system.mount-untrusted-directory-privately"; + + /* If polkit is not around, let's allow mounting authenticated images by default */ + polkit_flags = POLKIT_DEFAULT_ALLOW; + } + + /* We consider a directory "trusted" if it is owned by the peer or the foreign UID range */ + bool trusted_directory = IN_SET(owned_by, DIRECTORY_IS_ROOT_PEER_OWNED, DIRECTORY_IS_PEER_OWNED, DIRECTORY_IS_FOREIGN_OWNED); + + /* Let's definitely acquire the regular action privilege, for mounting properly signed images */ + r = varlink_verify_polkit_async_full( + link, + /* bus= */ NULL, + trusted_directory ? polkit_action : polkit_untrusted_action, + polkit_details, + /* good_user= */ UID_INVALID, + trusted_directory ? polkit_flags : 0, + polkit_registry); + if (r <= 0) + return r; + + /* Generate the common dissection directory here. We are not going to use it, but the clients might, + * and they likely are unprivileged, hence cannot create it themselves. Hence let's just create it + * here, if it is missing. */ + r = get_common_dissect_directory(NULL); + if (r < 0) + return r; + + _cleanup_close_ int mount_fd = open_tree(directory_fd, "", OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH); + if (mount_fd < 0) + return log_debug_errno(errno, "Failed to issue open_tree() of provided directory '%s': %m", strna(directory_path)); + + if (p.read_only > 0 && mount_setattr( + mount_fd, "", AT_EMPTY_PATH, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_RDONLY, + }, MOUNT_ATTR_SIZE_VER0) < 0) + return log_debug_errno(errno, "Failed to enable read-only mode: %m"); + + if (p.mode != MOUNT_MAP_IDENTITY) { + uid_t start; + + if (userns_fd >= 0) { + _cleanup_(uid_range_freep) UIDRange *uid_range_outside = NULL, *uid_range_inside = NULL, *gid_range_outside = NULL, *gid_range_inside = NULL; + r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_OUTSIDE, &uid_range_outside); + if (r < 0) + return log_debug_errno(r, "Failed to load outside UID range of provided userns: %m"); + r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_INSIDE, &uid_range_inside); + if (r < 0) + return log_debug_errno(r, "Failed to load inside UID range of provided userns: %m"); + r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_OUTSIDE, &gid_range_outside); + if (r < 0) + return log_debug_errno(r, "Failed to load outside GID range of provided userns: %m"); + r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_INSIDE, &gid_range_inside); + if (r < 0) + return log_debug_errno(r, "Failed to load inside GID range of provided userns: %m"); + + /* Be very strict for now */ + if (!uid_range_equal(uid_range_outside, gid_range_outside) || + !uid_range_equal(uid_range_inside, gid_range_inside) || + uid_range_outside->n_entries != 1 || + uid_range_outside->entries[0].nr != 0x10000 || + uid_range_inside->n_entries != 1 || + uid_range_inside->entries[0].start != 0 || + uid_range_inside->entries[0].nr != 0x10000) + return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + start = uid_range_outside->entries[0].start; + } else + start = 0; + + _cleanup_free_ char *new_uid_map = NULL; + switch (p.mode) { + case MOUNT_MAP_ROOT: + r = strextendf(&new_uid_map, UID_FMT " " UID_FMT " " UID_FMT, + peer_uid, start, (uid_t) 1); + break; + case MOUNT_MAP_FOREIGN: + r = strextendf(&new_uid_map, UID_FMT " " UID_FMT " " UID_FMT, + (uid_t) FOREIGN_UID_MIN, start, (uid_t) 0x10000); + break; + default: + assert_not_reached(); + } + if (r < 0) + return r; + + _cleanup_close_ int idmap_userns_fd = userns_acquire(new_uid_map, new_uid_map); + if (idmap_userns_fd < 0) + return log_debug_errno(idmap_userns_fd, "Failed to acquire user namespace for id mapping: %m"); + + if (mount_setattr(mount_fd, "", AT_EMPTY_PATH, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = idmap_userns_fd, + .propagation = MS_PRIVATE, + }, MOUNT_ATTR_SIZE_VER0) < 0) + return log_debug_errno(errno, "Failed to enable id mapping: %m"); + } + + if (userns_fd >= 0) { + r = nsresource_add_mount(userns_fd, mount_fd); + if (r < 0) + return r; + } + + int fd_idx = sd_varlink_push_fd(link, mount_fd); + if (fd_idx < 0) + return fd_idx; + + TAKE_FD(mount_fd); + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR("mountFileDescriptor", SD_JSON_BUILD_INTEGER(fd_idx))); +} + static int process_connection(sd_varlink_server *server, int _fd) { _cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */ _cleanup_(sd_varlink_close_unrefp) sd_varlink *vl = NULL; @@ -567,7 +920,7 @@ static int process_connection(sd_varlink_server *server, int _fd) { static int run(int argc, char *argv[]) { usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY; _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL; - _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; unsigned n_iterations = 0; int m, listen_fd, r; @@ -601,7 +954,8 @@ static int run(int argc, char *argv[]) { r = sd_varlink_server_bind_method_many( server, - "io.systemd.MountFileSystem.MountImage", vl_method_mount_image); + "io.systemd.MountFileSystem.MountImage", vl_method_mount_image, + "io.systemd.MountFileSystem.MountDirectory", vl_method_mount_directory); if (r < 0) return log_error_errno(r, "Failed to bind methods: %m"); diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c index d9ac78a1a9..8e4d6826a8 100644 --- a/src/network/networkd-address-pool.c +++ b/src/network/networkd-address-pool.c @@ -32,7 +32,7 @@ static int address_pool_new( .in_addr = *u, }; - r = ordered_set_ensure_put(&m->address_pools, NULL, p); + r = ordered_set_ensure_put(&m->address_pools, &trivial_hash_ops_free, p); if (r < 0) return r; diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c index b2a196c5fc..3182c0094e 100644 --- a/src/network/networkd-manager-varlink.c +++ b/src/network/networkd-manager-varlink.c @@ -12,15 +12,18 @@ #include "networkd-manager-varlink.h" #include "stat-util.h" #include "varlink-io.systemd.Network.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" static int vl_method_get_states(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *m = ASSERT_PTR(userdata); + int r; assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; return sd_varlink_replybo( link, @@ -39,8 +42,9 @@ static int vl_method_get_namespace_id(sd_varlink *link, sd_json_variant *paramet assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; /* Network namespaces have two identifiers: the inode number (which all namespace types have), and * the "nsid" (aka the "cookie"), which only network namespaces know as a concept, and which is not @@ -269,16 +273,22 @@ int manager_connect_varlink(Manager *m) { (void) sd_varlink_server_set_description(s, "varlink-api-network"); - r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Network); + r = sd_varlink_server_add_interface_many( + s, + &vl_interface_io_systemd_Network, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Network interface to varlink server: %m"); r = sd_varlink_server_bind_method_many( s, - "io.systemd.Network.GetStates", vl_method_get_states, - "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, - "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, - "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage); + "io.systemd.Network.GetStates", vl_method_get_states, + "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, + "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, + "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 8061976d7b..1592e258ec 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -230,9 +230,6 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd) { if (n < 0) return n; - if (strv_length(names) != (size_t) n) - return -EINVAL; - for (int i = 0; i < n; i++) { int fd = i + SD_LISTEN_FDS_START; @@ -695,7 +692,7 @@ Manager* manager_free(Manager *m) { m->wiphy_by_name = hashmap_free(m->wiphy_by_name); m->wiphy_by_index = hashmap_free_with_destructor(m->wiphy_by_index, wiphy_free); - ordered_set_free_free(m->address_pools); + ordered_set_free(m->address_pools); hashmap_free(m->route_table_names_by_number); hashmap_free(m->route_table_numbers_by_name); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index e778300c70..42bed0d642 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -1375,7 +1375,7 @@ static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) { * the first Router Advertisement was received. * * If the received Cur Hop Limit value is non-zero, the host SHOULD set - * its CurHopLimit variable to the received value.*/ + * its CurHopLimit variable to the received value. */ if (hop_limit <= 0) return 0; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9a25be666b..83ef927b8b 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -408,7 +408,7 @@ IPv6SendRA.ReachableTimeSec, config_parse_router_uint32_msec_use IPv6SendRA.RetransmitSec, config_parse_router_uint32_msec_usec, 0, offsetof(Network, router_retransmit_usec) IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) -IPv6SendRA.RouterPreference, config_parse_router_preference, 0, 0 +IPv6SendRA.RouterPreference, config_parse_router_preference, 0, offsetof(Network, router_preference) IPv6SendRA.HopLimit, config_parse_uint8, 0, offsetof(Network, router_hop_limit) IPv6SendRA.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns) IPv6SendRA.DNS, config_parse_radv_dns, 0, 0 @@ -429,6 +429,7 @@ IPv6Prefix.RouteMetric, config_parse_prefix_metric, IPv6Prefix.Token, config_parse_prefix_token, 0, 0 IPv6RoutePrefix.Route, config_parse_route_prefix, 0, 0 IPv6RoutePrefix.LifetimeSec, config_parse_route_prefix_lifetime, 0, 0 +IPv6RoutePrefix.Preference, config_parse_route_prefix_preference, 0, 0 IPv6PREF64Prefix.Prefix, config_parse_pref64_prefix, 0, 0 IPv6PREF64Prefix.LifetimeSec, config_parse_pref64_prefix_lifetime, 0, 0 LLDP.MUDURL, config_parse_mud_url, 0, offsetof(Network, lldp_mudurl) @@ -598,7 +599,7 @@ Network.DHCPv6PrefixDelegation, config_parse_tristate, IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec) IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) -IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0 +IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, offsetof(Network, router_preference) IPv6PrefixDelegation.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns) IPv6PrefixDelegation.DNS, config_parse_radv_dns, 0, 0 IPv6PrefixDelegation.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3ecddff129..7de0027aae 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -170,7 +170,7 @@ int network_verify(Network *network) { network->bond_name = mfree(network->bond_name); network->bridge_name = mfree(network->bridge_name); network->vrf_name = mfree(network->vrf_name); - network->stacked_netdev_names = hashmap_free_free_key(network->stacked_netdev_names); + network->stacked_netdev_names = hashmap_free(network->stacked_netdev_names); if (network->bond) { /* Bonding slave does not support addressing. */ @@ -818,7 +818,7 @@ static Network *network_free(Network *network) { free(network->bridge_name); free(network->bond_name); free(network->vrf_name); - hashmap_free_free_key(network->stacked_netdev_names); + hashmap_free(network->stacked_netdev_names); netdev_unref(network->bridge); netdev_unref(network->bond); netdev_unref(network->vrf); @@ -949,7 +949,7 @@ int config_parse_stacked_netdev( if (!name) return log_oom(); - r = hashmap_ensure_put(h, &string_hash_ops, name, INT_TO_PTR(kind)); + r = hashmap_ensure_put(h, &string_hash_ops_free, name, INT_TO_PTR(kind)); if (r == -ENOMEM) return log_oom(); if (r < 0) diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 18b130b959..c962e882d8 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -97,7 +97,7 @@ static NextHop* nexthop_free(NextHop *nexthop) { nexthop_detach_impl(nexthop); config_section_free(nexthop->section); - hashmap_free_free(nexthop->group); + hashmap_free(nexthop->group); set_free(nexthop->nexthops); set_free(nexthop->routes); @@ -271,7 +271,7 @@ static int nexthop_dup(const NextHop *src, NextHop **ret) { if (!g) return -ENOMEM; - r = hashmap_ensure_put(&dest->group, NULL, UINT32_TO_PTR(g->id), g); + r = hashmap_ensure_put(&dest->group, &trivial_hash_ops_value_free, UINT32_TO_PTR(g->id), g); if (r < 0) return r; if (r > 0) @@ -894,7 +894,7 @@ static bool nexthop_can_update(const NextHop *assigned_nexthop, const NextHop *r /* There are several more conditions if we can replace a group nexthop, e.g. hash threshold and * resilience. But, currently we do not support to modify that. Let's add checks for them in the - * future when we support to configure them.*/ + * future when we support to configure them. */ /* When a nexthop is replaced with a blackhole nexthop, and a group nexthop has multiple nexthops * including this nexthop, then the kernel refuses to replace the existing nexthop. @@ -1018,7 +1018,7 @@ void link_forget_nexthops(Link *link) { } static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) { - _cleanup_hashmap_free_free_ Hashmap *h = NULL; + _cleanup_hashmap_free_ Hashmap *h = NULL; _cleanup_free_ struct nexthop_grp *group = NULL; size_t size = 0, n_group; int r; @@ -1058,7 +1058,7 @@ static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) { if (!nhg) return log_oom(); - r = hashmap_ensure_put(&h, NULL, UINT32_TO_PTR(nhg->id), nhg); + r = hashmap_ensure_put(&h, &trivial_hash_ops_value_free, UINT32_TO_PTR(nhg->id), nhg); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -1069,9 +1069,7 @@ static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) { TAKE_PTR(nhg); } - hashmap_free_free(nexthop->group); - nexthop->group = TAKE_PTR(h); - + hashmap_free_and_replace(nexthop->group, h); nexthop_attach_to_group_members(nexthop); return 0; } @@ -1377,7 +1375,7 @@ static int config_parse_nexthop_group( int r; if (isempty(rvalue)) { - *group = hashmap_free_free(*group); + *group = hashmap_free(*group); return 1; } @@ -1431,7 +1429,7 @@ static int config_parse_nexthop_group( continue; } - r = hashmap_ensure_put(group, NULL, UINT32_TO_PTR(nhg->id), nhg); + r = hashmap_ensure_put(group, &trivial_hash_ops_value_free, UINT32_TO_PTR(nhg->id), nhg); if (r == -ENOMEM) return log_oom(); if (r == -EEXIST) { diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index deb37c4850..fbf87091a3 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -1134,6 +1134,37 @@ int config_parse_route_prefix_lifetime( return 0; } +int config_parse_route_prefix_preference( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(route_prefix_free_or_set_invalidp) RoutePrefix *p = NULL; + Network *network = ASSERT_PTR(userdata); + int r; + + assert(filename); + + r = route_prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return log_oom(); + + r = config_parse_router_preference(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, &p->route.preference, NULL); + if (r <= 0) + return r; + + TAKE_PTR(p); + return 0; +} + int config_parse_pref64_prefix( const char *unit, const char *filename, @@ -1511,25 +1542,18 @@ int config_parse_router_preference( void *data, void *userdata) { - Network *network = userdata; + uint8_t *preference = ASSERT_PTR(data); - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - if (streq(rvalue, "high")) - network->router_preference = SD_NDISC_PREFERENCE_HIGH; - else if (STR_IN_SET(rvalue, "medium", "normal", "default")) - network->router_preference = SD_NDISC_PREFERENCE_MEDIUM; + if (isempty(rvalue) || STR_IN_SET(rvalue, "medium", "normal", "default")) + *preference = SD_NDISC_PREFERENCE_MEDIUM; + else if (streq(rvalue, "high")) + *preference = SD_NDISC_PREFERENCE_HIGH; else if (streq(rvalue, "low")) - network->router_preference = SD_NDISC_PREFERENCE_LOW; + *preference = SD_NDISC_PREFERENCE_LOW; else - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid router preference, ignoring assignment: %s", rvalue); + return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue); - return 0; + return 1; } int config_parse_router_home_agent_lifetime( diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index afde1fcfe2..3f0a374632 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -86,6 +86,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_radv_dns); CONFIG_PARSER_PROTOTYPE(config_parse_radv_search_domains); CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix_lifetime); +CONFIG_PARSER_PROTOTYPE(config_parse_route_prefix_preference); CONFIG_PARSER_PROTOTYPE(config_parse_pref64_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_pref64_prefix_lifetime); CONFIG_PARSER_PROTOTYPE(config_parse_router_home_agent_lifetime); diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c index 6f5aef903a..48c4d1c0ee 100644 --- a/src/network/wait-online/wait-online.c +++ b/src/network/wait-online/wait-online.c @@ -23,7 +23,7 @@ static LinkOperationalStateRange arg_required_operstate = LINK_OPERSTATE_RANGE_I static AddressFamily arg_required_family = ADDRESS_FAMILY_NO; static bool arg_any = false; -STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_free_free_freep); +STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_freep); STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep); static int help(void) { @@ -85,7 +85,7 @@ static int parse_interface_with_operstate_range(const char *str) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid interface name: %s", ifname); - r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, range); + r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops_free_free, ifname, range); if (r == -ENOMEM) return log_oom(); if (r < 0) diff --git a/src/notify/notify.c b/src/notify/notify.c index 6afb9560c6..1c5b5e7be3 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -80,28 +80,49 @@ static int help(void) { return 0; } -static pid_t manager_pid(void) { - const char *e; - pid_t pid; +static int get_manager_pid(PidRef *ret) { int r; + assert(ret); + /* If we run as a service managed by systemd --user the $MANAGERPID environment variable points to * the service manager's PID. */ - e = getenv("MANAGERPID"); - if (!e) + const char *e = getenv("MANAGERPID"); + if (!e) { + *ret = PIDREF_NULL; return 0; + } - r = parse_pid(e, &pid); - if (r < 0) { - log_warning_errno(r, "$MANAGERPID is set to an invalid PID, ignoring: %s", e); - return 0; + _cleanup_(pidref_done) PidRef manager = PIDREF_NULL; + r = pidref_set_pidstr(&manager, e); + if (r < 0) + return log_warning_errno(r, "$MANAGERPID is set to an invalid PID, ignoring: %s", e); + + e = getenv("MANAGERPIDFDID"); + if (e) { + uint64_t manager_pidfd_id; + + r = safe_atou64(e, &manager_pidfd_id); + if (r < 0) + return log_warning_errno(r, "$MANAGERPIDFDID is not set to a valid inode number, ignoring: %s", e); + + r = pidref_acquire_pidfd_id(&manager); + if (r < 0) + return log_warning_errno(r, "Unable to acquire pidfd ID for manager: %m"); + + if (manager_pidfd_id != manager.fd_id) { + log_debug("$MANAGERPIDFDID doesn't match process currently referenced by $MANAGERPID, suppressing."); + *ret = PIDREF_NULL; + return 0; + } } - return pid; + *ret = TAKE_PIDREF(manager); + return 1; } static int pidref_parent_if_applicable(PidRef *ret) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL, manager = PIDREF_NULL; int r; assert(ret); @@ -112,12 +133,18 @@ static int pidref_parent_if_applicable(PidRef *ret) { /* Don't send from PID 1 or the service manager's PID (which might be distinct from 1, if we are a * --user service). That'd just be confusing for the service manager. */ - if (pidref.pid <= 1 || - pidref.pid == manager_pid()) - return pidref_set_self(ret); + if (pidref.pid == 1) + goto from_self; + + r = get_manager_pid(&manager); + if (r > 0 && pidref_equal(&pidref, &manager)) + goto from_self; *ret = TAKE_PIDREF(pidref); return 0; + +from_self: + return pidref_set_self(ret); } static int parse_argv(int argc, char *argv[]) { diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c index 6d6a8a814b..4ee21c0779 100644 --- a/src/nspawn/nspawn-cgroup.c +++ b/src/nspawn/nspawn-cgroup.c @@ -119,7 +119,7 @@ int create_subcgroup( CGroupUnified unified_requested, uid_t uid_shift, int userns_fd, - bool privileged) { + UserNamespaceMode userns_mode) { _cleanup_free_ char *cgroup = NULL, *payload = NULL; CGroupMask supported; @@ -163,14 +163,14 @@ int create_subcgroup( if (!payload) return log_oom(); - if (privileged) + if (userns_mode != USER_NAMESPACE_MANAGED) r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, payload, pid); else r = cg_create(SYSTEMD_CGROUP_CONTROLLER, payload); if (r < 0) return log_error_errno(r, "Failed to create %s subcgroup: %m", payload); - if (privileged) { + if (userns_mode != USER_NAMESPACE_MANAGED) { _cleanup_free_ char *fs = NULL; r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, payload, NULL, &fs); if (r < 0) diff --git a/src/nspawn/nspawn-cgroup.h b/src/nspawn/nspawn-cgroup.h index 7e2cd53ddc..8f039ffb28 100644 --- a/src/nspawn/nspawn-cgroup.h +++ b/src/nspawn/nspawn-cgroup.h @@ -5,9 +5,10 @@ #include <sys/types.h> #include "cgroup-util.h" +#include "nspawn-settings.h" int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift); -int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested, uid_t uid_shift, int userns_fd, bool privileged); +int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested, uid_t uid_shift, int userns_fd, UserNamespaceMode userns_mode); int mount_cgroups(const char *dest, CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns); int mount_systemd_cgroup_writable(const char *dest, CGroupUnified unified_requested); diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index f64d6b48a6..552d629a18 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -588,17 +588,17 @@ int mount_all(const char *dest, /* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing when we are privileged) */ { "tmpfs", "/tmp", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME, - MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR }, + MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR|MOUNT_USRQUOTA_GRACEFUL }, { "tmpfs", "/sys", "tmpfs", "mode=0555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV, - MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR|MOUNT_PRIVILEGED }, + MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR|MOUNT_UNMANAGED }, { "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS, - MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* skipped if above was mounted */ + MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR|MOUNT_UNMANAGED }, /* skipped if above was mounted */ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - MOUNT_FATAL|MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* skipped if above was mounted */ + MOUNT_FATAL|MOUNT_MKDIR|MOUNT_UNMANAGED }, /* skipped if above was mounted */ { "tmpfs", "/dev", "tmpfs", "mode=0755" TMPFS_LIMITS_PRIVATE_DEV, MS_NOSUID|MS_STRICTATIME, MOUNT_FATAL|MOUNT_MKDIR }, { "tmpfs", "/dev/shm", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME, - MOUNT_FATAL|MOUNT_MKDIR }, + MOUNT_FATAL|MOUNT_MKDIR|MOUNT_USRQUOTA_GRACEFUL }, { "tmpfs", "/run", "tmpfs", "mode=0755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL|MOUNT_MKDIR }, { "/run/host", "/run/host", NULL, NULL, MS_BIND, @@ -617,9 +617,9 @@ int mount_all(const char *dest, { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */ { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, - MOUNT_PRIVILEGED }, /* Then, make it r/o (don't mkdir/chown the mount point here, the previous entry already did that) */ + MOUNT_UNMANAGED|MOUNT_PRIVILEGED }, /* Then, make it r/o (don't mkdir/chown the mount point here, the previous entry already did that) */ { NULL, "/sys/fs/selinux", NULL, NULL, MS_PRIVATE, - MOUNT_PRIVILEGED }, /* Turn off propagation (we only want that for the mount propagation tunnel dir) */ + MOUNT_UNMANAGED|MOUNT_PRIVILEGED }, /* Turn off propagation (we only want that for the mount propagation tunnel dir) */ #endif }; @@ -628,6 +628,7 @@ int mount_all(const char *dest, bool ro = FLAGS_SET(mount_settings, MOUNT_APPLY_APIVFS_RO); bool in_userns = FLAGS_SET(mount_settings, MOUNT_IN_USERNS); bool tmpfs_tmp = FLAGS_SET(mount_settings, MOUNT_APPLY_TMPFS_TMP); + bool unmanaged = FLAGS_SET(mount_settings, MOUNT_UNMANAGED); bool privileged = FLAGS_SET(mount_settings, MOUNT_PRIVILEGED); int r; @@ -636,8 +637,9 @@ int mount_all(const char *dest, bool fatal = FLAGS_SET(m->mount_settings, MOUNT_FATAL); const char *o; - /* If we are not privileged but the entry is marked as privileged and to be mounted outside the user namespace, then skip it */ - if (!privileged && FLAGS_SET(m->mount_settings, MOUNT_PRIVILEGED) && !FLAGS_SET(m->mount_settings, MOUNT_IN_USERNS)) + /* If we are in managed user namespace mode but the entry is marked for mount outside of + * managed user namespace mode, and to be mounted outside the user namespace, then skip it */ + if (!unmanaged && FLAGS_SET(m->mount_settings, MOUNT_UNMANAGED) && !FLAGS_SET(m->mount_settings, MOUNT_IN_USERNS)) continue; if (in_userns != FLAGS_SET(m->mount_settings, MOUNT_IN_USERNS)) @@ -652,6 +654,9 @@ int mount_all(const char *dest, if (!tmpfs_tmp && FLAGS_SET(m->mount_settings, MOUNT_APPLY_TMPFS_TMP)) continue; + if (!privileged && FLAGS_SET(m->mount_settings, MOUNT_PRIVILEGED)) + continue; + r = chase(m->where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where, NULL); if (r < 0) return log_error_errno(r, "Failed to resolve %s%s: %m", strempty(dest), m->where); @@ -706,6 +711,23 @@ int mount_all(const char *dest, o = options; } + if (FLAGS_SET(m->mount_settings, MOUNT_USRQUOTA_GRACEFUL)) { + r = mount_option_supported(m->type, /* key= */ "usrquota", /* value= */ NULL); + if (r < 0) + log_warning_errno(r, "Failed to determine if '%s' supports 'usrquota', assuming it doesn't: %m", m->type); + else if (r == 0) + log_debug("Kernel doesn't support 'usrquota' on '%s', not including in mount options for '%s'.", m->type, m->where); + else { + _cleanup_free_ char *joined = NULL; + + if (!strextend_with_separator(&joined, ",", o ?: POINTER_MAX, "usrquota")) + return log_oom(); + + free_and_replace(options, joined); + o = options; + } + } + if (FLAGS_SET(m->mount_settings, MOUNT_PREFIX_ROOT)) { /* Optionally prefix the mount source with the root dir. This is useful in bind * mounts to be created within the container image before we transition into it. Note diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 53aa993d6a..87b3b91c43 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -20,7 +20,9 @@ typedef enum MountSettingsMask { MOUNT_TOUCH = 1 << 9, /* if set, touch file to mount over first */ MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */ MOUNT_FOLLOW_SYMLINKS = 1 << 11,/* if set, we'll follow symlinks for the mount target */ - MOUNT_PRIVILEGED = 1 << 12,/* if set, we'll only mount this in the outer child if we are running in privileged mode */ + MOUNT_UNMANAGED = 1 << 12,/* if set, we'll only mount this in the outer child if we are running in privileged mode */ + MOUNT_PRIVILEGED = 1 << 13,/* if set, we'll only mount this if we have full privileges */ + MOUNT_USRQUOTA_GRACEFUL = 1 << 14,/* if set, append "usrquota" to mount options if kernel tmpfs supports that */ } MountSettingsMask; typedef enum CustomMountType { diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 7842d93c34..2d883e2196 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -930,10 +930,11 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AU DEFINE_CONFIG_PARSE_ENUM(config_parse_userns_ownership, user_namespace_ownership, UserNamespaceOwnership); static const char *const user_namespace_ownership_table[_USER_NAMESPACE_OWNERSHIP_MAX] = { - [USER_NAMESPACE_OWNERSHIP_OFF] = "off", - [USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown", - [USER_NAMESPACE_OWNERSHIP_MAP] = "map", - [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto", + [USER_NAMESPACE_OWNERSHIP_OFF] = "off", + [USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown", + [USER_NAMESPACE_OWNERSHIP_MAP] = "map", + [USER_NAMESPACE_OWNERSHIP_FOREIGN] = "foreign", + [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto", }; /* Note: while "yes" maps to "auto" here, we don't really document that, in order to make things clearer and less confusing to users. */ diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 135b3dbb0a..0b30506391 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -29,14 +29,16 @@ typedef enum UserNamespaceMode { USER_NAMESPACE_NO, USER_NAMESPACE_FIXED, USER_NAMESPACE_PICK, + USER_NAMESPACE_MANAGED, _USER_NAMESPACE_MODE_MAX, _USER_NAMESPACE_MODE_INVALID = -EINVAL, } UserNamespaceMode; typedef enum UserNamespaceOwnership { - USER_NAMESPACE_OWNERSHIP_OFF, - USER_NAMESPACE_OWNERSHIP_CHOWN, - USER_NAMESPACE_OWNERSHIP_MAP, + USER_NAMESPACE_OWNERSHIP_OFF, /* do not change ownership */ + USER_NAMESPACE_OWNERSHIP_CHOWN, /* chown to target range */ + USER_NAMESPACE_OWNERSHIP_MAP, /* map from 0x00000000…0x0000FFFF range to target range */ + USER_NAMESPACE_OWNERSHIP_FOREIGN, /* map from 0x7FFE0000…0x7FFEFFFF range to target range */ USER_NAMESPACE_OWNERSHIP_AUTO, _USER_NAMESPACE_OWNERSHIP_MAX, _USER_NAMESPACE_OWNERSHIP_INVALID = -1, diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 5ab04b121a..4c054b2dbb 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -107,6 +107,7 @@ #include "sysctl-util.h" #include "terminal-util.h" #include "tmpfile-util.h" +#include "uid-classification.h" #include "umask-util.h" #include "unit-name.h" #include "user-util.h" @@ -139,7 +140,7 @@ static char *arg_hostname = NULL; /* The name the payload sees by default */ static const char *arg_selinux_context = NULL; static const char *arg_selinux_apifs_context = NULL; static char *arg_slice = NULL; -static bool arg_private_network = false; +static bool arg_private_network; /* initialized depending on arg_privileged in run() */ static bool arg_read_only = false; static StartMode arg_start_mode = START_PID1; static bool arg_ephemeral = false; @@ -197,7 +198,7 @@ static VolatileMode arg_volatile_mode = VOLATILE_NO; static ExposePort *arg_expose_ports = NULL; static char **arg_property = NULL; static sd_bus_message *arg_property_message = NULL; -static UserNamespaceMode arg_userns_mode = USER_NAMESPACE_NO; +static UserNamespaceMode arg_userns_mode; /* initialized depending on arg_privileged in run() */ static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; static UserNamespaceOwnership arg_userns_ownership = _USER_NAMESPACE_OWNERSHIP_INVALID; static int arg_kill_signal = 0; @@ -369,7 +370,7 @@ static int help(void) { " the service unit nspawn is running in\n" "\n%3$sUser Namespacing:%4$s\n" " --private-users=no Run without user namespacing\n" - " --private-users=yes|pick|identity\n" + " --private-users=yes|pick|identity|managed\n" " Run within user namespace, autoselect UID/GID range\n" " --private-users=UIDBASE[:NUIDS]\n" " Similar, but with user configured UID/GID range\n" @@ -518,7 +519,7 @@ static int detect_unified_cgroup_hierarchy_from_environment(void) { static int detect_unified_cgroup_hierarchy_from_image(const char *directory) { int r; - if (!arg_privileged) { + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { /* We only support the unified mode when running unprivileged */ arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_ALL; return 0; @@ -1257,6 +1258,11 @@ static int parse_argv(int argc, char *argv[]) { arg_userns_mode = USER_NAMESPACE_FIXED; arg_uid_shift = 0; arg_uid_range = UINT32_C(0x10000); + } else if (streq(optarg, "managed")) { + /* managed: User namespace on, and acquire it from systemd-nsresourced */ + arg_userns_mode = USER_NAMESPACE_MANAGED; + arg_uid_shift = UID_INVALID; + arg_uid_range = UINT32_C(0x10000); } else { /* anything else: User namespacing on, UID range is explicitly configured */ r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); @@ -1271,9 +1277,8 @@ static int parse_argv(int argc, char *argv[]) { case 'U': if (userns_supported()) { - arg_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_ownership is - * implied by USER_NAMESPACE_PICK - * further down. */ + /* Note that arg_userns_ownership is implied by USER_NAMESPACE_PICK further down. */ + arg_userns_mode = arg_privileged ? USER_NAMESPACE_PICK : USER_NAMESPACE_MANAGED; arg_uid_shift = UID_INVALID; arg_uid_range = UINT32_C(0x10000); @@ -1656,14 +1661,23 @@ static int parse_argv(int argc, char *argv[]) { static int verify_arguments(void) { int r; - SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); + SET_FLAG(arg_mount_settings, MOUNT_UNMANAGED, arg_userns_mode != USER_NAMESPACE_MANAGED); - if (!arg_privileged) { - if (!arg_private_network) { - log_notice("Automatically implying --private-network, since mounting /sys/ in an unprivileged user namespaces requires network namespacing."); - arg_private_network = true; - } - } + /* We can mount selinuxfs only if we are privileged and can do so before userns. In managed mode we + * have to enter the userns earlier, hence cannot do that. */ + /* SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); */ + SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_userns_mode != USER_NAMESPACE_MANAGED); + + SET_FLAG(arg_mount_settings, MOUNT_USE_USERNS, arg_userns_mode != USER_NAMESPACE_NO); + + if (arg_private_network) + SET_FLAG(arg_mount_settings, MOUNT_APPLY_APIVFS_NETNS, arg_private_network); + + if (!arg_privileged && arg_userns_mode != USER_NAMESPACE_MANAGED) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unprivileged operation requires managed user namespaces, as otherwise no UID range can be acquired."); + + if (arg_userns_mode == USER_NAMESPACE_MANAGED && !arg_private_network) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Managed user namespace operation requires private networking, as otherwise /sys/ may not be mounted."); if (arg_start_mode == START_PID2 && arg_unified_cgroup_hierarchy == CGROUP_UNIFIED_UNKNOWN) { /* If we are running the stub init in the container, we don't need to look at what the init @@ -1684,12 +1698,6 @@ static int verify_arguments(void) { arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_NONE; } - if (arg_userns_mode != USER_NAMESPACE_NO) - arg_mount_settings |= MOUNT_USE_USERNS; - - if (arg_private_network) - arg_mount_settings |= MOUNT_APPLY_APIVFS_NETNS; - if (!(arg_clone_ns_flags & CLONE_NEWPID) || !(arg_clone_ns_flags & CLONE_NEWUTS)) { arg_register = false; @@ -1699,8 +1707,7 @@ static int verify_arguments(void) { if (arg_userns_ownership < 0) arg_userns_ownership = - arg_userns_mode == USER_NAMESPACE_PICK ? USER_NAMESPACE_OWNERSHIP_AUTO : - USER_NAMESPACE_OWNERSHIP_OFF; + IN_SET(arg_userns_mode, USER_NAMESPACE_PICK, USER_NAMESPACE_MANAGED) ? USER_NAMESPACE_OWNERSHIP_AUTO : USER_NAMESPACE_OWNERSHIP_OFF; if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) arg_kill_signal = SIGRTMIN+3; @@ -1809,10 +1816,18 @@ static int verify_network_interfaces_initialized(void) { return 0; } +static int in_child_chown(void) { + /* Returns true when chown()ing inodes we create inside the outer child is required. Basically, we + * need the chowning when we implement userns ourselves. If userns is off we don#t need to chown(), + * obviously. And if we are in managed mode we already entered the userns, and hence don#t need to + * manually chown either. */ + return IN_SET(arg_userns_mode, USER_NAMESPACE_PICK, USER_NAMESPACE_FIXED); +} + static int userns_chown_at(int fd, const char *fname, uid_t uid, gid_t gid, int flags) { assert(fd >= 0 || fd == AT_FDCWD); - if (arg_userns_mode == USER_NAMESPACE_NO) + if (!in_child_chown()) return 0; if (uid == UID_INVALID && gid == GID_INVALID) @@ -2295,18 +2310,24 @@ static int copy_devnode_one(const char *dest, const char *node, bool ignore_mkno if (r < 0) return log_error_errno(r, "Failed to create directory %s: %m", parent); - if (mknod(to, st.st_mode, st.st_rdev) < 0) { - r = -errno; /* Save the original error code. */ + r = RET_NERRNO(mknod(to, st.st_mode, st.st_rdev)); + if (r < 0) { /* Explicitly warn the user when /dev/ is already populated. */ if (r == -EEXIST) log_notice("%s/dev/ is pre-mounted and pre-populated. If a pre-mounted /dev/ is provided it needs to be an unpopulated file system.", dest); + /* If arg_uid_shift != 0, then we cannot fall back to use bind mount. */ - if (arg_uid_shift != 0) { + if (!(arg_userns_mode == USER_NAMESPACE_NO || + (arg_userns_mode == USER_NAMESPACE_FIXED && arg_uid_shift == 0))) { if (ignore_mknod_failure) { log_debug_errno(r, "Failed to mknod(%s), ignoring: %m", to); return 0; } - return log_error_errno(r, "Failed to mknod(%s): %m", to); + + if (arg_userns_mode != USER_NAMESPACE_MANAGED || !ERRNO_IS_NEG_PRIVILEGE(r)) + return log_error_errno(r, "Failed to mknod(%s): %m", to); + + log_debug_errno(r, "Failed to create device node '%s' and running in managed mode, resorting to bind mount: %m", to); } /* Some systems abusively restrict mknod but allow bind mounts. */ @@ -2402,7 +2423,7 @@ static int make_extra_nodes(const char *dest) { return 0; } -static int setup_pts(const char *dest) { +static int setup_pts(const char *dest, uid_t chown_uid) { _cleanup_free_ char *options = NULL; const char *p; int r; @@ -2411,13 +2432,13 @@ static int setup_pts(const char *dest) { if (arg_selinux_apifs_context) (void) asprintf(&options, "newinstance,ptmxmode=0666,mode=" STRINGIFY(TTY_MODE) ",gid=" GID_FMT ",context=\"%s\"", - arg_uid_shift + TTY_GID, + chown_uid + TTY_GID, arg_selinux_apifs_context); else #endif (void) asprintf(&options, "newinstance,ptmxmode=0666,mode=" STRINGIFY(TTY_MODE) ",gid=" GID_FMT, - arg_uid_shift + TTY_GID); + chown_uid + TTY_GID); if (!options) return log_oom(); @@ -2854,7 +2875,9 @@ static int reset_audit_loginuid(void) { if ((arg_clone_ns_flags & CLONE_NEWPID) == 0) return 0; - if (!arg_privileged) + /* if we are in managed userns mode, then we are already in our userns, hence we cannot reset the + * loginuid anyway, hence don't bother */ + if (arg_userns_mode == USER_NAMESPACE_MANAGED) return 0; r = read_virtual_file("/proc/self/loginuid", SIZE_MAX, &p, /* ret_size= */ NULL); @@ -2886,8 +2909,8 @@ static int mount_tunnel_dig(const char *root) { const char *p, *q; int r; - if (!arg_privileged) { - log_debug("Not digging mount tunnel, because running unprivileged."); + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + log_debug("Not digging mount tunnel, because running in managed user namespace mode."); return 0; } @@ -2919,8 +2942,8 @@ static int mount_tunnel_dig(const char *root) { static int mount_tunnel_open(void) { int r; - if (!arg_privileged) { - log_debug("Not opening up mount tunnel, because running unprivileged."); + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + log_debug("Not opening up mount tunnel, because running in managed user namespace mode."); return 0; } @@ -3267,6 +3290,12 @@ static int chase_and_update(char **p, unsigned flags) { static int determine_uid_shift(const char *directory) { assert(directory); + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + /* In managed mode we should already know the UID shift */ + assert(uid_is_valid(arg_uid_shift)); + return 0; + } + if (arg_userns_mode == USER_NAMESPACE_NO) { arg_uid_shift = 0; return 0; @@ -3447,7 +3476,7 @@ static int inner_child( if (!arg_network_namespace_path && arg_private_network) { _cleanup_close_ int netns_fd = -EBADF; - if (arg_privileged) + if (arg_userns_mode != USER_NAMESPACE_MANAGED) if (unshare(CLONE_NEWNET) < 0) return log_error_errno(errno, "Failed to unshare network namespace: %m"); @@ -3463,8 +3492,8 @@ static int inner_child( (void) barrier_place(barrier); /* #3 */ } - if (arg_privileged) { - r = mount_sysfs(NULL, arg_mount_settings); + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { + r = mount_sysfs(NULL, arg_mount_settings | MOUNT_IN_USERNS); if (r < 0) return r; } @@ -3817,8 +3846,8 @@ static int setup_unix_export_dir_outside(char **ret) { assert(ret); - if (!arg_privileged) { - log_debug("Not digging socket tunnel, because running unprivileged."); + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + log_debug("Not digging socket tunnel, because running in managed user namespace mode."); return 0; } @@ -3874,7 +3903,7 @@ static int setup_unix_export_host_inside(const char *directory, const char *unix assert(directory); - if (!arg_privileged) + if (arg_userns_mode == USER_NAMESPACE_MANAGED) return 0; assert(unix_export_path); @@ -3928,12 +3957,15 @@ static DissectImageFlags determine_dissect_image_flags(void) { DISSECT_IMAGE_PIN_PARTITION_DEVICES | (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS) | DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | - (arg_console_mode == CONSOLE_INTERACTIVE ? DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH : 0); + (arg_console_mode == CONSOLE_INTERACTIVE ? DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH : 0) | + ((arg_userns_ownership == USER_NAMESPACE_OWNERSHIP_FOREIGN) ? DISSECT_IMAGE_FOREIGN_UID : + (arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_AUTO) ? DISSECT_IMAGE_IDENTITY_UID : 0); } static int outer_child( Barrier *barrier, const char *directory, + int mount_fd, DissectedImage *dissected_image, int fd_outer_socket, int fd_inner_socket, @@ -3952,9 +3984,9 @@ static int outer_child( /* This is the "outer" child process, i.e the one forked off by the container manager itself. Its * namespace situation is: * - * - CLONE_NEWNS : already has its own (created by clone() if arg_privileged, or unshare() if !arg_unprivileged) - * - CLONE_NEWUSER : if arg_privileged: still in the host's - * if !arg_privileged: already has its own (created by nsresource_allocate_userns()->setns(userns_fd)) + * - CLONE_NEWUSER : if not in USER_NAMESPACE_MANAGED mode: still in the host's + * if USER_NAMESPACE_MANAGED mode: already has its own (created by nsresource_allocate_userns()->setns(userns_fd)) + * - CLONE_NEWNS : already has its own (created by clone() if not USER_NAMESPACE_MANAGED, or unshare() otherwise) * - CLONE_NEWPID : still in the host's * - CLONE_NEWUTS : still in the host's * - CLONE_NEWIPC : still in the host's @@ -3987,7 +4019,23 @@ static int outer_child( if (r < 0) return r; - if (dissected_image) { + /* Put the root dir into the target directory now. One of three mechanisms is provided: either we + * have a single mount fd (typically unprivileged --directory= mode) or we have a fully dissected + * image (--image= mode), or we have a regular path. */ + if (mount_fd >= 0) { + assert(arg_directory); + assert(!arg_image); + + if (move_mount(mount_fd, "", AT_FDCWD, directory, MOVE_MOUNT_F_EMPTY_PATH) < 0) + return log_error_errno(errno, "Failed to attach root directory: %m"); + + mount_fd = safe_close(mount_fd); + log_debug("Successfully attached root directory to '%s'.", directory); + + } else if (dissected_image) { + assert(!arg_directory); + assert(arg_image); + /* If we are operating on a disk image, then mount its root directory now, but leave out the * rest. We can read the UID shift from it if we need to. Further down we'll mount the rest, * but then with the uid shift known. That way we can mount VFAT file systems shifted to the @@ -4004,12 +4052,31 @@ static int outer_child( (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0)); if (r < 0) return r; + } else { + assert(arg_directory); + assert(!arg_image); + + r = mount_nofollow_verbose(LOG_ERR, arg_directory, directory, /* fstype= */ NULL, MS_BIND|MS_REC, /* options= */ NULL); + if (r < 0) + return r; } r = determine_uid_shift(directory); if (r < 0) return r; + /* If we do userns on our own, we need to chown() all files ourselves before. Otherwise, if userns is + * off or we are in managed mode we already have the userns applied, hence don't need to chown + * anything */ + uid_t chown_uid, chown_range; + if (in_child_chown()) { + chown_uid = arg_uid_shift; + chown_range = arg_uid_range; + } else { + chown_uid = 0; + chown_range = UINT32_C(0x10000); + } + if (arg_userns_mode != USER_NAMESPACE_NO) { _cleanup_close_ int mntns_fd = -EBADF; @@ -4041,33 +4108,15 @@ static int outer_child( if (l != sizeof(arg_uid_shift)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while receiving UID shift."); + + if (in_child_chown()) + chown_uid = arg_uid_shift; } log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Selected user namespace base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); } - if (path_equal(directory, "/")) { - /* If the directory we shall boot is the host, let's operate on a bind mount at a different - * place, so that we can make changes to its mount structure (for example, to implement - * --volatile=) without this interfering with our ability to access files such as - * /etc/localtime to copy into the container. Note that we use a fixed place for this - * (instead of a temporary directory, since we are living in our own mount namespace here - * already, and thus don't need to be afraid of colliding with anyone else's mounts). */ - (void) mkdir_p("/run/systemd/nspawn-root", 0755); - - r = mount_nofollow_verbose(LOG_ERR, "/", "/run/systemd/nspawn-root", NULL, MS_BIND|MS_REC, NULL); - if (r < 0) - return r; - - directory = "/run/systemd/nspawn-root"; - } - - /* Make sure we always have a mount that we can move to root later on. */ - r = make_mount_point(directory); - if (r < 0) - return r; - /* So the whole tree is now MS_SLAVE, i.e. we'll still receive mount/umount events from the host * mount namespace. For the directory we are going to run our container let's turn this off, so that * we'll live in our own little world from now on, and propagation from the host may only happen via @@ -4086,7 +4135,7 @@ static int outer_child( r = setup_volatile_mode( directory, arg_volatile_mode, - arg_uid_shift, + chown_uid, arg_selinux_apifs_context); if (r < 0) return r; @@ -4094,8 +4143,8 @@ static int outer_child( r = bind_user_prepare( directory, arg_bind_user, - arg_uid_shift, - arg_uid_range, + chown_uid, + chown_range, &arg_custom_mounts, &arg_n_custom_mounts, &bind_user_context); if (r < 0) @@ -4126,17 +4175,47 @@ static int outer_child( directory, arg_custom_mounts, arg_n_custom_mounts, - arg_uid_shift, - arg_uid_range, + chown_uid, + chown_range, arg_selinux_apifs_context, MOUNT_ROOT_ONLY); if (r < 0) return r; - if (arg_userns_mode != USER_NAMESPACE_NO && - IN_SET(arg_userns_ownership, USER_NAMESPACE_OWNERSHIP_MAP, USER_NAMESPACE_OWNERSHIP_AUTO) && - arg_uid_shift != 0) { + if (!IN_SET(arg_userns_mode, USER_NAMESPACE_NO, USER_NAMESPACE_MANAGED) && + IN_SET(arg_userns_ownership, USER_NAMESPACE_OWNERSHIP_MAP, USER_NAMESPACE_OWNERSHIP_FOREIGN, USER_NAMESPACE_OWNERSHIP_AUTO) && + chown_uid != 0) { _cleanup_strv_free_ char **dirs = NULL; + RemountIdmapping mapping; + + switch (arg_userns_ownership) { + case USER_NAMESPACE_OWNERSHIP_MAP: + mapping = REMOUNT_IDMAPPING_HOST_ROOT; + break; + + case USER_NAMESPACE_OWNERSHIP_FOREIGN: + mapping = REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT; + break; + + case USER_NAMESPACE_OWNERSHIP_AUTO: { + struct stat st; + + if (lstat(directory, &st) < 0) + return log_error_errno(errno, "Failed to stat() container root directory '%s': %m", directory); + + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "Container root directory '%s' is not a directory: %m", directory); + + mapping = uid_is_foreign(st.st_uid) ? + REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT : + REMOUNT_IDMAPPING_HOST_ROOT; + break; + } + + default: + assert_not_reached(); + } if (arg_volatile_mode != VOLATILE_YES) { r = strv_extend(&dirs, directory); @@ -4155,7 +4234,13 @@ static int outer_child( return log_oom(); } - r = remount_idmap(dirs, arg_uid_shift, arg_uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); + r = remount_idmap( + dirs, + chown_uid, + chown_range, + /* host_owner= */ UID_INVALID, + /* dest_owner= */ UID_INVALID, + mapping); if (r == -EINVAL || ERRNO_IS_NEG_NOT_SUPPORTED(r)) { /* This might fail because the kernel or file system doesn't support idmapping. We * can't really distinguish this nicely, nor do we have any guarantees about the @@ -4177,7 +4262,7 @@ static int outer_child( r = setup_volatile_mode_after_remount_idmap( directory, arg_volatile_mode, - arg_uid_shift, + chown_uid, arg_selinux_apifs_context); if (r < 0) return r; @@ -4187,8 +4272,8 @@ static int outer_child( r = dissected_image_mount_and_warn( dissected_image, directory, - arg_uid_shift, - arg_uid_range, + chown_uid, + chown_range, /* userns_fd= */ -EBADF, determine_dissect_image_flags()| DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY| @@ -4212,11 +4297,11 @@ static int outer_child( "Short write while sending cgroup mode."); } - r = recursive_chown(directory, arg_uid_shift, arg_uid_range); + r = recursive_chown(directory, chown_uid, chown_range); if (r < 0) return r; - r = base_filesystem_create(directory, arg_uid_shift, (gid_t) arg_uid_shift); + r = base_filesystem_create(directory, chown_uid, (gid_t) chown_uid); if (r < 0) return r; @@ -4229,7 +4314,7 @@ static int outer_child( r = mount_all(directory, arg_mount_settings, - arg_uid_shift, + chown_uid, arg_selinux_apifs_context); if (r < 0) return r; @@ -4247,16 +4332,16 @@ static int outer_child( if (r < 0) return r; - (void) dev_setup(directory, arg_uid_shift, arg_uid_shift); + (void) dev_setup(directory, chown_uid, chown_uid); p = prefix_roota(directory, "/run/host"); - (void) make_inaccessible_nodes(p, arg_uid_shift, arg_uid_shift); + (void) make_inaccessible_nodes(p, chown_uid, chown_uid); r = setup_unix_export_host_inside(directory, unix_export_path); if (r < 0) return r; - r = setup_pts(directory); + r = setup_pts(directory, chown_uid); if (r < 0) return r; @@ -4280,8 +4365,8 @@ static int outer_child( directory, arg_custom_mounts, arg_n_custom_mounts, - arg_uid_shift, - arg_uid_range, + chown_uid, + chown_range, arg_selinux_apifs_context, MOUNT_NON_ROOT_ONLY); if (r < 0) @@ -4316,8 +4401,8 @@ static int outer_child( directory, arg_unified_cgroup_hierarchy, arg_userns_mode != USER_NAMESPACE_NO, - arg_uid_shift, - arg_uid_range, + chown_uid, + chown_range, arg_selinux_apifs_context, false); if (r < 0) @@ -4333,7 +4418,7 @@ static int outer_child( * (and fork for it) for which we then mount sysfs/procfs, and only then switch root. */ _cleanup_close_ int notify_fd = -EBADF; - if (arg_privileged) { + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { /* Mark everything as shared so our mounts get propagated down. This is required to make new * bind mounts available in systemd services inside the container that create a new mount * namespace. See https://github.com/systemd/systemd/issues/3860 Further submounts (such as @@ -4376,8 +4461,8 @@ static int outer_child( pid = raw_clone(SIGCHLD|CLONE_NEWNS| arg_clone_ns_flags | - (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0) | - ((arg_private_network && !arg_privileged) ? CLONE_NEWNET : 0)); + (IN_SET(arg_userns_mode, USER_NAMESPACE_FIXED, USER_NAMESPACE_PICK) ? CLONE_NEWUSER : 0) | + ((arg_private_network && arg_userns_mode == USER_NAMESPACE_MANAGED) ? CLONE_NEWNET : 0)); if (pid < 0) return log_error_errno(errno, "Failed to fork inner child: %m"); if (pid == 0) { @@ -4396,9 +4481,10 @@ static int outer_child( return log_error_errno(r, "Failed to join network namespace: %m"); } - if (!arg_privileged) { - /* In unprivileged operation, sysfs + procfs are special, we'll have to mount them - * inside the inner namespaces, but before we switch root. Hence do so here. */ + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + /* In managed usernamespace operation, sysfs + procfs are special, we'll have to + * mount them inside the inner namespaces, but before we switch root. Hence do so + * here. */ _cleanup_free_ char *j = path_join(directory, "/proc"); if (!j) return log_oom(); @@ -5161,6 +5247,8 @@ static int load_oci_bundle(void) { } static int run_container( + const char *directory, + int mount_fd, DissectedImage *dissected_image, int userns_fd, FDSet *fds, @@ -5248,9 +5336,8 @@ static int run_container( "Path %s doesn't refer to a network namespace, refusing.", arg_network_namespace_path); } - if (arg_privileged) { + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { assert(userns_fd < 0); - /* If we have no user namespace then we'll clone and create a new mount namespace right-away. */ *pid = raw_clone(SIGCHLD|CLONE_NEWNS); @@ -5260,7 +5347,6 @@ static int run_container( ", do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in)" : ""); } else { assert(userns_fd >= 0); - /* If we have a user namespace then we'll clone() first, and then join the user namespace, * and then open the mount namespace, so that it is owned by the user namespace */ @@ -5298,7 +5384,8 @@ static int run_container( (void) reset_signal_mask(); r = outer_child(&barrier, - arg_directory, + directory, + mount_fd, dissected_image, fd_outer_socket_pair[1], fd_inner_socket_pair[1], @@ -5416,9 +5503,11 @@ static int run_container( if (!barrier_place_and_sync(&barrier)) /* #1 */ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Child died too early."); - r = setup_uid_map(*pid, bind_user_uid, n_bind_user_uid); - if (r < 0) - return r; + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { + r = setup_uid_map(*pid, bind_user_uid, n_bind_user_uid); + if (r < 0) + return r; + } (void) barrier_place(&barrier); /* #2 */ } @@ -5442,7 +5531,7 @@ static int run_container( return r; if (arg_network_veth) { - if (arg_privileged) { + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { r = setup_veth(arg_machine, *pid, veth_name, arg_network_bridge || arg_network_zone, &arg_network_provided_mac); if (r < 0) @@ -5580,7 +5669,7 @@ static int run_container( arg_unified_cgroup_hierarchy, arg_uid_shift, userns_fd, - arg_privileged); + arg_userns_mode); if (r < 0) return r; @@ -5622,7 +5711,7 @@ static int run_container( if (!barrier_sync(&barrier)) /* #5.1 */ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Child died too early."); - if (arg_userns_mode != USER_NAMESPACE_NO) { + if (!IN_SET(arg_userns_mode, USER_NAMESPACE_NO, USER_NAMESPACE_MANAGED)) { r = wipe_fully_visible_api_fs(mntns_fd); if (r < 0) return r; @@ -5749,7 +5838,7 @@ static int run_container( fd_kmsg_fifo = safe_close(fd_kmsg_fifo); - if (arg_private_network && arg_privileged) { + if (arg_private_network && arg_userns_mode != USER_NAMESPACE_MANAGED) { r = move_back_network_interfaces(child_netns_fd, arg_network_interfaces); if (r < 0) return r; @@ -5914,15 +6003,25 @@ static int cant_be_in_netns(void) { return 0; } +static void initialize_defaults(void) { + arg_privileged = getuid() == 0; + + /* If running unprivileged default to systemd-nsresourced operation */ + arg_userns_mode = arg_privileged ? USER_NAMESPACE_NO : USER_NAMESPACE_MANAGED; + + /* Imply private networking for unprivileged operation, since kernel otherwise refuses mounting sysfs */ + arg_private_network = !arg_privileged; +} + static int run(int argc, char *argv[]) { - bool remove_directory = false, remove_image = false, veth_created = false, remove_tmprootdir = false; - _cleanup_close_ int master = -EBADF, userns_fd = -EBADF; + bool remove_directory = false, remove_image = false, veth_created = false; + _cleanup_close_ int master = -EBADF, userns_fd = -EBADF, mount_fd = -EBADF; _cleanup_fdset_free_ FDSet *fds = NULL; int r, n_fd_passed, ret = EXIT_SUCCESS; char veth_name[IFNAMSIZ] = ""; struct ExposeArgs expose_args = {}; _cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; - char tmprootdir[] = "/tmp/nspawn-root-XXXXXX"; + _cleanup_(rmdir_and_freep) char *rootdir = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; _cleanup_(fw_ctx_freep) FirewallContext *fw_ctx = NULL; @@ -5930,7 +6029,7 @@ static int run(int argc, char *argv[]) { log_setup(); - arg_privileged = getuid() == 0; + initialize_defaults(); r = parse_argv(argc, argv); if (r <= 0) @@ -5987,14 +6086,14 @@ static int run(int argc, char *argv[]) { /* Reapply environment settings. */ (void) detect_unified_cgroup_hierarchy_from_environment(); - if (!arg_privileged) { + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { r = cg_all_unified(); if (r < 0) { log_error_errno(r, "Failed to determine if we are in unified cgroupv2 mode: %m"); goto finish; } if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unprivileged operation only supported in unified cgroupv2 mode."); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Managed user namespace operation only supported in unified cgroupv2 mode."); } /* Ignore SIGPIPE here, because we use splice() on the ptyfwd stuff and that will generate SIGPIPE if @@ -6023,14 +6122,33 @@ static int run(int argc, char *argv[]) { if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */ arg_quiet = true; - if (arg_directory) { - assert(!arg_image); + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + /* Let's allocate a 64K userns first, if managed mode is chosen */ + + _cleanup_free_ char *userns_name = NULL; + if (asprintf(&userns_name, "nspawn-" PID_FMT "-%s", getpid_cached(), arg_machine) < 0) { + r = log_oom(); + goto finish; + } + + userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000)); + if (userns_fd < 0) { + r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m"); + goto finish; + } - if (!arg_privileged) { - r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Invoking container from plain directory tree is currently not supported if called without privileges."); + r = userns_get_base_uid(userns_fd, &arg_uid_shift, /* ret_gid= */ NULL); + if (r < 0) { + log_error_errno(r, "Failed to determine UID shift from userns: %m"); goto finish; } + arg_uid_range = UINT32_C(0x10000); + } + + if (arg_directory) { + assert(!arg_image); + /* Safety precaution: let's not allow running images from the live host OS image, as long as * /var from the host will propagate into container dynamically (because bad things happen if * two systems write to the same /var). Let's allow it for the special cases where /var is @@ -6200,6 +6318,15 @@ static int run(int argc, char *argv[]) { } } + if (arg_userns_mode == USER_NAMESPACE_MANAGED) { + r = mountfsd_mount_directory( + arg_directory, + userns_fd, + determine_dissect_image_flags(), + &mount_fd); + if (r < 0) + goto finish; + } } else { DissectImageFlags dissect_image_flags = determine_dissect_image_flags(); @@ -6274,20 +6401,7 @@ static int run(int argc, char *argv[]) { dissect_image_flags |= DISSECT_IMAGE_NO_PARTITION_TABLE; } - if (!mkdtemp(tmprootdir)) { - r = log_error_errno(errno, "Failed to create temporary directory: %m"); - goto finish; - } - - remove_tmprootdir = true; - - arg_directory = strdup(tmprootdir); - if (!arg_directory) { - r = log_oom(); - goto finish; - } - - if (arg_privileged) { + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { r = loop_device_make_by_path( arg_image, arg_read_only ? O_RDONLY : O_RDWR, @@ -6339,19 +6453,6 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; } else { - _cleanup_free_ char *userns_name = strjoin("nspawn-", arg_machine); - if (!userns_name) { - r = log_oom(); - goto finish; - } - - /* if we are unprivileged, let's allocate a 64K userns first */ - userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000)); - if (userns_fd < 0) { - r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m"); - goto finish; - } - r = mountfsd_mount_image( arg_image, userns_fd, @@ -6370,7 +6471,14 @@ static int run(int argc, char *argv[]) { arg_architecture = dissected_image_architecture(dissected_image); } - r = custom_mount_prepare_all(arg_directory, arg_custom_mounts, arg_n_custom_mounts); + /* Create a temporary place to mount stuff. */ + r = mkdtemp_malloc("/tmp/nspawn-root-XXXXXX", &rootdir); + if (r < 0) { + log_error_errno(r, "Failed to create temporary directory: %m"); + goto finish; + } + + r = custom_mount_prepare_all(rootdir, arg_custom_mounts, arg_n_custom_mounts); if (r < 0) goto finish; @@ -6405,6 +6513,8 @@ static int run(int argc, char *argv[]) { } for (;;) { r = run_container( + rootdir, + mount_fd, dissected_image, userns_fd, fds, @@ -6447,12 +6557,7 @@ finish: log_warning_errno(errno, "Can't remove image file '%s', ignoring: %m", arg_image); } - if (remove_tmprootdir) { - if (rmdir(tmprootdir) < 0) - log_debug_errno(errno, "Can't remove temporary root directory '%s', ignoring: %m", tmprootdir); - } - - if (arg_machine && arg_privileged) { + if (arg_machine && arg_userns_mode != USER_NAMESPACE_MANAGED) { const char *p; p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); @@ -6466,7 +6571,7 @@ finish: expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4); expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET6, &expose_args.address6); - if (arg_privileged) { + if (arg_userns_mode != USER_NAMESPACE_MANAGED) { if (veth_created) (void) remove_veth_links(veth_name, arg_network_veth_extra); (void) remove_bridge(arg_network_zone); diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index ab1cd0a7c1..152b506da5 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -595,7 +595,7 @@ static int manager_setup_bpf(Manager *m) { #endif int manager_startup(Manager *m) { - _cleanup_(set_freep) Set *fdstore_inodes = NULL, *registry_inodes = NULL; + _cleanup_set_free_ Set *fdstore_inodes = NULL, *registry_inodes = NULL; void *p; int r; diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c index 8c43581d8f..08277473e2 100644 --- a/src/nsresourced/nsresourcework.c +++ b/src/nsresourced/nsresourcework.c @@ -1422,7 +1422,7 @@ static void hash_ether_addr(UserNamespaceInfo *userns_info, const char *ifname, siphash24_compress_string(strempty(ifname), &state); siphash24_compress_byte(0, &state); /* separator */ n = htole64(n); /* add the 'index' to the mix in an endianess-independent fashion */ - siphash24_compress(&n, sizeof(n), &state); + siphash24_compress_typesafe(n, &state); h = htole64(siphash24_finalize(&state)); diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index ce3674365a..352d558281 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -20,6 +20,7 @@ #include "path-util.h" #include "percent-util.h" #include "varlink-io.systemd.oom.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" typedef struct ManagedOOMMessage { @@ -746,13 +747,21 @@ static int manager_varlink_init(Manager *m, int fd) { if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); - r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_oom); + r = sd_varlink_server_add_interface_many( + s, + &vl_interface_io_systemd_oom, + &vl_interface_io_systemd_service); if (r < 0) - return log_error_errno(r, "Failed to add oom interface to varlink server: %m"); + return log_error_errno(r, "Failed to add Varlink interfaces to varlink server: %m"); - r = sd_varlink_server_bind_method(s, "io.systemd.oom.ReportManagedOOMCGroups", process_managed_oom_request); + r = sd_varlink_server_bind_method_many( + s, + "io.systemd.oom.ReportManagedOOMCGroups", process_managed_oom_request, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) - return log_error_errno(r, "Failed to register varlink method: %m"); + return log_error_errno(r, "Failed to register varlink methods: %m"); if (fd < 0) r = sd_varlink_server_listen_address(s, VARLINK_ADDR_PATH_MANAGED_OOM_USER, 0666); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 72b13f8fac..002ca43edb 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -4548,7 +4548,7 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { if (r < 0) return log_error_errno(r, "Failed to acquire PIN from environment: %m"); if (r == 0) { - _cleanup_(strv_free_erasep) char **l = NULL; + _cleanup_strv_free_erase_ char **l = NULL; AskPasswordRequest req = { .tty_fd = -EBADF, @@ -5269,8 +5269,9 @@ static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameter assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; el = event_log_new(); if (!el) @@ -5332,8 +5333,9 @@ static int vl_method_remove_policy(sd_varlink *link, sd_json_variant *parameters assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; r = remove_policy(); if (r < 0) diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c index 37c7b389b4..21167a7b4a 100644 --- a/src/remount-fs/remount-fs.c +++ b/src/remount-fs/remount-fs.c @@ -34,7 +34,7 @@ static int track_pid(Hashmap **h, const char *path, pid_t pid) { if (!c) return log_oom(); - r = hashmap_ensure_put(h, NULL, PID_TO_PTR(pid), c); + r = hashmap_ensure_put(h, &trivial_hash_ops_value_free, PID_TO_PTR(pid), c); if (r == -ENOMEM) return log_oom(); if (r < 0) @@ -70,7 +70,7 @@ static int do_remount(const char *path, bool force_rw, Hashmap **pids) { } static int remount_by_fstab(Hashmap **ret_pids) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_hashmap_free_ Hashmap *pids = NULL; _cleanup_endmntent_ FILE *f = NULL; bool has_root = false; struct mntent* me; @@ -108,7 +108,7 @@ static int remount_by_fstab(Hashmap **ret_pids) { } static int run(int argc, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_hashmap_free_ Hashmap *pids = NULL; int r; log_setup(); diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 1310bd5f8b..c7b51c0224 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -511,7 +511,7 @@ static int dns_cache_put_positive( /* If StaleRetentionSec is greater than zero, the 'until' property is set to a duration * of StaleRetentionSec from the time of TTL expiry. * If StaleRetentionSec is zero, both the 'until' and 'until_valid' are set to the TTL duration, - * leading to the eviction of the record once the TTL expires.*/ + * leading to the eviction of the record once the TTL expires. */ usec_t until_valid = calculate_until_valid(rr, min_ttl, UINT32_MAX, timestamp, false); *i = (DnsCacheItem) { .type = DNS_CACHE_POSITIVE, diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 4c82d38bcc..054ce5c3d1 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -1789,9 +1789,9 @@ static bool dns_svc_param_is_valid(DnsSvcParam *i) { /* RFC 9460, section 7.3: addrs must exactly fill SvcParamValue */ case DNS_SVC_PARAM_KEY_IPV4HINT: - return i->length % (sizeof (struct in_addr)) == 0; + return i->length > 0 && i->length % (sizeof (struct in_addr)) == 0; case DNS_SVC_PARAM_KEY_IPV6HINT: - return i->length % (sizeof (struct in6_addr)) == 0; + return i->length > 0 && i->length % (sizeof (struct in6_addr)) == 0; /* Otherwise, permit any value */ default: @@ -1831,7 +1831,7 @@ int dns_packet_read_rr( return r; /* RFC 2181, Section 8, suggests to treat a TTL with the MSB set as a zero TTL. We avoid doing this - * for OPT records so that all 8 bits of the extended RCODE may be used .*/ + * for OPT records so that all 8 bits of the extended RCODE may be used. */ if (key->type != DNS_TYPE_OPT && rr->ttl & UINT32_C(0x80000000)) rr->ttl = 0; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index e57af66221..0696857802 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -612,7 +612,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( dns_stream_unref); int dns_stream_disconnect_all(Manager *m) { - _cleanup_(set_freep) Set *closed = NULL; + _cleanup_set_free_ Set *closed = NULL; int r; assert(m); diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 5e9e5bf17a..1c26dcbc66 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -14,6 +14,8 @@ #include "resolve-util.h" #include "resolved-bus.h" #include "resolved-link-bus.h" +#include "resolved-llmnr.h" +#include "resolved-mdns.h" #include "resolved-resolv-conf.h" #include "socket-netlink.h" #include "stdio-util.h" @@ -517,6 +519,8 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er (void) link_save_user(l); + manager_llmnr_maybe_stop(l->manager); + log_link_info(l, "Bus client set LLMNR setting: %s", resolve_support_to_string(mode)); } @@ -567,6 +571,8 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err (void) link_save_user(l); + manager_mdns_maybe_stop(l->manager); + log_link_info(l, "Bus client set MulticastDNS setting: %s", resolve_support_to_string(mode)); } @@ -769,6 +775,9 @@ int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error (void) manager_write_resolv_conf(l->manager); (void) manager_send_changed(l->manager, "DNS"); + manager_llmnr_maybe_stop(l->manager); + manager_mdns_maybe_stop(l->manager); + return sd_bus_reply_method_return(message, NULL); } diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 067a0813ec..928137b967 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -651,16 +651,13 @@ int link_update(Link *l) { r = manager_llmnr_start(l->manager); if (r < 0) return r; - } else - manager_llmnr_stop(l->manager); - + } if (link_get_mdns_support(l) != RESOLVE_SUPPORT_NO) { r = manager_mdns_start(l->manager); if (r < 0) return r; - } else - manager_mdns_stop(l->manager); + } link_allocate_scopes(l); link_add_rrs(l, false); diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index 9469bdac86..abb9c46960 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -25,6 +25,19 @@ void manager_llmnr_stop(Manager *m) { m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); } +void manager_llmnr_maybe_stop(Manager *m) { + assert(m); + + /* This stops LLMNR only when no interface enables LLMNR. */ + + Link *l; + HASHMAP_FOREACH(l, m->links) + if (link_get_llmnr_support(l) != RESOLVE_SUPPORT_NO) + return; + + manager_llmnr_stop(m); +} + int manager_llmnr_start(Manager *m) { int r; diff --git a/src/resolve/resolved-llmnr.h b/src/resolve/resolved-llmnr.h index 4cdd2606e6..358201b5df 100644 --- a/src/resolve/resolved-llmnr.h +++ b/src/resolve/resolved-llmnr.h @@ -11,4 +11,5 @@ int manager_llmnr_ipv4_tcp_fd(Manager *m); int manager_llmnr_ipv6_tcp_fd(Manager *m); void manager_llmnr_stop(Manager *m); +void manager_llmnr_maybe_stop(Manager *m); int manager_llmnr_start(Manager *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index dbaad81734..5ec946717f 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -105,6 +105,9 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void * break; } + /* Now check all the links, and if mDNS/llmr are disabled everywhere, stop them globally too. */ + manager_llmnr_maybe_stop(m); + manager_mdns_maybe_stop(m); return 0; fail: @@ -287,6 +290,9 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void * (void) manager_write_resolv_conf(m); (void) manager_send_changed(m, "DNS"); + /* Now check all the links, and if mDNS/llmr are disabled everywhere, stop them globally too. */ + manager_llmnr_maybe_stop(m); + manager_mdns_maybe_stop(m); return 0; } diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 7e9bb693af..4e6aade726 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -22,6 +22,19 @@ void manager_mdns_stop(Manager *m) { m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); } +void manager_mdns_maybe_stop(Manager *m) { + assert(m); + + /* This stops mDNS only when no interface enables mDNS. */ + + Link *l; + HASHMAP_FOREACH(l, m->links) + if (link_get_mdns_support(l) != RESOLVE_SUPPORT_NO) + return; + + manager_mdns_stop(m); +} + int manager_mdns_start(Manager *m) { int r; diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h index 38ef1808df..c52bce1069 100644 --- a/src/resolve/resolved-mdns.h +++ b/src/resolve/resolved-mdns.h @@ -10,4 +10,5 @@ int manager_mdns_ipv4_fd(Manager *m); int manager_mdns_ipv6_fd(Manager *m); void manager_mdns_stop(Manager *m); +void manager_mdns_maybe_stop(Manager *m); int manager_mdns_start(Manager *m); diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 107f722e7d..c30a2a3fea 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -9,6 +9,7 @@ #include "socket-netlink.h" #include "varlink-io.systemd.Resolve.h" #include "varlink-io.systemd.Resolve.Monitor.h" +#include "varlink-io.systemd.service.h" #include "varlink-util.h" typedef struct LookupParameters { @@ -1410,7 +1411,10 @@ static int varlink_main_server_init(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); - r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Resolve); + r = sd_varlink_server_add_interface_many( + s, + &vl_interface_io_systemd_Resolve, + &vl_interface_io_systemd_service); if (r < 0) return log_error_errno(r, "Failed to add Resolve interface to varlink server: %m"); @@ -1419,7 +1423,10 @@ static int varlink_main_server_init(Manager *m) { "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, "io.systemd.Resolve.ResolveService", vl_method_resolve_service, - "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record); + "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/rpm/triggers.systemd.in b/src/rpm/triggers.systemd.in index d480ab84b6..e257afb33b 100644 --- a/src/rpm/triggers.systemd.in +++ b/src/rpm/triggers.systemd.in @@ -70,9 +70,7 @@ assert(rpm.execute("systemd-tmpfiles", "--create")) %transfiletriggerin -P 1000600 udev -p <lua> -- {{UDEV_RULES_DIR}} -- This script will automatically update udev with new rules if files -- have been installed or updated in {{UDEV_RULES_DIR}}. -if posix.access("/run/udev/control") then - assert(rpm.execute("udevadm", "control", "--reload")) -end +assert(rpm.execute("{{SYSTEMD_UPDATE_HELPER_PATH}}", "mark-reload-system-units", "systemd-udevd.service")) %transfiletriggerin -P 1000500 -p <lua> -- {{SYSCTL_DIR}} -- This script will automatically apply sysctl rules if files have been diff --git a/src/rpm/triggers.systemd.sh.in b/src/rpm/triggers.systemd.sh.in index 1b94f7d73a..8c1f4e0b1f 100644 --- a/src/rpm/triggers.systemd.sh.in +++ b/src/rpm/triggers.systemd.sh.in @@ -75,9 +75,7 @@ fi %transfiletriggerin -P 1000600 udev -- {{UDEV_RULES_DIR}} # This script will automatically update udev with new rules if files # have been installed or updated in {{UDEV_RULES_DIR}}. -if test -e /run/udev/control; then - udevadm control --reload || : -fi +{{SYSTEMD_UPDATE_HELPER_PATH}} mark-reload-system-units systemd-udevd.service || : %transfiletriggerin -P 1000500 -- {{SYSCTL_DIR}} # This script will automatically apply sysctl rules if files have been diff --git a/src/run/run.c b/src/run/run.c index af28d60858..68966ccbda 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -40,6 +40,7 @@ #include "ptyfwd.h" #include "signal-util.h" #include "special.h" +#include "string-table.h" #include "strv.h" #include "terminal-util.h" #include "uid-classification.h" @@ -82,6 +83,7 @@ static bool arg_quiet = false; static bool arg_aggressive_gc = false; static char *arg_working_directory = NULL; static bool arg_shell = false; +static JobMode arg_job_mode = JOB_FAIL; static char **arg_cmdline = NULL; static char *arg_exec_path = NULL; static bool arg_ignore_failure = false; @@ -143,6 +145,8 @@ static int help(void) { " --json=pretty|short|off Print unit name and invocation id as JSON\n" " -G --collect Unload unit after it ran, even when failed\n" " -S --shell Invoke a $SHELL interactively\n" + " --job-mode=MODE Specify how to deal with already queued jobs,\n" + " when queueing a new job\n" " --ignore-failure Ignore the exit status of the invoked process\n" " --background=COLOR Set ANSI color for background\n" "\n%3$sPath options:%4$s\n" @@ -278,6 +282,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_WAIT, ARG_WORKING_DIRECTORY, ARG_SHELL, + ARG_JOB_MODE, ARG_IGNORE_FAILURE, ARG_BACKGROUND, ARG_JSON, @@ -327,6 +332,7 @@ static int parse_argv(int argc, char *argv[]) { { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY }, { "same-dir", no_argument, NULL, 'd' }, { "shell", no_argument, NULL, 'S' }, + { "job-mode", required_argument, NULL, ARG_JOB_MODE }, { "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE }, { "background", required_argument, NULL, ARG_BACKGROUND }, { "json", required_argument, NULL, ARG_JSON }, @@ -621,6 +627,17 @@ static int parse_argv(int argc, char *argv[]) { arg_shell = true; break; + case ARG_JOB_MODE: + if (streq(optarg, "help")) + return DUMP_STRING_TABLE(job_mode, JobMode, _JOB_MODE_MAX); + + r = job_mode_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Invalid job mode: %s", optarg); + + arg_job_mode = r; + break; + case ARG_IGNORE_FAILURE: arg_ignore_failure = true; break; @@ -1768,7 +1785,7 @@ static int make_transient_service_unit( return bus_log_create_error(r); /* Name and mode */ - r = sd_bus_message_append(m, "ss", service, "fail"); + r = sd_bus_message_append(m, "ss", service, job_mode_to_string(arg_job_mode)); if (r < 0) return bus_log_create_error(r); @@ -2283,7 +2300,7 @@ static int start_transient_scope(sd_bus *bus) { return bus_log_create_error(r); /* Name and Mode */ - r = sd_bus_message_append(m, "ss", scope, "fail"); + r = sd_bus_message_append(m, "ss", scope, job_mode_to_string(arg_job_mode)); if (r < 0) return bus_log_create_error(r); @@ -2452,7 +2469,7 @@ static int make_transient_trigger_unit( return bus_log_create_error(r); /* Name and Mode */ - r = sd_bus_message_append(m, "ss", trigger, "fail"); + r = sd_bus_message_append(m, "ss", trigger, job_mode_to_string(arg_job_mode)); if (r < 0) return bus_log_create_error(r); diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index 92b8284219..a7e4047054 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -243,17 +243,21 @@ static int verb_sign(int argc, char *argv[], void *userdata) { struct stat st; if (fstat(srcfd, &st) < 0) - return log_debug_errno(errno, "Failed to stat %s: %m", argv[1]); + return log_error_errno(errno, "Failed to stat %s: %m", argv[1]); r = stat_verify_regular(&st); if (r < 0) - return log_debug_errno(r, "%s is not a regular file: %m", argv[1]); + return log_error_errno(r, "%s is not a regular file: %m", argv[1]); _cleanup_(unlink_and_freep) char *tmp = NULL; _cleanup_close_ int dstfd = open_tmpfile_linkable(arg_output, O_RDWR|O_CLOEXEC, &tmp); if (dstfd < 0) return log_error_errno(r, "Failed to open temporary file: %m"); + r = fchmod_umask(dstfd, 0666); + if (r < 0) + log_debug_errno(r, "Failed to change temporary file mode: %m"); + r = copy_bytes(srcfd, dstfd, UINT64_MAX, COPY_REFLINK); if (r < 0) return log_error_errno(r, "Failed to copy %s to %s: %m", argv[1], tmp); diff --git a/src/shared/common-signal.h b/src/shared/common-signal.h index 1fe7b7623a..3fd7d83299 100644 --- a/src/shared/common-signal.h +++ b/src/shared/common-signal.h @@ -3,7 +3,7 @@ #include <syslog.h> -#include <sd-event.h> +#include "sd-event.h" /* All our long-running services should implement a SIGRTMIN+18 handler that can be used to trigger certain * actions that affect service runtime. The specific action is indicated via the "value integer" you can pass diff --git a/src/shared/copy.c b/src/shared/copy.c index bca41cf122..cb3944b279 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -1147,7 +1147,7 @@ static int fd_copy_directory( if (buf.st_dev != original_device) continue; - r = fd_is_mount_point(dirfd(d), de->d_name, 0); + r = is_mount_point_at(dirfd(d), de->d_name, 0); if (r < 0) return r; if (r > 0) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 5156c63fd3..0f840ff61a 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1572,7 +1572,8 @@ int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, SD_JSON_BUILD_PAIR_CONDITION(timestamp != USEC_INFINITY, "timestamp", SD_JSON_BUILD_UNSIGNED(timestamp)), SD_JSON_BUILD_PAIR_CONDITION(not_after != USEC_INFINITY, "notAfter", SD_JSON_BUILD_UNSIGNED(not_after)), SD_JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", SD_JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")), - SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", SD_JSON_BUILD_UNSIGNED(uid))); + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", SD_JSON_BUILD_UNSIGNED(uid)), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE))); if (r < 0) return log_error_errno(r, "Failed to call Encrypt() varlink call."); if (!isempty(error_id)) { @@ -1629,7 +1630,8 @@ int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, SD_JSON_BUILD_PAIR("blob", SD_JSON_BUILD_VARIANT(jinput)), SD_JSON_BUILD_PAIR_CONDITION(validate_timestamp != USEC_INFINITY, "timestamp", SD_JSON_BUILD_UNSIGNED(validate_timestamp)), SD_JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", SD_JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")), - SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", SD_JSON_BUILD_UNSIGNED(uid))); + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", SD_JSON_BUILD_UNSIGNED(uid)), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE))); if (r < 0) return log_error_errno(r, "Failed to call Decrypt() varlink call."); if (!isempty(error_id)) { diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h index e096b6d4d4..3b9580850c 100644 --- a/src/shared/creds-util.h +++ b/src/shared/creds-util.h @@ -58,8 +58,11 @@ int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret); int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed); typedef enum CredentialFlags { - CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */ - CREDENTIAL_ANY_SCOPE = 1 << 1, /* allow decryption of both system and user credentials */ + CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */ + CREDENTIAL_ANY_SCOPE = 1 << 1, /* allow decryption of both system and user credentials */ + + /* Only used by ipc_{encrypt,decrypt}_credential */ + CREDENTIAL_IPC_ALLOW_INTERACTIVE = 1 << 2, } CredentialFlags; /* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index ce1fa3a3d4..e4ae894782 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2170,6 +2170,9 @@ int dissected_image_mount( assert(m); + if (FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID)) /* For image based mounts we currently require an identity mapping */ + return -EOPNOTSUPP; + /* If 'where' is NULL then we'll use the new mount API to create fsmount() fds for the mounts and * store them in DissectedPartition.fsmount_fd. * @@ -4409,3 +4412,78 @@ int mountfsd_mount_image( return -EOPNOTSUPP; #endif } + +int mountfsd_mount_directory( + const char *path, + int userns_fd, + DissectImageFlags flags, + int *ret_mount_fd) { + + int r; + + /* Pick one identity, not both, that makes no sense. */ + assert(!FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID|DISSECT_IMAGE_IDENTITY_UID)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem"); + if (r < 0) + return log_error_errno(r, "Failed to connect to mountfsd: %m"); + + r = sd_varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for read: %m"); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + _cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (directory_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", path); + + r = sd_varlink_push_dup_fd(vl, directory_fd); + if (r < 0) + return log_error_errno(r, "Failed to push image fd into varlink connection: %m"); + + if (userns_fd >= 0) { + r = sd_varlink_push_dup_fd(vl, userns_fd); + if (r < 0) + return log_error_errno(r, "Failed to push image fd into varlink connection: %m"); + } + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MountFileSystem.MountDirectory", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_UNSIGNED("directoryFileDescriptor", 0), + SD_JSON_BUILD_PAIR_CONDITION(userns_fd >= 0, "userNamespaceFileDescriptor", SD_JSON_BUILD_UNSIGNED(1)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY)), + SD_JSON_BUILD_PAIR_STRING("mode", FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID) ? "foreign" : + FLAGS_SET(flags, DISSECT_IMAGE_IDENTITY_UID) ? "identity" : "auto"), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))); + if (r < 0) + return log_error_errno(r, "Failed to call MountDirectory() varlink call: %m"); + if (!isempty(error_id)) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to call MountDirectory() varlink call: %s", error_id); + + + static const sd_json_dispatch_field dispatch_table[] = { + { "mountFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, 0, SD_JSON_MANDATORY }, + {} + }; + + unsigned fsmount_fd_idx = UINT_MAX; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &fsmount_fd_idx); + if (r < 0) + return log_error_errno(r, "Failed to parse MountImage() reply: %m"); + + _cleanup_close_ int fsmount_fd = sd_varlink_take_fd(vl, fsmount_fd_idx); + if (fsmount_fd < 0) + return log_error_errno(fsmount_fd, "Failed to take mount fd from Varlink connection: %m"); + + *ret_mount_fd = TAKE_FD(fsmount_fd); + return 0; +} diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 2c118b97ef..c001915e89 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -89,6 +89,8 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE = 1 << 25, /* Try to mount the image beneath the specified mountpoint, rather than on top of it, and then umount the top */ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY = 1 << 26, /* Allow userspace verity keyring in /etc/verity.d/ and related dirs */ DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH = 1 << 27, /* Allow interactive authorization when going through mountfsd */ + DISSECT_IMAGE_FOREIGN_UID = 1 << 28, /* Request a foreign UID range mapping */ + DISSECT_IMAGE_IDENTITY_UID = 1 << 29, /* Explicitly request an identity UID range mapping */ } DissectImageFlags; struct DissectedImage { @@ -244,3 +246,4 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m int get_common_dissect_directory(char **ret); int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret); +int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd); diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 3eddc49bf9..3f551b6b1c 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -92,7 +92,7 @@ int efi_loader_get_entries(char ***ret) { return r; /* The variable contains a series of individually NUL terminated UTF-16 strings. We gracefully - * consider the final NUL byte optional (i.e. the last string may or may not end in a NUL byte).*/ + * consider the final NUL byte optional (i.e. the last string may or may not end in a NUL byte). */ for (size_t i = 0, start = 0;; i++) { _cleanup_free_ char *decoded = NULL; diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index c673e344ee..bc997a9383 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -99,7 +99,7 @@ static int do_execute( char *envp[], ExecDirFlags flags) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_hashmap_free_ Hashmap *pids = NULL; bool parallel_execution; int r; @@ -114,12 +114,6 @@ static int do_execute( parallel_execution = FLAGS_SET(flags, EXEC_DIR_PARALLEL) && !callbacks; - if (parallel_execution) { - pids = hashmap_new(NULL); - if (!pids) - return log_oom(); - } - /* Abort execution of this process after the timeout. We simply rely on SIGALRM as * default action terminating the process, and turn on alarm(). */ @@ -176,7 +170,7 @@ static int do_execute( continue; if (parallel_execution) { - r = hashmap_put(pids, PID_TO_PTR(pid), t); + r = hashmap_ensure_put(&pids, &trivial_hash_ops_value_free, PID_TO_PTR(pid), t); if (r < 0) return log_oom(); t = NULL; diff --git a/src/shared/group-record.c b/src/shared/group-record.c index eea60af334..6d696bdaaa 100644 --- a/src/shared/group-record.c +++ b/src/shared/group-record.c @@ -331,9 +331,24 @@ int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord ** return 0; } +bool group_record_matches_group_name(const GroupRecord *g, const char *group_name) { + assert(g); + assert(group_name); + + if (streq_ptr(g->group_name, group_name)) + return true; + + if (streq_ptr(g->group_name_and_realm_auto, group_name)) + return true; + + return false; +} + int group_record_match(GroupRecord *h, const UserDBMatch *match) { assert(h); - assert(match); + + if (!match) + return true; if (h->gid < match->gid_min || h->gid > match->gid_max) return false; diff --git a/src/shared/group-record.h b/src/shared/group-record.h index a2cef81c8a..5705fe2511 100644 --- a/src/shared/group-record.h +++ b/src/shared/group-record.h @@ -47,3 +47,5 @@ int group_record_match(GroupRecord *h, const UserDBMatch *match); const char* group_record_group_name_and_realm(GroupRecord *h); UserDisposition group_record_disposition(GroupRecord *h); + +bool group_record_matches_group_name(const GroupRecord *g, const char *groupname); diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index 3818904733..1a6b68f920 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -4,7 +4,7 @@ #include "libmount-util.h" -int libmount_parse( +int libmount_parse_full( const char *path, FILE *source, struct libmnt_table **ret_table, diff --git a/src/shared/libmount-util.h b/src/shared/libmount-util.h index 2f789e7426..a363f7e9b7 100644 --- a/src/shared/libmount-util.h +++ b/src/shared/libmount-util.h @@ -9,12 +9,27 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct libmnt_table*, mnt_free_table, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct libmnt_iter*, mnt_free_iter, NULL); -int libmount_parse( +int libmount_parse_full( const char *path, FILE *source, struct libmnt_table **ret_table, struct libmnt_iter **ret_iter); +static inline int libmount_parse_mountinfo( + FILE *source, + struct libmnt_table **ret_table, + struct libmnt_iter **ret_iter) { + + return libmount_parse_full("/proc/self/mountinfo", source, ret_table, ret_iter); +} + +static inline int libmount_parse_with_utab( + struct libmnt_table **ret_table, + struct libmnt_iter **ret_iter) { + + return libmount_parse_full(NULL, NULL, ret_table, ret_iter); +} + int libmount_is_leaf( struct libmnt_table *table, struct libmnt_fs *fs); diff --git a/src/shared/machine-id-setup.c b/src/shared/machine-id-setup.c index 4240525742..d444ea06e9 100644 --- a/src/shared/machine-id-setup.c +++ b/src/shared/machine-id-setup.c @@ -74,7 +74,7 @@ static int acquire_machine_id(const char *root, bool machine_id_from_firmware, s } /* Then, try reading the D-Bus machine ID, unless it is a symlink */ - fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT | CHASE_NOFOLLOW, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); + fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT|CHASE_NOFOLLOW|CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); if (fd >= 0 && id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret) >= 0) { log_info("Initializing machine ID from D-Bus machine ID."); return 0; @@ -131,54 +131,89 @@ static int acquire_machine_id(const char *root, bool machine_id_from_firmware, s } int machine_id_setup(const char *root, sd_id128_t machine_id, MachineIdSetupFlags flags, sd_id128_t *ret) { - const char *etc_machine_id, *run_machine_id; - _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *etc_machine_id = NULL, *run_machine_id = NULL; bool writable, write_run_machine_id = true; + _cleanup_close_ int fd = -EBADF, run_fd = -EBADF; + bool unlink_run_machine_id = false; int r; - etc_machine_id = prefix_roota(root, "/etc/machine-id"); - WITH_UMASK(0000) { - /* We create this 0444, to indicate that this isn't really - * something you should ever modify. Of course, since the file - * will be owned by root it doesn't matter much, but maybe - * people look. */ + _cleanup_close_ int inode_fd = -EBADF; + + r = chase("/etc/machine-id", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_REGULAR, &etc_machine_id, &inode_fd); + if (r == -ENOENT) { + _cleanup_close_ int etc_fd = -EBADF; + _cleanup_free_ char *etc = NULL; + + r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, &etc, &etc_fd); + if (r < 0) + return log_error_errno(r, "Failed to open '/etc/': %m"); - (void) mkdir_parents(etc_machine_id, 0755); - fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444); - if (fd < 0) { - int old_errno = errno; + etc_machine_id = path_join(etc, "machine-id"); + if (!etc_machine_id) + return log_oom(); - fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); + /* We create this 0444, to indicate that this isn't really something you should ever + * modify. Of course, since the file will be owned by root it doesn't matter much, but maybe + * people look. */ + + fd = openat(etc_fd, "machine-id", O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW|O_CLOEXEC, 0444); if (fd < 0) { - if (old_errno == EROFS && errno == ENOENT) + if (errno == EROFS) return log_error_errno(errno, - "System cannot boot: Missing /etc/machine-id and /etc is mounted read-only.\n" - "Booting up is supported only when:\n" - "1) /etc/machine-id exists and is populated.\n" - "2) /etc/machine-id exists and is empty.\n" - "3) /etc/machine-id is missing and /etc is writable.\n"); - else - return log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); + "System cannot boot: Missing %s and %s/ is read-only.\n" + "Booting up is supported only when:\n" + "1) /etc/machine-id exists and is populated.\n" + "2) /etc/machine-id exists and is empty.\n" + "3) /etc/machine-id is missing and /etc/ is writable.", + etc_machine_id, + etc); + + return log_error_errno(errno, "Cannot create '%s': %m", etc_machine_id); } - writable = false; - } else + log_debug("Successfully opened new '%s' file.", etc_machine_id); writable = true; + } else if (r < 0) + return log_error_errno(r, "Cannot open '/etc/machine-id': %m"); + else { + /* We pinned the inode, now try to convert it into a writable file */ + + fd = xopenat_full(inode_fd, /* path= */ NULL, O_RDWR|O_CLOEXEC, XO_REGULAR, 0444); + if (fd < 0) { + log_debug_errno(fd, "Failed to open '%s' in writable mode, retrying in read-only mode: %m", etc_machine_id); + + /* If that didn't work, convert it into a readable file */ + fd = xopenat_full(inode_fd, /* path= */ NULL, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); + if (fd < 0) + return log_error_errno(fd, "Cannot open '%s' in neither writable nor read-only mode: %m", etc_machine_id); + + log_debug("Successfully opened existing '%s' file in read-only mode.", etc_machine_id); + writable = false; + } else { + log_debug("Successfully opened existing '%s' file in writable mode.", etc_machine_id); + writable = true; + } + } } /* A we got a valid machine ID argument, that's what counts */ if (sd_id128_is_null(machine_id) || FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_FIRMWARE)) { /* Try to read any existing machine ID */ - if (id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id) >= 0) + r = id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id); + if (r >= 0) goto finish; + log_debug_errno(r, "Unable to read current machine ID, acquiring new one: %m"); + /* Hmm, so, the id currently stored is not useful, then let's acquire one. */ r = acquire_machine_id(root, FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_FIRMWARE), &machine_id); if (r < 0) return r; - write_run_machine_id = !r; + + write_run_machine_id = !r; /* acquire_machine_id() returns 1 in case we read this machine ID + * from /run/machine-id */ } if (writable) { @@ -204,40 +239,52 @@ int machine_id_setup(const char *root, sd_id128_t machine_id, MachineIdSetupFlag r = id128_write_fd(fd, ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, machine_id); if (r < 0) return log_error_errno(r, "Failed to write %s: %m", etc_machine_id); - else - goto finish; + + goto finish; } } - fd = safe_close(fd); + /* Hmm, we couldn't or shouldn't write the machine-id to /etc/? So let's write it to /run/machine-id + * as a replacement */ - /* Hmm, we couldn't or shouldn't write the machine-id to /etc? - * So let's write it to /run/machine-id as a replacement */ + if (write_run_machine_id) { + _cleanup_free_ char *run = NULL; - run_machine_id = prefix_roota(root, "/run/machine-id"); + r = chase("/run/", root, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, &run, &run_fd); + if (r < 0) + return log_error_errno(r, "Failed to open '/run/': %m"); - if (write_run_machine_id) { - WITH_UMASK(0022) - r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id); - if (r < 0) { - (void) unlink(run_machine_id); - return log_error_errno(r, "Cannot write %s: %m", run_machine_id); + run_machine_id = path_join(run, "machine-id"); + if (!run_machine_id) + return log_oom(); + + WITH_UMASK(0022) { + r = id128_write_at(run_fd, "machine-id", ID128_FORMAT_PLAIN, machine_id); + if (r < 0) { + (void) unlinkat(run_fd, "machine-id", /* flags = */ 0); + return log_error_errno(r, "Cannot write '%s': %m", run_machine_id); + } } + + unlink_run_machine_id = true; + } else { + r = chase("/run/machine-id", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_REGULAR, &run_machine_id, /* ret_inode_fd= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to open '/run/machine-id': %m"); } /* And now, let's mount it over */ - r = mount_follow_verbose(LOG_ERR, run_machine_id, etc_machine_id, NULL, MS_BIND, NULL); + r = mount_follow_verbose(LOG_ERR, run_machine_id, FORMAT_PROC_FD_PATH(fd), /* fstype= */ NULL, MS_BIND, /* options= */ NULL); if (r < 0) { - (void) unlink(run_machine_id); + if (unlink_run_machine_id) + (void) unlinkat(ASSERT_FD(run_fd), "machine-id", /* flags = */ 0); return r; } - log_full(FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_TRANSIENT) ? LOG_DEBUG : LOG_INFO, "Installed transient %s file.", etc_machine_id); + log_full(FLAGS_SET(flags, MACHINE_ID_SETUP_FORCE_TRANSIENT) ? LOG_DEBUG : LOG_INFO, "Installed transient '%s' file.", etc_machine_id); - /* Mark the mount read-only */ - r = mount_follow_verbose(LOG_WARNING, NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL); - if (r < 0) - return r; + /* Mark the mount read-only (note: we are not going via FORMAT_PROC_FD_PATH() here because that fd is not updated to our new bind mount) */ + (void) mount_follow_verbose(LOG_WARNING, /* source= */ NULL, etc_machine_id, /* fstype= */ NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, /* options= */ NULL); finish: if (!in_initrd()) @@ -250,30 +297,39 @@ finish: } int machine_id_commit(const char *root) { - _cleanup_close_ int fd = -EBADF, initial_mntns_fd = -EBADF; - const char *etc_machine_id; sd_id128_t id; int r; - /* Before doing anything, sync everything to ensure any changes by first-boot units are persisted. - * - * First, explicitly sync the file systems we care about and check if it worked. */ - FOREACH_STRING(sync_path, "/etc/", "/var/") { - r = syncfs_path(AT_FDCWD, sync_path); - if (r < 0) - return log_error_errno(r, "Cannot sync %s: %m", sync_path); - } + if (empty_or_root(root)) { + /* Before doing anything, sync everything to ensure any changes by first-boot units are + * persisted. + * + * First, explicitly sync the file systems we care about and check if it worked. */ + FOREACH_STRING(sync_path, "/etc/", "/var/") { + r = syncfs_path(AT_FDCWD, sync_path); + if (r < 0) + return log_error_errno(r, "Cannot sync %s: %m", sync_path); + } - /* Afterwards, sync() the rest too, but we can't check the return value for these. */ - sync(); + /* Afterwards, sync() the rest too, but we can't check the return value for these. */ + sync(); + } /* Replaces a tmpfs bind mount of /etc/machine-id by a proper file, atomically. For this, the umount is removed * in a mount namespace, a new file is created at the right place. Afterwards the mount is also removed in the * original mount namespace, thus revealing the file that was just created. */ - etc_machine_id = prefix_roota(root, "/etc/machine-id"); + _cleanup_close_ int etc_fd = -EBADF; + _cleanup_free_ char *etc = NULL; + r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, &etc, &etc_fd); + if (r < 0) + return log_error_errno(r, "Failed to open /etc/: %m"); + + _cleanup_free_ char *etc_machine_id = path_join(etc, "machine-id"); + if (!etc_machine_id) + return log_oom(); - r = path_is_mount_point(etc_machine_id); + r = is_mount_point_at(etc_fd, "machine-id", /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); if (r == 0) { @@ -282,9 +338,12 @@ int machine_id_commit(const char *root) { } /* Read existing machine-id */ - fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); + + _cleanup_close_ int fd = xopenat_full(etc_fd, "machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, MODE_INVALID); if (fd < 0) - return log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); + return log_error_errno(fd, "Cannot open %s: %m", etc_machine_id); + + etc_fd = safe_close(etc_fd); r = fd_is_temporary_fs(fd); if (r < 0) @@ -298,10 +357,8 @@ int machine_id_commit(const char *root) { if (r < 0) return log_error_errno(r, "We didn't find a valid machine ID in %s: %m", etc_machine_id); - fd = safe_close(fd); - /* Store current mount namespace */ - initial_mntns_fd = namespace_open_by_type(NAMESPACE_MOUNT); + _cleanup_close_ int initial_mntns_fd = namespace_open_by_type(NAMESPACE_MOUNT); if (initial_mntns_fd < 0) return log_error_errno(initial_mntns_fd, "Can't fetch current mount namespace: %m"); @@ -310,15 +367,23 @@ int machine_id_commit(const char *root) { if (r < 0) return log_error_errno(r, "Failed to set up new mount namespace: %m"); - r = umount_verbose(LOG_ERR, etc_machine_id, 0); + /* Open /etc/ again after we transitioned into our own private mount namespace */ + _cleanup_close_ int etc_fd_again = -EBADF; + r = chase("/etc/", root, CHASE_PREFIX_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &etc_fd_again); + if (r < 0) + return log_error_errno(r, "Failed to open /etc/: %m"); + + r = umountat_detach_verbose(LOG_ERR, etc_fd_again, "machine-id"); if (r < 0) return r; /* Update a persistent version of etc_machine_id */ - r = id128_write(etc_machine_id, ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, id); + r = id128_write_at(etc_fd_again, "machine-id", ID128_FORMAT_PLAIN | ID128_SYNC_ON_WRITE, id); if (r < 0) return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id); + etc_fd_again = safe_close(etc_fd_again); + /* Return to initial namespace and proceed a lazy tmpfs unmount */ r = namespace_enter(/* pidns_fd = */ -EBADF, initial_mntns_fd, @@ -326,10 +391,15 @@ int machine_id_commit(const char *root) { /* userns_fd = */ -EBADF, /* root_fd = */ -EBADF); if (r < 0) - return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); + return log_warning_errno(r, + "Failed to switch back to initial mount namespace: %m.\n" + "We'll keep transient %s file until next reboot.", etc_machine_id); - if (umount2(etc_machine_id, MNT_DETACH) < 0) - return log_warning_errno(errno, "Failed to unmount transient %s file: %m.\nWe keep that mount until next reboot.", etc_machine_id); + r = umountat_detach_verbose(LOG_DEBUG, fd, /* filename= */ NULL); + if (r < 0) + return log_warning_errno(r, + "Failed to unmount transient %s file: %m.\n" + "We keep that mount until next reboot.", etc_machine_id); return 0; } diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 55d3899532..87c7d52a97 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -206,7 +206,7 @@ static int protofile_print_item( * delimiter. To work around this limitation, mkfs.xfs allows escaping whitespace by using the / * character (which isn't allowed in filenames and as such can be used to escape whitespace). See * https://lore.kernel.org/linux-xfs/20230222090303.h6tujm7y32gjhgal@andromeda/T/#m8066b3e7d62a080ee7434faac4861d944e64493b - * for more information.*/ + * for more information. */ if (strchr(de->d_name, ' ')) { copy = strdup(de->d_name); diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index 73be3b5dce..db8c2b61d2 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -34,11 +34,12 @@ #include "virt.h" typedef enum MountMode { - MNT_NONE = 0, - MNT_FATAL = 1 << 0, - MNT_IN_CONTAINER = 1 << 1, - MNT_CHECK_WRITABLE = 1 << 2, - MNT_FOLLOW_SYMLINK = 1 << 3, + MNT_NONE = 0, + MNT_FATAL = 1 << 0, + MNT_IN_CONTAINER = 1 << 1, + MNT_CHECK_WRITABLE = 1 << 2, + MNT_FOLLOW_SYMLINK = 1 << 3, + MNT_USRQUOTA_GRACEFUL = 1 << 4, } MountMode; typedef struct MountPoint { @@ -92,7 +93,7 @@ static const MountPoint mount_table[] = { mac_smack_use, MNT_FATAL }, #endif { "tmpfs", "/dev/shm", "tmpfs", "mode=01777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, + NULL, MNT_FATAL|MNT_IN_CONTAINER|MNT_USRQUOTA_GRACEFUL }, { "devpts", "/dev/pts", "devpts", "mode=" STRINGIFY(TTY_MODE) ",gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, NULL, MNT_IN_CONTAINER }, #if ENABLE_SMACK @@ -189,13 +190,29 @@ static int mount_one(const MountPoint *p, bool relabel) { else (void) mkdir_p(p->where, 0755); + _cleanup_free_ char *extend_options = NULL; + const char *o = p->options; + if (FLAGS_SET(p->mode, MNT_USRQUOTA_GRACEFUL)) { + r = mount_option_supported(p->type, "usrquota", /* value= */ NULL); + if (r < 0) + log_warning_errno(r, "Unable to determine whether %s supports 'usrquota' mount option, assuming not: %m", p->type); + else if (r == 0) + log_info("Not enabling 'usrquota' on '%s' as kernel lacks support for it.", p->where); + else { + if (!strextend_with_separator(&extend_options, ",", p->options ?: POINTER_MAX, "usrquota")) + return log_oom(); + + o = extend_options; + } + } + log_debug("Mounting %s to %s of type %s with options %s.", p->what, p->where, p->type, - strna(p->options)); + strna(o)); - r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, p->options, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)); + r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, o, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)); if (r < 0) return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 39aa3d3ec8..75a18051bc 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -60,7 +60,7 @@ int umount_recursive_full(const char *prefix, int flags, char **keep) { _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL; bool again = false; - r = libmount_parse("/proc/self/mountinfo", f, &table, &iter); + r = libmount_parse_mountinfo(f, &table, &iter); if (r < 0) return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m"); @@ -217,7 +217,7 @@ int bind_remount_recursive_with_mountinfo( rewind(proc_self_mountinfo); - r = libmount_parse("/proc/self/mountinfo", proc_self_mountinfo, &table, &iter); + r = libmount_parse_mountinfo(proc_self_mountinfo, &table, &iter); if (r < 0) return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m"); @@ -736,16 +736,62 @@ int mount_verbose_full( int umount_verbose( int error_log_level, - const char *what, + const char *where, int flags) { - assert(what); + assert(where); - log_debug("Umounting %s...", what); + log_debug("Unmounting '%s'...", where); - if (umount2(what, flags) < 0) - return log_full_errno(error_log_level, errno, - "Failed to unmount %s: %m", what); + if (umount2(where, flags) < 0) + return log_full_errno(error_log_level, errno, "Failed to unmount '%s': %m", where); + + return 0; +} + +int umountat_detach_verbose( + int error_log_level, + int fd, + const char *where) { + + /* Similar to umountat_verbose(), but goes by fd + path. This implies MNT_DETACH, since to do this we + * must pin the inode in question via an fd. */ + + assert(fd >= 0 || fd == AT_FDCWD); + + /* If neither fd nor path are specified take this as reference to the cwd */ + if (fd == AT_FDCWD && isempty(where)) + return umount_verbose(error_log_level, ".", MNT_DETACH|UMOUNT_NOFOLLOW); + + /* If we don't actually take the fd into consideration for this operation shortcut things, so that we + * don't have to open the inode */ + if (fd == AT_FDCWD || path_is_absolute(where)) + return umount_verbose(error_log_level, where, MNT_DETACH|UMOUNT_NOFOLLOW); + + _cleanup_free_ char *prefix = NULL; + const char *p; + if (fd_get_path(fd, &prefix) < 0) + p = "<fd>"; /* if we can't get the path, return something vaguely useful */ + else + p = prefix; + _cleanup_free_ char *joined = isempty(where) ? strdup(p) : path_join(p, where); + + log_debug("Unmounting '%s'...", strna(joined)); + + _cleanup_close_ int inode_fd = -EBADF; + int mnt_fd; + if (isempty(where)) + mnt_fd = fd; + else { + inode_fd = openat(fd, where, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (inode_fd < 0) + return log_full_errno(error_log_level, errno, "Failed to pin '%s': %m", strna(joined)); + + mnt_fd = inode_fd; + } + + if (umount2(FORMAT_PROC_FD_PATH(mnt_fd), MNT_DETACH) < 0) + return log_full_errno(error_log_level, errno, "Failed to unmount '%s': %m", strna(joined)); return 0; } @@ -1300,7 +1346,7 @@ int fd_make_mount_point(int fd) { assert(fd >= 0); - r = fd_is_mount_point(fd, NULL, 0); + r = is_mount_point_at(fd, NULL, 0); if (r < 0) return log_debug_errno(r, "Failed to determine whether file descriptor is a mount point: %m"); if (r > 0) @@ -1313,9 +1359,15 @@ int fd_make_mount_point(int fd) { return 1; } -int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner, RemountIdmapping idmapping) { +int make_userns(uid_t uid_shift, + uid_t uid_range, + uid_t source_owner, + uid_t dest_owner, + RemountIdmapping idmapping) { + _cleanup_close_ int userns_fd = -EBADF; _cleanup_free_ char *line = NULL; + uid_t source_base = 0; /* Allocates a userns file descriptor with the mapping we need. For this we'll fork off a child * process whose only purpose is to give us a new user namespace. It's killed when we got it. */ @@ -1323,8 +1375,18 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest if (!userns_shift_range_valid(uid_shift, uid_range)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid UID range for user namespace."); - if (IN_SET(idmapping, REMOUNT_IDMAPPING_NONE, REMOUNT_IDMAPPING_HOST_ROOT)) { - if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0u, uid_shift, uid_range) < 0) + switch (idmapping) { + + case REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT: + source_base = FOREIGN_UID_BASE; + _fallthrough_; + + case REMOUNT_IDMAPPING_NONE: + case REMOUNT_IDMAPPING_HOST_ROOT: + + if (asprintf(&line, + UID_FMT " " UID_FMT " " UID_FMT "\n", + source_base, uid_shift, uid_range) < 0) return log_oom_debug(); /* If requested we'll include an entry in the mapping so that the host root user can make @@ -1341,28 +1403,34 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest if (idmapping == REMOUNT_IDMAPPING_HOST_ROOT) if (strextendf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", - UID_MAPPED_ROOT, 0u, 1u) < 0) + UID_MAPPED_ROOT, (uid_t) 0u, (uid_t) 1u) < 0) return log_oom_debug(); - } - if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER) { + break; + + case REMOUNT_IDMAPPING_HOST_OWNER: /* Remap the owner of the bind mounted directory to the root user within the container. This * way every file written by root within the container to the bind-mounted directory will * be owned by the original user from the host. All other users will remain unmapped. */ - if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", source_owner, uid_shift, 1u) < 0) + if (asprintf(&line, + UID_FMT " " UID_FMT " " UID_FMT "\n", + source_owner, uid_shift, (uid_t) 1u) < 0) return log_oom_debug(); - } + break; - if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER) { + case REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER: /* Remap the owner of the bind mounted directory to the owner of the target directory * within the container. This way every file written by target directory owner within the * container to the bind-mounted directory will be owned by the original host user. * All other users will remain unmapped. */ - if (asprintf( - &line, + if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", - source_owner, dest_owner, 1u) < 0) + source_owner, dest_owner, (uid_t) 1u) < 0) return log_oom_debug(); + break; + + default: + assert_not_reached(); } /* We always assign the same UID and GID ranges */ @@ -1503,7 +1571,7 @@ int get_sub_mounts(const char *prefix, SubMount **ret_mounts, size_t *ret_n_moun assert(ret_mounts); assert(ret_n_mounts); - r = libmount_parse("/proc/self/mountinfo", NULL, &table, &iter); + r = libmount_parse_mountinfo(/* source = */ NULL, &table, &iter); if (r < 0) return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m"); @@ -1826,7 +1894,12 @@ static int path_get_mount_info_at( if (r < 0) return log_debug_errno(r, "Failed to get mount ID: %m"); - r = libmount_parse("/proc/self/mountinfo", NULL, &table, &iter); + /* When getting options is requested, we also need to parse utab, otherwise userspace options like + * "_netdev" will be lost. */ + if (ret_options) + r = libmount_parse_with_utab(&table, &iter); + else + r = libmount_parse_mountinfo(/* source = */ NULL, &table, &iter); if (r < 0) return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m"); diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 496a95ab05..895fe2b2cd 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -79,6 +79,11 @@ int umount_verbose( const char *where, int flags); +int umountat_detach_verbose( + int error_log_level, + int fd, + const char *where); + int mount_option_mangle( const char *options, unsigned long mount_flags, @@ -151,6 +156,9 @@ typedef enum RemountIdmapping { * to add inodes to file systems mapped this way should set this flag, but given it comes with * certain security implications defaults to off, and requires explicit opt-in. */ REMOUNT_IDMAPPING_HOST_ROOT, + /* Much like REMOUNT_IDMAPPING_HOST_ROOT, but the source mapping is not from 0…65535 but from the + * foreign UID range. */ + REMOUNT_IDMAPPING_FOREIGN_WITH_HOST_ROOT, /* Define a mapping from root user within the container to the owner of the bind mounted directory. * This ensures no root-owned files will be written in a bind-mounted directory owned by a different * user. No other users are mapped. */ diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index b60fc67b26..302cf3d0f6 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1398,7 +1398,7 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { req->message = UI_get0_output_string(uis); - _cleanup_(strv_freep) char **l = NULL; + _cleanup_strv_free_ char **l = NULL; r = ask_password_auto(req, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l); if (r < 0) { log_error_errno(r, "Failed to query for PIN: %m"); diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 5ec9a069b7..96c1c07bc8 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -97,7 +97,7 @@ int pcrextend_file_system_word(const char *path, char **ret_word, char **ret_nor if (dfd < 0) return log_error_errno(dfd, "Failed to open path '%s': %m", path); - r = fd_is_mount_point(dfd, NULL, 0); + r = is_mount_point_at(dfd, NULL, 0); if (r < 0) return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized_path); if (r == 0) diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index d8a5537658..4eb4858931 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -183,34 +183,20 @@ int terminal_urlify_man(const char *page, const char *section, char **ret) { return terminal_urlify(url, text, ret); } -typedef enum { - LINE_SECTION, - LINE_COMMENT, - LINE_NORMAL, -} LineType; - -static LineType classify_line_type(const char *line, CatFlags flags) { - const char *t = skip_leading_chars(line, WHITESPACE); - - if ((flags & CAT_FORMAT_HAS_SECTIONS) && *t == '[') - return LINE_SECTION; - if (IN_SET(*t, '#', ';', '\0')) - return LINE_COMMENT; - return LINE_NORMAL; -} - static int cat_file(const char *filename, bool newline, CatFlags flags) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = NULL; int r; + assert(filename); + f = fopen(filename, "re"); if (!f) - return -errno; + return log_error_errno(errno, "Failed to open \"%s\": %m", filename); r = terminal_urlify_path(filename, NULL, &urlified); if (r < 0) - return r; + return log_error_errno(r, "Failed to urlify path \"%s\": %m", filename); printf("%s%s# %s%s\n", newline ? "\n" : "", @@ -219,7 +205,7 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) { ansi_normal()); fflush(stdout); - for (;;) { + for (bool continued = false;;) { _cleanup_free_ char *line = NULL; r = read_line(f, LONG_LINE_MAX, &line); @@ -228,58 +214,78 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) { if (r == 0) break; - LineType line_type = classify_line_type(line, flags); - if (FLAGS_SET(flags, CAT_TLDR)) { - if (line_type == LINE_SECTION) { - /* The start of a section, let's not print it yet. */ + const char *l = skip_leading_chars(line, WHITESPACE); + + /* comment */ + if (*l != '\0' && strchr(COMMENTS, *l)) { + if (!FLAGS_SET(flags, CAT_TLDR)) + printf("%s%s%s\n", ansi_highlight_grey(), line, ansi_normal()); + continue; + } + + /* empty line */ + if (FLAGS_SET(flags, CAT_TLDR) && (isempty(l) || streq(l, "\\"))) + continue; + + /* section */ + if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && *l == '[' && !continued) { + if (FLAGS_SET(flags, CAT_TLDR)) + /* On TLDR, let's not print it yet. */ free_and_replace(section, line); - continue; - } + else + printf("%s%s%s\n", ansi_highlight_cyan(), line, ansi_normal()); + continue; + } - if (line_type == LINE_COMMENT) - continue; + /* normal line */ - /* Before we print the actual line, print the last section header */ - if (section) { - /* Do not print redundant section headers */ - if (!streq_ptr(section, old_section)) - printf("%s%s%s\n", - ansi_highlight_cyan(), - section, - ansi_normal()); + /* Before we print the line, print the last section header. */ + if (FLAGS_SET(flags, CAT_TLDR) && section) { + /* Do not print redundant section headers */ + if (!streq_ptr(section, old_section)) + printf("%s%s%s\n", ansi_highlight_cyan(), section, ansi_normal()); - free_and_replace(old_section, section); - } + free_and_replace(old_section, section); + } + + /* Check if the line ends with a backslash. */ + bool escaped = false; + char *e; + for (e = line; *e != '\0'; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + } + + /* Highlight the trailing backslash. */ + if (escaped) { + assert(e > line); + *(e-1) = '\0'; + + if (!strextend(&line, ansi_highlight_red(), "\\", ansi_normal())) + return log_oom(); } /* Highlight the left side (directive) of a Foo=bar assignment */ - if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && line_type == LINE_NORMAL) { + if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && !continued) { const char *p = strchr(line, '='); if (p) { - _cleanup_free_ char *highlighted = NULL, *directive = NULL; + _cleanup_free_ char *directive = NULL; directive = strndup(line, p - line); if (!directive) return log_oom(); - highlighted = strjoin(ansi_highlight_green(), - directive, - "=", - ansi_normal(), - p + 1); - if (!highlighted) - return log_oom(); - - free_and_replace(line, highlighted); + printf("%s%s=%s%s\n", ansi_highlight_green(), directive, ansi_normal(), p + 1); + continued = escaped; + continue; } } - printf("%s%s%s\n", - line_type == LINE_SECTION ? ansi_highlight_cyan() : - line_type == LINE_COMMENT ? ansi_highlight_grey() : - "", - line, - line_type != LINE_NORMAL ? ansi_normal() : ""); + /* Otherwise, print the line as is. */ + printf("%s\n", line); + continued = escaped; } return 0; diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index d47bbccd9e..fb7c1c05cf 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -426,7 +426,7 @@ static int insert_window_title_fix(PTYForward *f, size_t offset) { if (!f->osc_sequence) return 0; - const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence*/ + const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence */ if (!t) return 0; diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index 767db48cc0..2f50fa30ea 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -219,7 +219,7 @@ static int rm_rf_inner_child( return 0; /* Stop at mount points */ - r = fd_is_mount_point(fd, fname, 0); + r = is_mount_point_at(fd, fname, 0); if (r < 0) return r; if (r > 0) diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index ff49e219a0..27ed339ff8 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -31,7 +31,6 @@ #if HAVE_SELINUX DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(context_t, context_free, NULL); -#define _cleanup_context_free_ _cleanup_(context_freep) typedef enum Initialized { UNINITIALIZED, @@ -471,7 +470,7 @@ int mac_selinux_get_our_label(char **ret) { int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **ret_label) { #if HAVE_SELINUX _cleanup_freecon_ char *mycon = NULL, *peercon = NULL, *fcon = NULL; - _cleanup_context_free_ context_t pcon = NULL, bcon = NULL; + _cleanup_(context_freep) context_t pcon = NULL, bcon = NULL; const char *range = NULL, *bcon_str = NULL; security_class_t sclass; int r; diff --git a/src/shared/shift-uid.c b/src/shared/shift-uid.c index 435ba46c8e..d8e7b493c5 100644 --- a/src/shared/shift-uid.c +++ b/src/shared/shift-uid.c @@ -363,7 +363,7 @@ read_only: if (!is_toplevel) { _cleanup_free_ char *name = NULL; - /* When we hit a ready-only subtree we simply skip it, but log about it. */ + /* When we hit a read-only subtree we simply skip it, but log about it. */ (void) fd_get_path(fd, &name); log_debug("Skipping read-only file or directory %s.", strna(name)); r = changed; diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index a828ffa0a7..a9c635a478 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -7,6 +7,7 @@ #include "hashmap.h" #include "hexdecoct.h" #include "path-util.h" +#include "percent-util.h" #include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" @@ -54,6 +55,26 @@ static void show_self_modifiable( printf("%13s %s\n", i == value ? heading : "", *i); } +static void show_tmpfs_limit(const char *tmpfs, const TmpfsLimit *limit, uint32_t scale) { + assert(tmpfs); + assert(limit); + + if (!limit->is_set) + return; + + printf(" %s Limit:", tmpfs); + + if (limit->limit != UINT64_MAX) + printf(" %s", FORMAT_BYTES(limit->limit)); + if (limit->limit == UINT64_MAX || limit->limit_scale != UINT32_MAX) { + if (limit->limit != UINT64_MAX) + printf(" or"); + + printf(" %i%%", UINT32_SCALE_TO_PERCENT(scale)); + } + printf("\n"); +} + void user_record_show(UserRecord *hr, bool show_full_group_info) { _cleanup_strv_free_ char **langs = NULL; const char *hd, *ip, *shell; @@ -65,6 +86,13 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" User name: %s\n", user_record_user_name_and_realm(hr)); + if (!strv_isempty(hr->aliases)) { + STRV_FOREACH(i, hr->aliases) + printf(i == hr->aliases ? + " Alias: %s" : ", %s", *i); + putchar('\n'); + } + if (hr->state) { const char *color; @@ -361,6 +389,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->io_weight != UINT64_MAX) printf(" IO Weight: %" PRIu64 "\n", hr->io_weight); + show_tmpfs_limit("TMP", &hr->tmp_limit, user_record_tmp_limit_scale(hr)); + show_tmpfs_limit("SHM", &hr->dev_shm_limit, user_record_dev_shm_limit_scale(hr)); + if (hr->access_mode != MODE_INVALID) printf(" Access Mode: 0%03o\n", user_record_access_mode(hr)); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 88970425cc..1e5c3f589b 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -15,6 +15,7 @@ #include "locale-util.h" #include "memory-util.h" #include "path-util.h" +#include "percent-util.h" #include "pkcs11-util.h" #include "rlimit-util.h" #include "sha256.h" @@ -95,6 +96,8 @@ UserRecord* user_record_new(void) { .drop_caches = -1, .auto_resize_mode = _AUTO_RESIZE_MODE_INVALID, .rebalance_weight = REBALANCE_WEIGHT_UNSET, + .tmp_limit = TMPFS_LIMIT_NULL, + .dev_shm_limit = TMPFS_LIMIT_NULL, }; return h; @@ -140,6 +143,7 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->user_name); free(h->realm); free(h->user_name_and_realm_auto); + strv_free(h->aliases); free(h->real_name); free(h->email_address); erase_and_free(h->password_hint); @@ -981,6 +985,40 @@ static int dispatch_rebalance_weight(const char *name, sd_json_variant *variant, return 0; } +static int dispatch_tmpfs_limit(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TmpfsLimit *limit = ASSERT_PTR(userdata); + int r; + + if (sd_json_variant_is_null(variant)) { + *limit = TMPFS_LIMIT_NULL; + return 0; + } + + r = sd_json_dispatch_uint64(name, variant, flags, &limit->limit); + if (r < 0) + return r; + + limit->is_set = true; + return 0; +} + +static int dispatch_tmpfs_limit_scale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TmpfsLimit *limit = ASSERT_PTR(userdata); + int r; + + if (sd_json_variant_is_null(variant)) { + *limit = TMPFS_LIMIT_NULL; + return 0; + } + + r = sd_json_dispatch_uint32(name, variant, flags, &limit->limit_scale); + if (r < 0) + return r; + + limit->is_set = true; + return 0; +} + static int dispatch_privileged(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field privileged_dispatch_table[] = { @@ -1274,6 +1312,10 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j { "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT }, { "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT }, { "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT }, + { "tmpLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, tmp_limit), 0, }, + { "tmpLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, tmp_limit), 0, }, + { "devShmLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, dev_shm_limit), 0, }, + { "devShmLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, dev_shm_limit), 0, }, {}, }; @@ -1538,6 +1580,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load static const sd_json_dispatch_field user_dispatch_table[] = { { "userName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), SD_JSON_RELAX }, + { "aliases", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, aliases), SD_JSON_RELAX }, { "realm", SD_JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 }, { "blobDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), SD_JSON_STRICT }, { "blobManifest", SD_JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 }, @@ -1623,6 +1666,10 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load { "selfModifiableFields", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_fields), SD_JSON_STRICT }, { "selfModifiableBlobs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_blobs), SD_JSON_STRICT }, { "selfModifiablePrivileged", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, self_modifiable_privileged), SD_JSON_STRICT }, + { "tmpLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, tmp_limit), 0, }, + { "tmpLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, tmp_limit), 0, }, + { "devShmLimit", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit, offsetof(UserRecord, dev_shm_limit), 0, }, + { "devShmLimitScale", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_tmpfs_limit_scale, offsetof(UserRecord, dev_shm_limit), 0, }, { "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 }, { "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 }, @@ -2136,6 +2183,32 @@ int user_record_languages(UserRecord *h, char ***ret) { return 0; } +uint32_t user_record_tmp_limit_scale(UserRecord *h) { + assert(h); + + if (h->tmp_limit.is_set) + return h->tmp_limit.limit_scale; + + /* By default grant regular users only 80% quota */ + if (user_record_disposition(h) == USER_REGULAR) + return UINT32_SCALE_FROM_PERCENT(80); + + return UINT32_MAX; +} + +uint32_t user_record_dev_shm_limit_scale(UserRecord *h) { + assert(h); + + if (h->dev_shm_limit.is_set) + return h->dev_shm_limit.limit_scale; + + /* By default grant regular users only 80% quota */ + if (user_record_disposition(h) == USER_REGULAR) + return UINT32_SCALE_FROM_PERCENT(80); + + return UINT32_MAX; +} + const char** user_record_self_modifiable_fields(UserRecord *h) { /* As a rule of thumb: a setting is safe if it cannot be used by a * user to give themselves some unfair advantage over other users on @@ -2625,6 +2698,28 @@ int user_record_is_nobody(const UserRecord *u) { return u->uid == UID_NOBODY || STRPTR_IN_SET(u->user_name, NOBODY_USER_NAME, "nobody"); } +bool user_record_matches_user_name(const UserRecord *u, const char *user_name) { + assert(u); + assert(user_name); + + if (streq_ptr(u->user_name, user_name)) + return true; + + if (streq_ptr(u->user_name_and_realm_auto, user_name)) + return true; + + if (strv_contains(u->aliases, user_name)) + return true; + + const char *realm = strrchr(user_name, '@'); + if (realm && streq_ptr(realm+1, u->realm)) + STRV_FOREACH(a, u->aliases) + if (startswith(user_name, *a) == realm) + return true; + + return false; +} + int suitable_blob_filename(const char *name) { /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */ return filename_is_valid(name) && @@ -2670,7 +2765,9 @@ bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) int user_record_match(UserRecord *u, const UserDBMatch *match) { assert(u); - assert(match); + + if (!match) + return true; if (u->uid < match->uid_min || u->uid > match->uid_max) return false; @@ -2691,7 +2788,8 @@ int user_record_match(UserRecord *u, const UserDBMatch *match) { u->cifs_user_name, }; - if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names)) + if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) && + !user_name_fuzzy_match((const char**) u->aliases, strv_length(u->aliases), match->fuzzy_names)) return false; } diff --git a/src/shared/user-record.h b/src/shared/user-record.h index d3decdb5c1..fc8510c074 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -230,6 +230,19 @@ typedef enum AutoResizeMode { #define REBALANCE_WEIGHT_MAX UINT64_C(10000) #define REBALANCE_WEIGHT_UNSET UINT64_MAX +typedef struct TmpfsLimit { + /* Absolute and relative tmpfs limits */ + uint64_t limit; + uint32_t limit_scale; + bool is_set; +} TmpfsLimit; + +#define TMPFS_LIMIT_NULL \ + (TmpfsLimit) { \ + .limit = UINT64_MAX, \ + .limit_scale = UINT32_MAX, \ + } \ + typedef struct UserRecord { /* The following three fields are not part of the JSON record */ unsigned n_ref; @@ -239,6 +252,7 @@ typedef struct UserRecord { char *user_name; char *realm; char *user_name_and_realm_auto; /* the user_name field concatenated with '@' and the realm, if the latter is defined */ + char **aliases; char *real_name; char *email_address; char *password_hint; @@ -388,6 +402,8 @@ typedef struct UserRecord { char **self_modifiable_blobs; char **self_modifiable_privileged; + TmpfsLimit tmp_limit, dev_shm_limit; + sd_json_variant *json; } UserRecord; @@ -435,6 +451,8 @@ uint64_t user_record_rebalance_weight(UserRecord *h); uint64_t user_record_capability_bounding_set(UserRecord *h); uint64_t user_record_capability_ambient_set(UserRecord *h); int user_record_languages(UserRecord *h, char ***ret); +uint32_t user_record_tmp_limit_scale(UserRecord *h); +uint32_t user_record_dev_shm_limit_scale(UserRecord *h); const char **user_record_self_modifiable_fields(UserRecord *h); const char **user_record_self_modifiable_blobs(UserRecord *h); @@ -490,6 +508,8 @@ typedef struct UserDBMatch { bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches); int user_record_match(UserRecord *u, const UserDBMatch *match); +bool user_record_matches_user_name(const UserRecord *u, const char *username); + const char* user_storage_to_string(UserStorage t) _const_; UserStorage user_storage_from_string(const char *s) _pure_; diff --git a/src/shared/userdb-dropin.c b/src/shared/userdb-dropin.c index 9f027d7783..81fd5f3ebc 100644 --- a/src/shared/userdb-dropin.c +++ b/src/shared/userdb-dropin.c @@ -4,6 +4,7 @@ #include "fd-util.h" #include "fileio.h" #include "format-util.h" +#include "group-record.h" #include "path-util.h" #include "stdio-util.h" #include "user-util.h" @@ -87,7 +88,7 @@ static int load_user( if (r < 0) return r; - if (name && !streq_ptr(name, u->user_name)) + if (name && !user_record_matches_user_name(u, name)) return -EINVAL; if (uid_is_valid(uid) && uid != u->uid) @@ -231,7 +232,7 @@ static int load_group( if (r < 0) return r; - if (name && !streq_ptr(name, g->group_name)) + if (name && !group_record_matches_group_name(g, name)) return -EINVAL; if (gid_is_valid(gid) && gid != g->gid) diff --git a/src/shared/userdb.c b/src/shared/userdb.c index be7c77d950..32f851b0f3 100644 --- a/src/shared/userdb.c +++ b/src/shared/userdb.c @@ -384,10 +384,21 @@ static int userdb_connect( if (r < 0) return log_debug_errno(r, "Failed to bind reply callback: %m"); + _cleanup_free_ char *service = NULL; + r = path_extract_filename(path, &service); + if (r < 0) + return log_debug_errno(r, "Failed to extract service name from socket path: %m"); + assert(r != O_DIRECTORY); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query); + r = sd_json_variant_set_field_string(&patched_query, "service", service); + if (r < 0) + return log_debug_errno(r, "Unable to set service JSON field: %m"); + if (more) - r = sd_varlink_observe(vl, method, query); + r = sd_varlink_observe(vl, method, patched_query); else - r = sd_varlink_invoke(vl, method, query); + r = sd_varlink_invoke(vl, method, patched_query); if (r < 0) return log_debug_errno(r, "Failed to invoke varlink method: %m"); @@ -438,13 +449,7 @@ static int userdb_start_query( if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN)) == 0 && !strv_contains(except, "io.systemd.Multiplexer") && (!only || strv_contains(only, "io.systemd.Multiplexer"))) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query); - - r = sd_json_variant_set_field_string(&patched_query, "service", "io.systemd.Multiplexer"); - if (r < 0) - return log_debug_errno(r, "Unable to set service JSON field: %m"); - - r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query); + r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, query); if (r >= 0) { iterator->nss_covered = true; /* The multiplexer does NSS */ iterator->dropin_covered = true; /* It also handles drop-in stuff */ @@ -461,7 +466,6 @@ static int userdb_start_query( } FOREACH_DIRENT(de, d, return -errno) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = NULL; _cleanup_free_ char *p = NULL; bool is_nss, is_dropin; @@ -495,12 +499,7 @@ static int userdb_start_query( if (!p) return -ENOMEM; - patched_query = sd_json_variant_ref(query); - r = sd_json_variant_set_field_string(&patched_query, "service", de->d_name); - if (r < 0) - return log_debug_errno(r, "Unable to set service JSON field: %m"); - - r = userdb_connect(iterator, p, method, more, patched_query); + r = userdb_connect(iterator, p, method, more, query); if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service * and could connect to it */ iterator->nss_covered = true; @@ -853,6 +852,7 @@ int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) { /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains * the more traditional sources, which are probably good to show first. */ + errno = 0; pw = getpwent(); if (pw) { _cleanup_free_ char *buffer = NULL; diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 1d6cab8da7..b80cbc65d0 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -30,18 +30,21 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( static SD_VARLINK_DEFINE_METHOD( Register, - SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), - SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(class, SD_VARLINK_STRING, 0), - SD_VARLINK_DEFINE_INPUT_BY_TYPE(leader, ProcessId, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(ifIndices, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_DEFINE_INPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(class, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The leader PID as simple positive integer."), + SD_VARLINK_DEFINE_INPUT(leader, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The leader PID as ProcessId structure. If both the leader and leaderProcessId parameters are specified they must reference the same process. Typically one would only specify one or the other however. It's generally recommended to specify leaderProcessId as it references a process in a robust way without risk of identifier recycling."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(leaderProcessId, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(ifIndices, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(sshAddress, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(sshPrivateKeyPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Controls whether to allocate a scope unit for the machine to register. If false, the client already took care of that and registered a service/scope specific to the machine."), - SD_VARLINK_DEFINE_INPUT(allocateUnit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(allocateUnit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), VARLINK_DEFINE_POLKIT_INPUT); static SD_VARLINK_DEFINE_METHOD( diff --git a/src/shared/varlink-io.systemd.MountFileSystem.c b/src/shared/varlink-io.systemd.MountFileSystem.c index 43b812b0d2..423f275059 100644 --- a/src/shared/varlink-io.systemd.MountFileSystem.c +++ b/src/shared/varlink-io.systemd.MountFileSystem.c @@ -49,6 +49,31 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_OUTPUT(imageName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_OUTPUT(imageUuid, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_ENUM_TYPE( + MountMapMode, + SD_VARLINK_FIELD_COMMENT("Map the caller's UID to root in the user namespace, do not map anything else."), + SD_VARLINK_DEFINE_ENUM_VALUE(root), + SD_VARLINK_FIELD_COMMENT("Map the foreign UID range to the base UID range in the user namespace (i.e. UID zero and above), covering 64K users."), + SD_VARLINK_DEFINE_ENUM_VALUE(foreign), + SD_VARLINK_FIELD_COMMENT("Apply an identity (1:1) mapping, but limit it to 64K users."), + SD_VARLINK_DEFINE_ENUM_VALUE(identity), + SD_VARLINK_FIELD_COMMENT("Determine automatically based on provided directory and caller."), + SD_VARLINK_DEFINE_ENUM_VALUE(auto)); + +static SD_VARLINK_DEFINE_METHOD( + MountDirectory, + SD_VARLINK_FIELD_COMMENT("Directory file descriptor of the directory to assign to the user namespace. Must be a regular, i.e. non-O_PATH file descriptor."), + SD_VARLINK_DEFINE_INPUT(directoryFileDescriptor, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("File descriptor to the user namespace to assign this directory to. If not specified uses the host user namespace."), + SD_VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to mark the resulting mount file descriptor as read-only. If not specified defaults to false."), + SD_VARLINK_DEFINE_INPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Which kinda of UID/GID mapping to apply to the resulting mount file descriptor."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(mode, MountMapMode, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("The freshly allocated mount file descriptor for the mount."), + SD_VARLINK_DEFINE_OUTPUT(mountFileDescriptor, SD_VARLINK_INT, 0)); + static SD_VARLINK_DEFINE_ERROR(IncompatibleImage); static SD_VARLINK_DEFINE_ERROR(MultipleRootPartitionsFound); static SD_VARLINK_DEFINE_ERROR(RootPartitionNotFound); @@ -59,9 +84,17 @@ static SD_VARLINK_DEFINE_ERROR(VerityFailure); SD_VARLINK_DEFINE_INTERFACE( io_systemd_MountFileSystem, "io.systemd.MountFileSystem", + SD_VARLINK_INTERFACE_COMMENT("APIs for unprivileged mounting."), + SD_VARLINK_SYMBOL_COMMENT("Encodes the designated purpose of a partition."), &vl_type_PartitionDesignator, + SD_VARLINK_SYMBOL_COMMENT("Information about a specific partition."), &vl_type_PartitionInfo, + SD_VARLINK_SYMBOL_COMMENT("Selects the type of UID/GID mapping to apply."), + &vl_type_MountMapMode, + SD_VARLINK_SYMBOL_COMMENT("Takes a disk image file descriptor as input, returns a set of mount file descriptors for it."), &vl_method_MountImage, + SD_VARLINK_SYMBOL_COMMENT("Takes a directory file descriptor as input, returns a mount file descriptor."), + &vl_method_MountDirectory, &vl_error_IncompatibleImage, &vl_error_MultipleRootPartitionsFound, &vl_error_RootPartitionNotFound, diff --git a/src/shared/varlink-io.systemd.Udev.c b/src/shared/varlink-io.systemd.Udev.c index fb34036b42..09c1ca70ce 100644 --- a/src/shared/varlink-io.systemd.Udev.c +++ b/src/shared/varlink-io.systemd.Udev.c @@ -3,6 +3,11 @@ #include "varlink-io.systemd.Udev.h" static SD_VARLINK_DEFINE_METHOD( + SetTrace, + SD_VARLINK_FIELD_COMMENT("Enable/disable."), + SD_VARLINK_DEFINE_INPUT(enable, SD_VARLINK_BOOL, 0)); + +static SD_VARLINK_DEFINE_METHOD( SetChildrenMax, SD_VARLINK_FIELD_COMMENT("The maximum number of child processes. When 0 is specified, the maximum is determined based on the system resources."), SD_VARLINK_DEFINE_INPUT(number, SD_VARLINK_INT, 0)); @@ -22,6 +27,8 @@ SD_VARLINK_DEFINE_INTERFACE( io_systemd_Udev, "io.systemd.Udev", SD_VARLINK_INTERFACE_COMMENT("An interface for controlling systemd-udevd."), + SD_VARLINK_SYMBOL_COMMENT("Enable/disable trace logging."), + &vl_method_SetTrace, SD_VARLINK_SYMBOL_COMMENT("Sets the maximum number of child processes."), &vl_method_SetChildrenMax, SD_VARLINK_SYMBOL_COMMENT("Sets the global udev properties."), diff --git a/src/shared/varlink-io.systemd.UserDatabase.c b/src/shared/varlink-io.systemd.UserDatabase.c index 3dc72c0257..905e2ba681 100644 --- a/src/shared/varlink-io.systemd.UserDatabase.c +++ b/src/shared/varlink-io.systemd.UserDatabase.c @@ -5,45 +5,69 @@ static SD_VARLINK_DEFINE_METHOD_FULL( GetUserRecord, SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("The numeric 32bit UNIX UID of the record, if look-up by UID is desired."), SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The UNIX user name of the record, if look-up by name is desired."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The userdb provider service to search on. Must be set to the base name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."), SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The retrieved user record."), SD_VARLINK_DEFINE_OUTPUT(record, SD_VARLINK_OBJECT, 0), + SD_VARLINK_FIELD_COMMENT("If set to true, indicates that the user record is not complete, i.e. that the 'privileged' section has been stripped because the client lacks the privileges to access it."), SD_VARLINK_DEFINE_OUTPUT(incomplete, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( GetGroupRecord, SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("The numeric 32bit UNIX GID of the record, if look-up by GID is desired."), SD_VARLINK_DEFINE_INPUT(gid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The UNIX group name of the record, if look-up by name is desired."), SD_VARLINK_DEFINE_INPUT(groupName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The userdb provider service to search on. Must be set to the base name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."), SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The retrieved group record."), SD_VARLINK_DEFINE_OUTPUT(record, SD_VARLINK_OBJECT, 0), + SD_VARLINK_FIELD_COMMENT("If set to true, indicates that the group record is not complete, i.e. that the 'privileged' section has been stripped because the client lacks the privileges to access it."), SD_VARLINK_DEFINE_OUTPUT(incomplete, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( GetMemberships, SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("The UNIX user name of the user to search for memberships for."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The UNIX group name of the group to search for memberships for."), SD_VARLINK_DEFINE_INPUT(groupName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The userdb provider to search on. Must be set to the base name of the userdb entrypoint socket. This is necessary in order to support services that implement multiple userdb services on the same socket."), SD_VARLINK_DEFINE_INPUT(service, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The UNIX user name of a discovered membership relationship."), SD_VARLINK_DEFINE_OUTPUT(userName, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The UNIX group name of a discovered membership relationship."), SD_VARLINK_DEFINE_OUTPUT(groupName, SD_VARLINK_STRING, 0)); static SD_VARLINK_DEFINE_ERROR(NoRecordFound); static SD_VARLINK_DEFINE_ERROR(BadService); static SD_VARLINK_DEFINE_ERROR(ServiceNotAvailable); -static SD_VARLINK_DEFINE_ERROR(ConflictingRecordNotFound); +static SD_VARLINK_DEFINE_ERROR(ConflictingRecordFound); static SD_VARLINK_DEFINE_ERROR(EnumerationNotSupported); /* As per https://systemd.io/USER_GROUP_API/ */ SD_VARLINK_DEFINE_INTERFACE( io_systemd_UserDatabase, "io.systemd.UserDatabase", + SD_VARLINK_INTERFACE_COMMENT("APIs for querying user and group records."), + SD_VARLINK_SYMBOL_COMMENT("Retrieve one or more user records. Look-up is either keyed by UID or user name, or if neither is specified all known records are enumerated."), &vl_method_GetUserRecord, + SD_VARLINK_SYMBOL_COMMENT("Retrieve one or more group records. Look-up is either keyed by GID or group name, or if neither is specified all known records are enumerated."), &vl_method_GetGroupRecord, + SD_VARLINK_SYMBOL_COMMENT("Retrieve membership relationships between users and groups."), &vl_method_GetMemberships, + SD_VARLINK_SYMBOL_COMMENT("Error indicating that no matching user or group record was found."), &vl_error_NoRecordFound, + SD_VARLINK_SYMBOL_COMMENT("Error indicating that the contacted service does not implement the specified service name."), &vl_error_BadService, + SD_VARLINK_SYMBOL_COMMENT("Error indicating that the backing service currently is not operational and no answer can be provided."), &vl_error_ServiceNotAvailable, - &vl_error_ConflictingRecordNotFound, + SD_VARLINK_SYMBOL_COMMENT("Error indicating that there's a user record matching either UID/GID or the user/group name, but not both at the same time."), + &vl_error_ConflictingRecordFound, + SD_VARLINK_SYMBOL_COMMENT("Error indicating that retrieval of user/group records on this service is only supported if either user/group name or UID/GID are specified, but not if nothing is specified."), &vl_error_EnumerationNotSupported); diff --git a/src/shared/varlink-io.systemd.service.c b/src/shared/varlink-io.systemd.service.c index f34d5cfead..dd1329f878 100644 --- a/src/shared/varlink-io.systemd.service.c +++ b/src/shared/varlink-io.systemd.service.c @@ -2,7 +2,9 @@ #include <unistd.h> +#include "env-util.h" #include "json-util.h" +#include "strv.h" #include "varlink-io.systemd.service.h" static SD_VARLINK_DEFINE_METHOD(Ping); @@ -11,8 +13,16 @@ static SD_VARLINK_DEFINE_METHOD(Reload); static SD_VARLINK_DEFINE_METHOD( SetLogLevel, - SD_VARLINK_FIELD_COMMENT("The maximum log level."), - SD_VARLINK_DEFINE_INPUT(level, SD_VARLINK_INT, 0)); + SD_VARLINK_FIELD_COMMENT("The maximum log level, using BSD syslog log level integers."), + SD_VARLINK_DEFINE_INPUT(level, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + GetEnvironment, + SD_VARLINK_FIELD_COMMENT("Returns the current environment block, i.e. the contents of environ[]."), + SD_VARLINK_DEFINE_OUTPUT(environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY)); + +static SD_VARLINK_DEFINE_ERROR( + InconsistentEnvironment); SD_VARLINK_DEFINE_INTERFACE( io_systemd_service, @@ -20,16 +30,25 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_INTERFACE_COMMENT("An interface to control basic properties of systemd services."), SD_VARLINK_SYMBOL_COMMENT("Checks if the service is running."), &vl_method_Ping, - SD_VARLINK_SYMBOL_COMMENT("Reloads configurations."), + SD_VARLINK_SYMBOL_COMMENT("Reloads configuration files."), &vl_method_Reload, SD_VARLINK_SYMBOL_COMMENT("Sets the maximum log level."), - &vl_method_SetLogLevel); + &vl_method_SetLogLevel, + SD_VARLINK_SYMBOL_COMMENT("Get current environment block."), + &vl_method_GetEnvironment, + SD_VARLINK_SYMBOL_COMMENT("Returned if the environment block is currently not in a valid state."), + &vl_error_InconsistentEnvironment); + +/* Generic implementations for some of the method calls above */ int varlink_method_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; log_debug("Received io.systemd.service.Ping"); @@ -48,11 +67,6 @@ int varlink_method_set_log_level(sd_varlink *link, sd_json_variant *parameters, assert(link); assert(parameters); - /* NOTE: The method does have 1 parameter, but we must compare to 2 here, because - * sd_json_variant_elements() breaks abstraction and exposes internal structure of JsonObject. */ - if (sd_json_variant_elements(parameters) != 2) - return sd_varlink_error_invalid_parameter(link, parameters); - r = sd_varlink_dispatch(link, parameters, dispatch_table, &level); if (r != 0) return r; @@ -70,3 +84,48 @@ int varlink_method_set_log_level(sd_varlink *link, sd_json_variant *parameters, return sd_varlink_reply(link, NULL); } + +int varlink_method_get_environment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + uid_t uid; + int r; + + assert(link); + assert(parameters); + + /* This is a lot like /proc/$PID/environ, but can properly report the actual environment block as + * seen from the process itself, which might be quite different from the contents of the memory that + * was originally passed in. This is particularly relevant for cases where the environ[] block has + * been enlarged and similar. */ + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = sd_varlink_get_peer_uid(link, &uid); + if (r < 0) + return r; + + /* Don't hand out environment block to arbitrary clients, in some cases people might make the mistake + * of passing secrets via env vars */ + if (uid != 0 && uid != getuid()) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); + + log_debug("Received io.systemd.service.GetEnvironment()"); + + _cleanup_strv_free_ char **l = NULL; + STRV_FOREACH(e, environ) { + if (!env_assignment_is_valid(*e)) + goto invalid; + if (!utf8_is_valid(*e)) + goto invalid; + + r = strv_env_replace_strdup(&l, *e); + if (r < 0) + return r; + } + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("environment", l)); + +invalid: + return sd_varlink_error(link, "io.systemd.service.InconsistentEnvironment", parameters); +} diff --git a/src/shared/varlink-io.systemd.service.h b/src/shared/varlink-io.systemd.service.h index 3f164783f5..a68d58d6d4 100644 --- a/src/shared/varlink-io.systemd.service.h +++ b/src/shared/varlink-io.systemd.service.h @@ -8,3 +8,4 @@ extern const sd_varlink_interface vl_interface_io_systemd_service; int varlink_method_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int varlink_method_set_log_level(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int varlink_method_get_environment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/shutdown/test-umount.c b/src/shutdown/test-umount.c index 93da2e0fc3..fe30d9a2fe 100644 --- a/src/shutdown/test-umount.c +++ b/src/shutdown/test-umount.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "detach-swap.h" #include "errno-util.h" +#include "fd-util.h" #include "log.h" #include "path-util.h" #include "string-util.h" @@ -11,17 +12,18 @@ static void test_mount_points_list_one(const char *fname) { _cleanup_(mount_points_list_free) LIST_HEAD(MountPoint, mp_list_head); - _cleanup_free_ char *testdata_fname = NULL; + _cleanup_fclose_ FILE *f = NULL; log_info("/* %s(\"%s\") */", __func__, fname ?: "/proc/self/mountinfo"); if (fname) { + _cleanup_free_ char *testdata_fname = NULL; assert_se(get_testdata_dir(fname, &testdata_fname) >= 0); - fname = testdata_fname; + ASSERT_NOT_NULL(f = fopen(testdata_fname, "re")); } LIST_HEAD_INIT(mp_list_head); - assert_se(mount_points_list_get(fname, &mp_list_head) >= 0); + assert_se(mount_points_list_get(f, &mp_list_head) >= 0); LIST_FOREACH(mount_point, m, mp_list_head) log_debug("path=%s o=%s f=0x%lx try-ro=%s", diff --git a/src/shutdown/umount.c b/src/shutdown/umount.c index 4bc01c75e0..a788f8cf83 100644 --- a/src/shutdown/umount.c +++ b/src/shutdown/umount.c @@ -47,16 +47,16 @@ void mount_points_list_free(MountPoint **head) { mount_point_free(head, *head); } -int mount_points_list_get(const char *mountinfo, MountPoint **head) { +int mount_points_list_get(FILE *f, MountPoint **head) { _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL; int r; assert(head); - r = libmount_parse(mountinfo, NULL, &table, &iter); + r = libmount_parse_mountinfo(f, &table, &iter); if (r < 0) - return log_error_errno(r, "Failed to parse %s: %m", mountinfo ?: "/proc/self/mountinfo"); + return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); for (;;) { _cleanup_free_ char *options = NULL, *remount_options = NULL; @@ -70,7 +70,7 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) { if (r == 1) /* EOF */ break; if (r < 0) - return log_error_errno(r, "Failed to get next entry from %s: %m", mountinfo ?: "/proc/self/mountinfo"); + return log_error_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m"); path = mnt_fs_get_target(fs); if (!path) @@ -141,7 +141,7 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) { r = libmount_is_leaf(table, fs); if (r < 0) - return log_error_errno(r, "Failed to get children mounts for %s from %s: %m", path, mountinfo ?: "/proc/self/mountinfo"); + return log_error_errno(r, "Failed to get children mounts for %s from /proc/self/mountinfo: %m", path); bool leaf = r; *m = (MountPoint) { diff --git a/src/shutdown/umount.h b/src/shutdown/umount.h index f8f9ae8038..22498e1e77 100644 --- a/src/shutdown/umount.h +++ b/src/shutdown/umount.h @@ -6,6 +6,7 @@ ***/ #include <stdbool.h> +#include <stdio.h> #include "list.h" @@ -22,5 +23,5 @@ typedef struct MountPoint { LIST_FIELDS(struct MountPoint, mount_point); } MountPoint; -int mount_points_list_get(const char *mountinfo, MountPoint **head); +int mount_points_list_get(FILE *f, MountPoint **head); void mount_points_list_free(MountPoint **head); diff --git a/src/systemctl/systemctl-is-system-running.c b/src/systemctl/systemctl-is-system-running.c index 59be6a7a7e..8d5303c2d8 100644 --- a/src/systemctl/systemctl-is-system-running.c +++ b/src/systemctl/systemctl-is-system-running.c @@ -66,6 +66,10 @@ int verb_is_system_running(int argc, char *argv[], void *userdata) { } if (arg_wait && STR_IN_SET(state, "initializing", "starting")) { + /* The signal handler will allocate memory and assign to 'state', hence need to free previous + * one before entering the event loop. */ + state = mfree(state); + r = sd_event_loop(event); if (r < 0) { log_warning_errno(r, "Failed to get property from event loop: %m"); @@ -73,6 +77,8 @@ int verb_is_system_running(int argc, char *argv[], void *userdata) { puts("unknown"); return EXIT_FAILURE; } + + assert(state); } if (!arg_quiet) diff --git a/src/systemd/sd-json.h b/src/systemd/sd-json.h index 3930d82b0d..a3a1dff94d 100644 --- a/src/systemd/sd-json.h +++ b/src/systemd/sd-json.h @@ -168,6 +168,8 @@ int sd_json_variant_set_field_unsigned(sd_json_variant **v, const char *field, u int sd_json_variant_set_field_boolean(sd_json_variant **v, const char *field, int b); int sd_json_variant_set_field_strv(sd_json_variant **v, const char *field, char **l); +int sd_json_variant_unset_field(sd_json_variant **v, const char *field); + sd_json_variant* sd_json_variant_find(sd_json_variant *haystack, sd_json_variant *needle); int sd_json_variant_append_array(sd_json_variant **v, sd_json_variant *element); diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index aeb08ef97f..26633276da 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -206,7 +206,7 @@ int sd_rtnl_message_new_routing_policy_rule(sd_netlink *rtnl, sd_netlink_message int sd_rtnl_message_routing_policy_rule_get_family(sd_netlink_message *m, int *ret); /* family */ int sd_rtnl_message_routing_policy_rule_set_dst_prefixlen(sd_netlink_message *m, uint8_t prefixlen); /* dst_len */ int sd_rtnl_message_routing_policy_rule_get_dst_prefixlen(sd_netlink_message *m, uint8_t *ret); -int sd_rtnl_message_routing_policy_rule_set_src_prefixlen(sd_netlink_message *m, uint8_t prefixlen); /* src_len*/ +int sd_rtnl_message_routing_policy_rule_set_src_prefixlen(sd_netlink_message *m, uint8_t prefixlen); /* src_len */ int sd_rtnl_message_routing_policy_rule_get_src_prefixlen(sd_netlink_message *m, uint8_t *ret); int sd_rtnl_message_routing_policy_rule_set_tos(sd_netlink_message *m, uint8_t tos); /* tos */ int sd_rtnl_message_routing_policy_rule_get_tos(sd_netlink_message *m, uint8_t *ret); diff --git a/src/systemd/sd-varlink-idl.h b/src/systemd/sd-varlink-idl.h index fe65c00eb6..63d79d33f7 100644 --- a/src/systemd/sd-varlink-idl.h +++ b/src/systemd/sd-varlink-idl.h @@ -70,7 +70,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_field_type_t) { SD_VARLINK_STRING, SD_VARLINK_OBJECT, SD_VARLINK_ENUM_VALUE, - _SD_VARLINK_FIELD_COMMENT, /* Not really a field, just a comment about a field*/ + _SD_VARLINK_FIELD_COMMENT, /* Not really a field, just a comment about a field */ _SD_VARLINK_FIELD_TYPE_MAX, _SD_VARLINK_FIELD_TYPE_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(SD_VARLINK_FIELD) diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 03509163d3..528c20e829 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -215,6 +215,7 @@ int sd_varlink_set_relative_timeout(sd_varlink *v, uint64_t usec); sd_varlink_server* sd_varlink_get_server(sd_varlink *v); int sd_varlink_set_description(sd_varlink *v, const char *d); +const char* sd_varlink_get_description(sd_varlink *v); /* Automatically mark the parameters part of incoming messages as security sensitive */ int sd_varlink_set_input_sensitive(sd_varlink *v); diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 6cb7c6fbf1..53ccc59a7f 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -831,7 +831,7 @@ static int update_render_progress(sd_event_source *source, void *userdata) { if (!terminal_is_dumb()) { for (size_t i = 0; i <= n; i++) - fputs("\n", stderr); /* Possibly scroll the terminal to make room (including total)*/ + fputs("\n", stderr); /* Possibly scroll the terminal to make room (including total) */ fprintf(stderr, "\e[%zuF", n+1); /* Go back */ diff --git a/src/test/test-cap-list.c b/src/test/test-cap-list.c index 49be4a2d27..0fb2f31ed2 100644 --- a/src/test/test-cap-list.c +++ b/src/test/test-cap-list.c @@ -12,6 +12,11 @@ #include "strv.h" #include "tests.h" +static inline void cap_free_charpp(char **p) { + if (*p) + cap_free(*p); +} + /* verify the capability parser */ TEST(cap_list) { assert_se(!capability_to_name(-1)); @@ -46,7 +51,7 @@ TEST(cap_list) { assert_se(capability_from_name("-1") == -EINVAL); for (int i = 0; i < capability_list_length(); i++) { - _cleanup_cap_free_charp_ char *a = NULL; + _cleanup_(cap_free_charpp) char *a = NULL; const char *b; unsigned u; diff --git a/src/test/test-chase.c b/src/test/test-chase.c index c7ca3fd051..510264c547 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -10,6 +10,7 @@ #include "id128-util.h" #include "mkdir.h" #include "path-util.h" +#include "random-util.h" #include "rm-rf.h" #include "string-util.h" #include "tests.h" @@ -754,6 +755,34 @@ TEST(trailing_dot_dot) { assert_se(path_equal(fdpath, expected2)); } +TEST(use_chase_as_mkdir_p) { + _cleanup_free_ char *p = NULL; + ASSERT_OK_ERRNO(asprintf(&p, "/tmp/chasemkdir%" PRIu64 "/a/b/c", random_u64())); + + _cleanup_close_ int fd = -EBADF; + ASSERT_OK(chase(p, NULL, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755, NULL, &fd)); + + ASSERT_OK_EQ(inode_same_at(AT_FDCWD, p, fd, NULL, AT_EMPTY_PATH), 1); + + _cleanup_close_ int fd2 = -EBADF; + ASSERT_OK(chase(p, p, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755, NULL, &fd2)); + + _cleanup_free_ char *pp = ASSERT_PTR(path_join(p, p)); + + ASSERT_OK_EQ(inode_same_at(AT_FDCWD, pp, fd2, NULL, AT_EMPTY_PATH), 1); + + _cleanup_free_ char *f = NULL; + ASSERT_OK(path_extract_directory(p, &f)); + + _cleanup_free_ char *ff = NULL; + ASSERT_OK(path_extract_directory(f, &ff)); + + _cleanup_free_ char *fff = NULL; + ASSERT_OK(path_extract_directory(ff, &fff)); + + ASSERT_OK(rm_rf(fff, REMOVE_PHYSICAL)); +} + static int intro(void) { arg_test_dir = saved_argv[1]; return EXIT_SUCCESS; diff --git a/src/test/test-devnum-util.c b/src/test/test-devnum-util.c index ebef794001..782f15d86f 100644 --- a/src/test/test-devnum-util.c +++ b/src/test/test-devnum-util.c @@ -121,4 +121,21 @@ TEST(devnum_format_str) { test_devnum_format_str_one(makedev(4095, 1048575), "4095:1048575"); } +TEST(devnum_to_ptr) { + dev_t m = makedev(0, 0); + ASSERT_EQ(major(m), 0U); + ASSERT_EQ(minor(m), 0U); + ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m))); + + m = makedev(DEVNUM_MAJOR_MAX, DEVNUM_MINOR_MAX); + ASSERT_EQ(major(m), DEVNUM_MAJOR_MAX); + ASSERT_EQ(minor(m), DEVNUM_MINOR_MAX); + ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m))); + + m = makedev(5, 8); + ASSERT_EQ(major(m), 5U); + ASSERT_EQ(minor(m), 8U); + ASSERT_EQ(m, PTR_TO_DEVNUM(DEVNUM_TO_PTR(m))); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-escape.c b/src/test/test-escape.c index 89a25febb4..7021ff54d2 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -15,7 +15,7 @@ TEST(cescape) { TEST(xescape) { _cleanup_free_ char *t = NULL; - assert_se(t = xescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "")); + assert_se(t = xescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", /* bad= */ NULL)); ASSERT_STREQ(t, "abc\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb"); } diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c index 1bc4088fb4..4c18a45231 100644 --- a/src/test/test-extract-word.c +++ b/src/test/test-extract-word.c @@ -547,6 +547,49 @@ TEST(extract_first_word) { ASSERT_STREQ(t, "가너도루"); free(t); assert_se(isempty(p)); + + /* For issue #16735. */ + p = "test1@foo\\x2dbar\\x2dbaz.service test2@aaa\\x2dbbb\\x2dccc.service test3@escaped-path-like-data.service test4@/pure/path/like/data.service"; + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE)); + ASSERT_STREQ(t, "test1@foox2dbarx2dbaz.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE)); + ASSERT_STREQ(t, "test2@aaax2dbbbx2dccc.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE)); + ASSERT_STREQ(t, "test3@escaped-path-like-data.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE)); + ASSERT_STREQ(t, "test4@/pure/path/like/data.service"); + free(t); + + p = "test1@foo\\x2dbar\\x2dbaz.service test2@aaa\\x2dbbb\\x2dccc.service test3@escaped-path-like-data.service test4@/pure/path/like/data.service"; + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_RETAIN_ESCAPE)); + ASSERT_STREQ(t, "test1@foo\\x2dbar\\x2dbaz.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_RETAIN_ESCAPE)); + ASSERT_STREQ(t, "test2@aaa\\x2dbbb\\x2dccc.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_RETAIN_ESCAPE)); + ASSERT_STREQ(t, "test3@escaped-path-like-data.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_RETAIN_ESCAPE)); + ASSERT_STREQ(t, "test4@/pure/path/like/data.service"); + free(t); + + p = "test1@foo\\x2dbar\\x2dbaz.service test2@aaa\\x2dbbb\\x2dccc.service test3@escaped-path-like-data.service test4@/pure/path/like/data.service"; + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_CUNESCAPE)); + ASSERT_STREQ(t, "test1@foo-bar-baz.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_CUNESCAPE)); + ASSERT_STREQ(t, "test2@aaa-bbb-ccc.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_CUNESCAPE)); + ASSERT_STREQ(t, "test3@escaped-path-like-data.service"); + free(t); + ASSERT_OK_POSITIVE(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE | EXTRACT_CUNESCAPE)); + ASSERT_STREQ(t, "test4@/pure/path/like/data.service"); + free(t); } TEST(extract_first_word_and_warn) { diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c index e1485a84d4..6b6058b08a 100644 --- a/src/test/test-hashmap-plain.c +++ b/src/test/test-hashmap-plain.c @@ -74,36 +74,21 @@ TEST(hashmap_ensure_replace) { } TEST(hashmap_copy) { - _cleanup_hashmap_free_ Hashmap *m = NULL; - _cleanup_hashmap_free_free_ Hashmap *copy = NULL; - char *val1, *val2, *val3, *val4, *r; - - val1 = strdup("val1"); - assert_se(val1); - val2 = strdup("val2"); - assert_se(val2); - val3 = strdup("val3"); - assert_se(val3); - val4 = strdup("val4"); - assert_se(val4); + _cleanup_hashmap_free_ Hashmap *m = NULL, *copy = NULL; - m = hashmap_new(&string_hash_ops); + ASSERT_NOT_NULL(m = hashmap_new(&string_hash_ops)); - hashmap_put(m, "key 1", val1); - hashmap_put(m, "key 2", val2); - hashmap_put(m, "key 3", val3); - hashmap_put(m, "key 4", val4); + ASSERT_OK_POSITIVE(hashmap_put(m, "key 1", (void*) "val1")); + ASSERT_OK_POSITIVE(hashmap_put(m, "key 2", (void*) "val2")); + ASSERT_OK_POSITIVE(hashmap_put(m, "key 3", (void*) "val3")); + ASSERT_OK_POSITIVE(hashmap_put(m, "key 4", (void*) "val4")); - copy = hashmap_copy(m); + ASSERT_NOT_NULL(copy = hashmap_copy(m)); - r = hashmap_get(copy, "key 1"); - ASSERT_STREQ(r, "val1"); - r = hashmap_get(copy, "key 2"); - ASSERT_STREQ(r, "val2"); - r = hashmap_get(copy, "key 3"); - ASSERT_STREQ(r, "val3"); - r = hashmap_get(copy, "key 4"); - ASSERT_STREQ(r, "val4"); + ASSERT_STREQ(hashmap_get(copy, "key 1"), "val1"); + ASSERT_STREQ(hashmap_get(copy, "key 2"), "val2"); + ASSERT_STREQ(hashmap_get(copy, "key 3"), "val3"); + ASSERT_STREQ(hashmap_get(copy, "key 4"), "val4"); } TEST(hashmap_get_strv) { @@ -140,7 +125,7 @@ TEST(hashmap_get_strv) { } TEST(hashmap_move_one) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL, *n = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL, *n = NULL; char *val1, *val2, *val3, *val4, *r; val1 = strdup("val1"); @@ -152,8 +137,8 @@ TEST(hashmap_move_one) { val4 = strdup("val4"); assert_se(val4); - m = hashmap_new(&string_hash_ops); - n = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); + n = hashmap_new(&string_hash_ops_value_free); hashmap_put(m, "key 1", val1); hashmap_put(m, "key 2", val2); @@ -176,7 +161,7 @@ TEST(hashmap_move_one) { } TEST(hashmap_move) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL, *n = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL, *n = NULL; char *val1, *val2, *val3, *val4, *r; val1 = strdup("val1"); @@ -188,8 +173,8 @@ TEST(hashmap_move) { val4 = strdup("val4"); assert_se(val4); - m = hashmap_new(&string_hash_ops); - n = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); + n = hashmap_new(&string_hash_ops_value_free); hashmap_put(n, "key 1", strdup(val1)); hashmap_put(m, "key 1", val1); @@ -282,7 +267,7 @@ TEST(hashmap_remove1) { } TEST(hashmap_remove2) { - _cleanup_hashmap_free_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; char key1[] = "key 1"; char key2[] = "key 2"; char val1[] = "val 1"; @@ -292,7 +277,7 @@ TEST(hashmap_remove2) { r = hashmap_remove2(NULL, "key 1", &r2); ASSERT_NULL(r); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_free_free); assert_se(m); r = hashmap_remove2(m, "no such key", &r2); @@ -480,7 +465,7 @@ TEST(hashmap_foreach_key) { } TEST(hashmap_foreach) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; bool value_found[] = { false, false, false, false }; char *val1, *val2, *val3, *val4, *s; unsigned count; @@ -499,7 +484,7 @@ TEST(hashmap_foreach) { count++; assert_se(count == 0); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); count = 0; HASHMAP_FOREACH(s, m) @@ -527,8 +512,7 @@ TEST(hashmap_foreach) { } TEST(hashmap_merge) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL; - _cleanup_hashmap_free_ Hashmap *n = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL, *n = NULL; char *val1, *val2, *val3, *val4, *r; val1 = strdup("my val1"); @@ -540,7 +524,7 @@ TEST(hashmap_merge) { val4 = strdup("my val4"); assert_se(val4); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); n = hashmap_new(&string_hash_ops); hashmap_put(m, "Key 1", val1); @@ -559,13 +543,13 @@ TEST(hashmap_merge) { } TEST(hashmap_contains) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; char *val1; val1 = strdup("my val"); assert_se(val1); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); assert_se(!hashmap_contains(m, "Key 1")); hashmap_put(m, "Key 1", val1); @@ -578,13 +562,13 @@ TEST(hashmap_contains) { } TEST(hashmap_isempty) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; char *val1; val1 = strdup("my val"); assert_se(val1); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); assert_se(hashmap_isempty(m)); hashmap_put(m, "Key 1", val1); @@ -594,7 +578,7 @@ TEST(hashmap_isempty) { } TEST(hashmap_size) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; char *val1, *val2, *val3, *val4; val1 = strdup("my val"); @@ -609,7 +593,7 @@ TEST(hashmap_size) { assert_se(hashmap_size(NULL) == 0); assert_se(hashmap_buckets(NULL) == 0); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); hashmap_put(m, "Key 1", val1); hashmap_put(m, "Key 2", val2); @@ -622,7 +606,7 @@ TEST(hashmap_size) { } TEST(hashmap_get) { - _cleanup_hashmap_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; char *r; char *val; @@ -632,7 +616,7 @@ TEST(hashmap_get) { r = hashmap_get(NULL, "Key 1"); ASSERT_NULL(r); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_value_free); hashmap_put(m, "Key 1", val); @@ -646,7 +630,7 @@ TEST(hashmap_get) { } TEST(hashmap_get2) { - _cleanup_(hashmap_free_free_freep) Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *m = NULL; char *r; char *val; char key_orig[] = "Key 1"; @@ -661,7 +645,7 @@ TEST(hashmap_get2) { r = hashmap_get2(NULL, key_orig, &key_copy); ASSERT_NULL(r); - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_free_free); hashmap_put(m, key_copy, val); key_copy = NULL; @@ -870,32 +854,29 @@ TEST(hashmap_steal_first) { assert_se(hashmap_isempty(m)); } -TEST(hashmap_clear_free_free) { +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(test_hash_ops_key, char, string_hash_func, string_compare_func, free); +DEFINE_PRIVATE_HASH_OPS_FULL(test_hash_ops_full, char, string_hash_func, string_compare_func, free, char, free); + +TEST(hashmap_clear) { _cleanup_hashmap_free_ Hashmap *m = NULL; - m = hashmap_new(&string_hash_ops); + m = hashmap_new(&string_hash_ops_free_free); assert_se(m); assert_se(hashmap_put(m, strdup("key 1"), NULL) == 1); assert_se(hashmap_put(m, strdup("key 2"), NULL) == 1); assert_se(hashmap_put(m, strdup("key 3"), NULL) == 1); - hashmap_clear_free_free(m); + hashmap_clear(m); assert_se(hashmap_isempty(m)); assert_se(hashmap_put(m, strdup("key 1"), strdup("value 1")) == 1); assert_se(hashmap_put(m, strdup("key 2"), strdup("value 2")) == 1); assert_se(hashmap_put(m, strdup("key 3"), strdup("value 3")) == 1); - hashmap_clear_free_free(m); + hashmap_clear(m); assert_se(hashmap_isempty(m)); -} - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(test_hash_ops_key, char, string_hash_func, string_compare_func, free); -DEFINE_PRIVATE_HASH_OPS_FULL(test_hash_ops_full, char, string_hash_func, string_compare_func, free, char, free); - -TEST(hashmap_clear_free_with_destructor) { - _cleanup_hashmap_free_ Hashmap *m = NULL; + m = hashmap_free(m); m = hashmap_new(&test_hash_ops_key); assert_se(m); @@ -904,7 +885,7 @@ TEST(hashmap_clear_free_with_destructor) { assert_se(hashmap_put(m, strdup("key 2"), NULL) == 1); assert_se(hashmap_put(m, strdup("key 3"), NULL) == 1); - hashmap_clear_free(m); + hashmap_clear(m); assert_se(hashmap_isempty(m)); m = hashmap_free(m); @@ -915,7 +896,7 @@ TEST(hashmap_clear_free_with_destructor) { assert_se(hashmap_put(m, strdup("key 2"), strdup("value 2")) == 1); assert_se(hashmap_put(m, strdup("key 3"), strdup("value 3")) == 1); - hashmap_clear_free(m); + hashmap_clear(m); assert_se(hashmap_isempty(m)); } diff --git a/src/test/test-hexdecoct.c b/src/test/test-hexdecoct.c index 5c39fc7cc2..e5121d148e 100644 --- a/src/test/test-hexdecoct.c +++ b/src/test/test-hexdecoct.c @@ -503,11 +503,15 @@ TEST(hexdump) { hexdump(stdout, NULL, 0); hexdump(stdout, "", 0); + hexdump(stdout, "", SIZE_MAX); hexdump(stdout, "", 1); hexdump(stdout, "x", 1); + hexdump(stdout, "x", SIZE_MAX); hexdump(stdout, "x", 2); hexdump(stdout, "foobar", 7); + hexdump(stdout, "foobar", SIZE_MAX); hexdump(stdout, "f\nobar", 7); + hexdump(stdout, "f\nobar", SIZE_MAX); hexdump(stdout, "xxxxxxxxxxxxxxxxxxxxyz", 23); for (i = 0; i < ELEMENTSOF(data); i++) diff --git a/src/test/test-json.c b/src/test/test-json.c index 8dd5746495..630ff8c851 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1419,4 +1419,42 @@ TEST(fd_info) { pidref_done(&pidref); } +TEST(json_variant_unset_field) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK_POSITIVE(sd_json_variant_is_blank_object(v)); + ASSERT_OK_ZERO(sd_json_variant_unset_field(&v, "foo")); + ASSERT_OK_POSITIVE(sd_json_variant_is_blank_object(v)); + + ASSERT_NULL(v); + + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR_STRING("foo", "bar"), + SD_JSON_BUILD_PAIR_STRING("quux", "waldo"), + SD_JSON_BUILD_PAIR_STRING("piff", "paff"))); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = sd_json_variant_ref(v); + + ASSERT_OK_POSITIVE(sd_json_variant_equal(v, w)); + ASSERT_OK_ZERO(sd_json_variant_unset_field(&v, "fooxxx")); + ASSERT_OK_POSITIVE(sd_json_variant_equal(v, w)); + ASSERT_OK_POSITIVE(sd_json_variant_unset_field(&v, "foo")); + ASSERT_OK_ZERO(sd_json_variant_equal(v, w)); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *x = NULL; + ASSERT_OK(sd_json_buildo(&x, + SD_JSON_BUILD_PAIR_STRING("quux", "waldo"), + SD_JSON_BUILD_PAIR_STRING("piff", "paff"))); + ASSERT_OK_POSITIVE(sd_json_variant_equal(v, x)); + + ASSERT_OK_POSITIVE(sd_json_variant_unset_field(&v, "piff")); + x = sd_json_variant_unref(x); + ASSERT_OK(sd_json_buildo(&x, + SD_JSON_BUILD_PAIR_STRING("quux", "waldo"))); + ASSERT_OK_POSITIVE(sd_json_variant_equal(x, v)); + + ASSERT_OK_POSITIVE(sd_json_variant_unset_field(&v, "quux")); + ASSERT_OK_POSITIVE(sd_json_variant_is_blank_object(v)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-label.c b/src/test/test-label.c index 06690ec86c..cac6cc0f50 100644 --- a/src/test/test-label.c +++ b/src/test/test-label.c @@ -22,7 +22,7 @@ static int check_path(int dir_fd, const char *path) { if (isempty(path)) return -EINVAL; - /* assume length of pathname is not greater than 40*/ + /* assume length of pathname is not greater than 40 */ if (strlen(path) > 40) return -ENAMETOOLONG; @@ -60,7 +60,7 @@ static int post_labelling_func(int dir_fd, const char *path, bool created) { } static int get_dir_fd(const char *dir_path, mode_t mode) { - /* create a new directory and return its descriptor*/ + /* create a new directory and return its descriptor */ int dir_fd = -EBADF; assert(dir_path); @@ -83,12 +83,12 @@ static int labelling_op(int dir_fd, const char *text, const char *path, mode_t m if (r < 0) return log_error_errno(r, "Error in pathname =>: %m"); - /* Open the file within the directory for writing*/ + /* Open the file within the directory for writing */ write_fd = RET_NERRNO(openat(dir_fd, path, O_CLOEXEC|O_WRONLY|O_TRUNC|O_CREAT, 0644)); if (write_fd < 0) return log_error_errno(write_fd, "Error in opening directory for writing =>: %m"); - /* Write data to the file*/ + /* Write data to the file */ count = RET_NERRNO(write(write_fd, text, strlen(text))); if (count < 0) return log_error_errno(count, "Error occurred while opening file for writing =>: %m"); diff --git a/src/test/test-libmount.c b/src/test/test-libmount.c index 9ba428e5be..d514bb94d3 100644 --- a/src/test/test-libmount.c +++ b/src/test/test-libmount.c @@ -24,7 +24,7 @@ static void test_libmount_unescaping_one( f = fmemopen((char*) string, strlen(string), "r"); assert_se(f); - assert_se(libmount_parse(title, f, &table, &iter) >= 0); + assert_se(libmount_parse_mountinfo(f, &table, &iter) >= 0); struct libmnt_fs *fs; const char *source, *target; diff --git a/src/test/test-mempool.c b/src/test/test-mempool.c index 1f071dba05..b8f139ca30 100644 --- a/src/test/test-mempool.c +++ b/src/test/test-mempool.c @@ -61,7 +61,7 @@ TEST(mempool_trim) { mempool_trim(&test_mempool); - /* free everything from the original set*/ + /* free everything from the original set */ for (size_t i = 0; i < NN; i += 1) { assert_se(!a[i] || a[i]->value == i); diff --git a/src/test/test-mempress.c b/src/test/test-mempress.c index dfbd63e601..6bbc485de6 100644 --- a/src/test/test-mempress.c +++ b/src/test/test-mempress.c @@ -5,8 +5,8 @@ #include <sys/mman.h> #include <unistd.h> -#include <sd-bus.h> -#include <sd-event.h> +#include "sd-bus.h" +#include "sd-event.h" #include "bus-locator.h" #include "bus-wait-for-jobs.h" diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c index 4ac8f869d6..5fb4d23f3e 100644 --- a/src/test/test-mount-util.c +++ b/src/test/test-mount-util.c @@ -393,7 +393,7 @@ TEST(umount_recursive) { assert_se(umount_recursive_full(t->prefix, MNT_DETACH, (char**) t->keep) >= 0); - r = libmount_parse("/proc/self/mountinfo", f, &table, &iter); + r = libmount_parse_mountinfo(f, &table, &iter); if (r < 0) { log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); _exit(EXIT_FAILURE); @@ -452,14 +452,14 @@ TEST(fd_make_mount_point) { fd = open(s, O_PATH|O_CLOEXEC); assert_se(fd >= 0); - assert_se(fd_is_mount_point(fd, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(is_mount_point_at(fd, NULL, AT_SYMLINK_FOLLOW) == 0); assert_se(fd_make_mount_point(fd) > 0); /* Reopen the inode so that we end up on the new mount */ fd2 = open(s, O_PATH|O_CLOEXEC); - assert_se(fd_is_mount_point(fd2, NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(is_mount_point_at(fd2, NULL, AT_SYMLINK_FOLLOW) > 0); assert_se(fd_make_mount_point(fd2) == 0); @@ -587,4 +587,24 @@ TEST(path_is_network_fs_harder) { } } +TEST(umountat) { + int r; + + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + _cleanup_close_ int dfd = mkdtemp_open(NULL, O_CLOEXEC, &p); + ASSERT_OK(dfd); + + ASSERT_OK(mkdirat(dfd, "foo", 0777)); + + _cleanup_free_ char *q = ASSERT_PTR(path_join(p, "foo")); + + r = mount_nofollow_verbose(LOG_ERR, "tmpfs", q, "tmpfs", 0, NULL); + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return (void) log_tests_skipped("not running privileged"); + + ASSERT_OK(r); + ASSERT_OK(umountat_detach_verbose(LOG_ERR, dfd, "foo")); + ASSERT_ERROR(umountat_detach_verbose(LOG_ERR, dfd, "foo"), EINVAL); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-mountpoint-util.c b/src/test/test-mountpoint-util.c index 89093d0212..32a3b927e5 100644 --- a/src/test/test-mountpoint-util.c +++ b/src/test/test-mountpoint-util.c @@ -50,13 +50,13 @@ TEST(mount_propagation_flag) { TEST(mnt_id) { _cleanup_fclose_ FILE *f = NULL; - _cleanup_hashmap_free_free_ Hashmap *h = NULL; + _cleanup_hashmap_free_ Hashmap *h = NULL; char *p; void *k; int r; assert_se(f = fopen("/proc/self/mountinfo", "re")); - assert_se(h = hashmap_new(&trivial_hash_ops)); + assert_se(h = hashmap_new(&trivial_hash_ops_value_free)); for (;;) { _cleanup_free_ char *line = NULL, *path = NULL; @@ -271,8 +271,9 @@ TEST(path_is_mount_point) { assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); } -TEST(fd_is_mount_point) { +TEST(is_mount_point_at) { _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_free_ char *pwd = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -280,44 +281,51 @@ TEST(fd_is_mount_point) { assert_se(fd >= 0); /* Not allowed, since "/" is a path, not a plain filename */ - assert_se(fd_is_mount_point(fd, "/", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, ".", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "./", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "..", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "../", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "/proc", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "/proc/", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "proc/sys", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "proc/sys/", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "/", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "..", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "../", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "/proc", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "/proc/", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "proc/sys", 0) == -EINVAL); + assert_se(is_mount_point_at(fd, "proc/sys/", 0) == -EINVAL); /* This one definitely is a mount point */ - assert_se(fd_is_mount_point(fd, "proc", 0) > 0); - assert_se(fd_is_mount_point(fd, "proc/", 0) > 0); + assert_se(is_mount_point_at(fd, "proc", 0) > 0); + assert_se(is_mount_point_at(fd, "proc/", 0) > 0); safe_close(fd); fd = open("/tmp", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); assert_se(fd >= 0); assert_se(mkdtemp_malloc("/tmp/not-mounted-XXXXXX", &tmpdir) >= 0); - assert_se(fd_is_mount_point(fd, basename(tmpdir), 0) == 0); - assert_se(fd_is_mount_point(fd, strjoina(basename(tmpdir), "/"), 0) == 0); + assert_se(is_mount_point_at(fd, basename(tmpdir), 0) == 0); + assert_se(is_mount_point_at(fd, strjoina(basename(tmpdir), "/"), 0) == 0); safe_close(fd); fd = open("/proc", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); assert_se(fd >= 0); - assert_se(fd_is_mount_point(fd, NULL, 0) > 0); - assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL); - assert_se(fd_is_mount_point(fd, "version", 0) == 0); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, NULL, 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, "", 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, ".", 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, "./", 0)); + ASSERT_OK_ZERO(is_mount_point_at(fd, "version", 0)); + + ASSERT_OK(safe_getcwd(&pwd)); + ASSERT_OK_ERRNO(fchdir(fd)); + + ASSERT_OK_POSITIVE(is_mount_point_at(AT_FDCWD, NULL, 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(AT_FDCWD, "", 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(AT_FDCWD, "./", 0)); + + ASSERT_OK_ERRNO(chdir(pwd)); safe_close(fd); fd = open("/proc/version", O_RDONLY|O_CLOEXEC|O_NOCTTY); assert_se(fd >= 0); - r = fd_is_mount_point(fd, NULL, 0); + r = is_mount_point_at(fd, NULL, 0); assert_se(IN_SET(r, 0, -ENOTDIR)); /* on old kernels we can't determine if regular files are mount points if we have no directory fd */ - assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL); if (!mount_new_api_supported()) return; @@ -346,7 +354,7 @@ TEST(fd_is_mount_point) { ASSERT_OK(readlinkat_malloc(fd, "regular", &t)); ASSERT_STREQ(t, "/usr"); - ASSERT_OK(fd_is_mount_point(fd, "regular", 0)); + ASSERT_OK(is_mount_point_at(fd, "regular", 0)); } TEST(ms_nosymfollow_supported) { diff --git a/src/test/test-pidref.c b/src/test/test-pidref.c index 10033b5826..cb7feb0347 100644 --- a/src/test/test-pidref.c +++ b/src/test/test-pidref.c @@ -7,8 +7,6 @@ #include "stdio-util.h" #include "tests.h" -#define PIDREF_NULL_NONCONST (PidRef) { .fd = -EBADF } - TEST(pidref_is_set) { assert_se(!pidref_is_set(NULL)); assert_se(!pidref_is_set(&PIDREF_NULL)); @@ -17,14 +15,14 @@ TEST(pidref_is_set) { TEST(pidref_equal) { assert_se(pidref_equal(NULL, NULL)); - assert_se(pidref_equal(NULL, &PIDREF_NULL_NONCONST)); - assert_se(pidref_equal(&PIDREF_NULL_NONCONST, NULL)); - assert_se(pidref_equal(&PIDREF_NULL_NONCONST, &PIDREF_NULL_NONCONST)); + assert_se(pidref_equal(NULL, &PIDREF_NULL)); + assert_se(pidref_equal(&PIDREF_NULL, NULL)); + assert_se(pidref_equal(&PIDREF_NULL, &PIDREF_NULL)); assert_se(!pidref_equal(NULL, &PIDREF_MAKE_FROM_PID(1))); assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), NULL)); - assert_se(!pidref_equal(&PIDREF_NULL_NONCONST, &PIDREF_MAKE_FROM_PID(1))); - assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_NULL_NONCONST)); + assert_se(!pidref_equal(&PIDREF_NULL, &PIDREF_MAKE_FROM_PID(1))); + assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_NULL)); assert_se(pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_MAKE_FROM_PID(1))); assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_MAKE_FROM_PID(2))); } @@ -231,7 +229,7 @@ TEST(pidref_is_remote) { assert_se(!pidref_is_remote(&PIDREF_MAKE_FROM_PID(getpid_cached()))); assert_se(!pidref_is_remote(&PIDREF_AUTOMATIC)); - static const PidRef p = { + PidRef p = { .pid = 1, .fd = -EREMOTE, .fd_id = 4711, diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 166c0fa2c9..0db49fe3a8 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -28,6 +28,7 @@ #include "missing_syscall.h" #include "namespace-util.h" #include "parse-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "procfs-util.h" #include "rlimit-util.h" @@ -1065,6 +1066,21 @@ TEST(pidref_from_same_root_fs) { ASSERT_OK_ZERO(pidref_from_same_root_fs(&child2, &self)); } +TEST(pidfd_get_inode_id_self_cached) { + int r; + + log_info("pid=" PID_FMT, getpid_cached()); + + uint64_t id; + r = pidfd_get_inode_id_self_cached(&id); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + log_info("pidfdid not supported"); + else { + assert(r >= 0); + log_info("pidfdid=%" PRIu64, id); + } +} + static int intro(void) { log_show_color(true); return EXIT_SUCCESS; diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 2b44905346..fbfb3990e4 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -1200,7 +1200,7 @@ TEST(strspn_from_end) { } TEST(streq_skip_trailing_chars) { - /* NULL is WHITESPACE by default*/ + /* NULL is WHITESPACE by default */ assert_se(streq_skip_trailing_chars("foo bar", "foo bar", NULL)); assert_se(streq_skip_trailing_chars("foo", "foo", NULL)); assert_se(streq_skip_trailing_chars("foo bar ", "foo bar", NULL)); diff --git a/src/test/test-strv.c b/src/test/test-strv.c index d641043c50..b1d30d73a5 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -1255,4 +1255,26 @@ TEST(strv_find_closest) { ASSERT_NULL(strv_find_closest(l, "sfajosajfosdjaofjdsaf")); } +TEST(strv_equal_ignore_order) { + + ASSERT_TRUE(strv_equal_ignore_order(NULL, NULL)); + ASSERT_TRUE(strv_equal_ignore_order(NULL, STRV_MAKE(NULL))); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE(NULL), NULL)); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE(NULL), STRV_MAKE(NULL))); + + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo"), NULL)); + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo"), STRV_MAKE(NULL))); + ASSERT_FALSE(strv_equal_ignore_order(NULL, STRV_MAKE("foo"))); + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE(NULL), STRV_MAKE("foo"))); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("foo"), STRV_MAKE("foo"))); + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo"), STRV_MAKE("foo", "bar"))); + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("foo", "bar"), STRV_MAKE("foo"))); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("foo", "bar"), STRV_MAKE("foo", "bar"))); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("bar", "foo"), STRV_MAKE("foo", "bar"))); + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("bar", "foo"), STRV_MAKE("foo", "bar", "quux"))); + ASSERT_FALSE(strv_equal_ignore_order(STRV_MAKE("bar", "foo", "quux"), STRV_MAKE("foo", "bar"))); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("bar", "foo", "quux"), STRV_MAKE("quux", "foo", "bar"))); + ASSERT_TRUE(strv_equal_ignore_order(STRV_MAKE("bar", "foo"), STRV_MAKE("bar", "foo", "bar", "foo", "foo"))); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index f3cbaf48a2..6ce4a78adc 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -744,7 +744,7 @@ static int dir_cleanup( if (S_ISDIR(sx.stx_mode)) { int q; - q = fd_is_mount_point(dirfd(d), de->d_name, 0); + q = is_mount_point_at(dirfd(d), de->d_name, 0); if (q < 0) log_debug_errno(q, "Failed to determine whether \"%s/%s\" is a mount point, ignoring: %m", p, de->d_name); else if (q > 0) { @@ -2687,7 +2687,7 @@ static int rm_if_wrong_type_safe( } /* Do not remove mount points. */ - r = fd_is_mount_point(parent_fd, name, follow_links ? AT_SYMLINK_FOLLOW : 0); + r = is_mount_point_at(parent_fd, name, follow_links ? AT_SYMLINK_FOLLOW : 0); if (r < 0) (void) log_warning_errno(r, "Failed to check if \"%s/%s\" is a mount point: %m; continuing.", parent_name ?: "...", name); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 5ff892c25d..1c1b95bef3 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -40,6 +40,7 @@ #include "set.h" #include "signal-util.h" #include "socket-util.h" +#include "static-destruct.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -54,7 +55,9 @@ static enum { static bool arg_plymouth = false; static bool arg_console = false; -static const char *arg_device = NULL; +static char *arg_device = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_device, freep); static int send_passwords(const char *socket_name, char **passwords) { int r; @@ -530,12 +533,13 @@ static int parse_argv(int argc, char *argv[]) { case ARG_CONSOLE: arg_console = true; if (optarg) { - if (isempty(optarg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty console device path is not allowed."); - arg_device = optarg; + arg_device = strdup(optarg); + if (!arg_device) + return log_oom(); } break; @@ -667,6 +671,11 @@ static int ask_on_consoles(char *argv[]) { r = get_kernel_consoles(&consoles); if (r < 0) return log_error_errno(r, "Failed to determine devices of /dev/console: %m"); + if (r <= 1) { + /* No need to spawn subprocesses, there's only one console or using /dev/console as fallback */ + arg_device = TAKE_PTR(consoles[0]); + return 0; + } pids = set_new(NULL); if (!pids) @@ -714,7 +723,7 @@ static int ask_on_consoles(char *argv[]) { } terminate_agents(pids); - return 0; + return 1; } static int run(int argc, char *argv[]) { @@ -728,11 +737,14 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - if (arg_console && !arg_device) - /* - * Spawn a separate process for each console device. - */ - return ask_on_consoles(argv); + /* Spawn a separate process for each console device if there're multiple. */ + if (arg_console && !arg_device) { + r = ask_on_consoles(argv); + if (r != 0) + return r; + + assert(arg_device); + } if (arg_device) /* Later on, a controlling terminal will be acquired, therefore the current process has to diff --git a/src/udev/meson.build b/src/udev/meson.build index f008ea1f66..697d108141 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later udevadm_sources = files( + 'udevadm-cat.c', 'udevadm-control.c', 'udevadm-hwdb.c', 'udevadm-info.c', @@ -31,9 +32,11 @@ libudevd_core_sources = files( 'udev-builtin.c', 'udev-config.c', 'udev-ctrl.c', + 'udev-dump.c', 'udev-event.c', 'udev-format.c', 'udev-manager.c', + 'udev-manager-ctrl.c', 'udev-node.c', 'udev-rules.c', 'udev-spawn.c', diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 9a04abf590..9ad446aa2d 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -136,7 +136,7 @@ static int run(int argc, char *argv[]) { usleep_safe(us); } - assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY) == 0); + assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY, /* extra = */ NULL) == 0); const char *syspath = strjoina("/sys", devpath); r = device_new_from_synthetic_event(&dev, syspath, action); diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index b190504a83..749589463d 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -25,10 +25,10 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, - [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, #if HAVE_ACL [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, #endif + [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, }; void udev_builtin_init(void) { diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index 3b5f3bd120..83cf103ab5 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -47,10 +47,10 @@ extern const UdevBuiltin udev_builtin_net_driver; extern const UdevBuiltin udev_builtin_net_id; extern const UdevBuiltin udev_builtin_net_setup_link; extern const UdevBuiltin udev_builtin_path_id; -extern const UdevBuiltin udev_builtin_usb_id; #if HAVE_ACL extern const UdevBuiltin udev_builtin_uaccess; #endif +extern const UdevBuiltin udev_builtin_usb_id; void udev_builtin_init(void); void udev_builtin_exit(void); diff --git a/src/udev/udev-config.c b/src/udev/udev-config.c index d511691ab2..6562b34d2f 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -112,6 +112,20 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; + } else if (proc_cmdline_key_streq(key, "udev.trace")) { + + if (!value) + config->trace = true; + else { + r = parse_boolean(value); + if (r < 0) + log_warning_errno(r, "Failed to parse udev.trace argument, ignoring: %s", value); + else + config->trace = r; + } + + return 0; + } else { if (startswith(key, "udev.")) log_warning("Unknown udev kernel command line option \"%s\", ignoring.", key); @@ -257,13 +271,24 @@ static int parse_argv(int argc, char *argv[], UdevConfig *config) { manager->config_by_command.name || \ manager->config_by_udev_conf.name; +static void manager_merge_config_log_level(Manager *manager) { + assert(manager); + + MERGE_BOOL(trace); + + if (manager->config.trace) + manager->config.log_level = LOG_DEBUG; + else + MERGE_NON_NEGATIVE(log_level, log_get_max_level()); +} + static void manager_merge_config(Manager *manager) { assert(manager); /* udev.conf has the lowest priority, then followed by command line arguments, kernel command line options, and values set by udev control. */ - MERGE_NON_NEGATIVE(log_level, log_get_max_level()); + manager_merge_config_log_level(manager); MERGE_NON_NEGATIVE(resolve_name_timing, RESOLVE_NAME_EARLY); MERGE_NON_ZERO(exec_delay_usec, 0); MERGE_NON_ZERO(timeout_usec, DEFAULT_WORKER_TIMEOUT_USEC); @@ -306,20 +331,40 @@ void manager_set_children_max(Manager *manager, unsigned n) { void manager_set_log_level(Manager *manager, int log_level) { assert(manager); - assert(LOG_PRI(log_level) == log_level); + assert(log_level_is_valid(log_level)); int old = log_get_max_level(); - log_set_max_level(log_level); - manager->config.log_level = manager->config_by_control.log_level = log_level; + manager->config_by_control.log_level = log_level; + manager_merge_config_log_level(manager); - if (log_level != old) - manager_kill_workers(manager, /* force = */ false); + if (manager->config.log_level == old) + return; + + log_set_max_level(manager->config.log_level); + manager_kill_workers(manager, /* force = */ false); +} + +void manager_set_trace(Manager *manager, bool enable) { + assert(manager); + + bool old = manager->config.trace; + + manager->config_by_control.trace = enable; + manager_merge_config_log_level(manager); + + if (manager->config.trace == old) + return; + + log_set_max_level(manager->config.log_level); + manager_kill_workers(manager, /* force = */ false); } static void manager_adjust_config(UdevConfig *config) { assert(config); + log_set_max_level(config->log_level); + if (config->timeout_usec < MIN_WORKER_TIMEOUT_USEC) { log_debug("Timeout (%s) for processing event is too small, using the default: %s", FORMAT_TIMESPAN(config->timeout_usec, 1), @@ -362,7 +407,7 @@ static int manager_set_environment_one(Manager *manager, const char *s) { _cleanup_free_ char *old_key = NULL, *old_value = NULL; old_value = hashmap_get2(manager->properties, key, (void**) &old_key); - r = hashmap_ensure_replace(&manager->properties, &string_hash_ops, key, value); + r = hashmap_ensure_replace(&manager->properties, &string_hash_ops_free_free, key, value); if (r < 0) { assert(!old_key); assert(!old_value); @@ -411,7 +456,6 @@ int manager_load(Manager *manager, int argc, char *argv[]) { if (arg_debug) log_set_target(LOG_TARGET_CONSOLE); - log_set_max_level(manager->config.log_level); manager_adjust_config(&manager->config); return 1; } @@ -424,7 +468,6 @@ UdevReloadFlags manager_reload_config(Manager *manager) { manager->config_by_udev_conf = UDEV_CONFIG_INIT; manager_parse_udev_config(&manager->config_by_udev_conf); manager_merge_config(manager); - log_set_max_level(manager->config.log_level); manager_adjust_config(&manager->config); if (manager->config.resolve_name_timing != old.resolve_name_timing) @@ -434,7 +477,8 @@ UdevReloadFlags manager_reload_config(Manager *manager) { manager->config.exec_delay_usec != old.exec_delay_usec || manager->config.timeout_usec != old.timeout_usec || manager->config.timeout_signal != old.timeout_signal || - manager->config.blockdev_read_only != old.blockdev_read_only) + manager->config.blockdev_read_only != old.blockdev_read_only || + manager->config.trace != old.trace) return UDEV_RELOAD_KILL_WORKERS; return 0; diff --git a/src/udev/udev-config.h b/src/udev/udev-config.h index 68bb1ea98c..fd31979f6d 100644 --- a/src/udev/udev-config.h +++ b/src/udev/udev-config.h @@ -18,6 +18,7 @@ typedef struct UdevConfig { usec_t timeout_usec; int timeout_signal; bool blockdev_read_only; + bool trace; } UdevConfig; #define UDEV_CONFIG_INIT \ @@ -28,6 +29,7 @@ typedef struct UdevConfig { void manager_set_children_max(Manager *manager, unsigned n); void manager_set_log_level(Manager *manager, int log_level); +void manager_set_trace(Manager *manager, bool enable); void manager_set_environment(Manager *manager, char * const *v); int manager_load(Manager *manager, int argc, char *argv[]); diff --git a/src/udev/udev-ctrl.h b/src/udev/udev-ctrl.h index 11fc0b62de..bcdd849d0d 100644 --- a/src/udev/udev-ctrl.h +++ b/src/udev/udev-ctrl.h @@ -30,7 +30,7 @@ typedef int (*udev_ctrl_handler_t)(UdevCtrl *udev_ctrl, UdevCtrlMessageType type int udev_ctrl_new_from_fd(UdevCtrl **ret, int fd); static inline int udev_ctrl_new(UdevCtrl **ret) { - return udev_ctrl_new_from_fd(ret, -1); + return udev_ctrl_new_from_fd(ret, -EBADF); } int udev_ctrl_enable_receiving(UdevCtrl *uctrl); diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h index ed231764bc..c157c487cf 100644 --- a/src/udev/udev-def.h +++ b/src/udev/udev-def.h @@ -50,10 +50,10 @@ typedef enum UdevBuiltinCommand { UDEV_BUILTIN_NET_ID, UDEV_BUILTIN_NET_LINK, UDEV_BUILTIN_PATH_ID, - UDEV_BUILTIN_USB_ID, #if HAVE_ACL UDEV_BUILTIN_UACCESS, #endif + UDEV_BUILTIN_USB_ID, _UDEV_BUILTIN_MAX, _UDEV_BUILTIN_INVALID = -EINVAL, } UdevBuiltinCommand; @@ -73,10 +73,10 @@ typedef enum UdevReloadFlags { UDEV_RELOAD_BUILTIN_NET_ID = 1u << UDEV_BUILTIN_NET_ID, UDEV_RELOAD_BUILTIN_NET_LINK = 1u << UDEV_BUILTIN_NET_LINK, UDEV_RELOAD_BUILTIN_PATH_ID = 1u << UDEV_BUILTIN_PATH_ID, - UDEV_RELOAD_BUILTIN_USB_ID = 1u << UDEV_BUILTIN_USB_ID, #if HAVE_ACL UDEV_RELOAD_BUILTIN_UACCESS = 1u << UDEV_BUILTIN_UACCESS, #endif + UDEV_RELOAD_BUILTIN_USB_ID = 1u << UDEV_BUILTIN_USB_ID, UDEV_RELOAD_KILL_WORKERS = 1u << (_UDEV_BUILTIN_MAX + 0), UDEV_RELOAD_RULES = 1u << (_UDEV_BUILTIN_MAX + 1), } UdevReloadFlags; diff --git a/src/udev/udev-dump.c b/src/udev/udev-dump.c new file mode 100644 index 0000000000..918c966c4e --- /dev/null +++ b/src/udev/udev-dump.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "ansi-color.h" +#include "device-private.h" +#include "device-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "udev-builtin.h" +#include "udev-dump.h" +#include "udev-event.h" +#include "user-util.h" + +static void event_cache_written_value(Hashmap **values, const char *attr, const char *value) { + assert(values); + + _unused_ _cleanup_free_ void *key = NULL; + free(hashmap_remove2(*values, attr, &key)); + + if (hashmap_put_strdup_full(values, &path_hash_ops_free_free, attr, value) < 0) + log_oom_debug(); +} + +void event_cache_written_sysattr(UdevEvent *event, const char *attr, const char *value) { + event_cache_written_value(&event->written_sysattrs, attr, value); +} + +void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char *value) { + event_cache_written_value(&event->written_sysctls, attr, value); +} + +void dump_event(UdevEvent *event, FILE *f) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + + if (!f) + f = stdout; + + if (!hashmap_isempty(event->written_sysattrs)) { + const char *key, *value; + + fprintf(f, "%sWritten sysfs attributes:%s\n", ansi_highlight(), ansi_normal()); + HASHMAP_FOREACH_KEY(value, key, event->written_sysattrs) + fprintf(f, " %s : %s\n", key, value); + } + + if (!hashmap_isempty(event->written_sysctls)) { + const char *key, *value; + + fprintf(f, "%sWritten sysctl entries:%s\n", ansi_highlight(), ansi_normal()); + HASHMAP_FOREACH_KEY(value, key, event->written_sysctls) + fprintf(f, " %s : %s\n", key, value); + } + + fprintf(f, "%sProperties:%s\n", ansi_highlight(), ansi_normal()); + FOREACH_DEVICE_PROPERTY(dev, key, value) + fprintf(f, " %s=%s\n", key, value); + + if (sd_device_get_tag_first(dev)) { + fprintf(f, "%sTags:%s\n", ansi_highlight(), ansi_normal()); + FOREACH_DEVICE_TAG(dev, tag) + fprintf(f, " %s\n", tag); + } + + if (sd_device_get_devnum(dev, NULL) >= 0) { + + if (sd_device_get_devlink_first(dev)) { + int prio = 0; + (void) device_get_devlink_priority(dev, &prio); + fprintf(f, "%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); + FOREACH_DEVICE_DEVLINK(dev, devlink) + fprintf(f, " %s\n", devlink); + } + + fprintf(f, "%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); + + uid_t uid = event->uid; + if (!uid_is_valid(uid)) + (void) device_get_devnode_uid(dev, &uid); + if (uid_is_valid(uid)) { + _cleanup_free_ char *user = uid_to_name(uid); + fprintf(f, "%sDevice node owner:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); + } + + gid_t gid = event->gid; + if (!gid_is_valid(uid)) + (void) device_get_devnode_gid(dev, &gid); + if (gid_is_valid(gid)) { + _cleanup_free_ char *group = gid_to_name(gid); + fprintf(f, "%sDevice node group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); + } + + mode_t mode = event->mode; + if (mode == MODE_INVALID) + (void) device_get_devnode_mode(dev, &mode); + if (mode != MODE_INVALID) + fprintf(f, "%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); + + if (!ordered_hashmap_isempty(event->seclabel_list)) { + const char *name, *label; + fprintf(f, "%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) + fprintf(f, " %s : %s\n", name, label); + } + } + + if (sd_device_get_ifindex(dev, NULL) >= 0) { + if (!isempty(event->name)) + fprintf(f, "%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); + + if (!strv_isempty(event->altnames)) { + bool space = true; + fprintf(f, "%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); + fputstrv(f, event->altnames, "\n ", &space); + fputs("\n", f); + } + } + + if (!ordered_hashmap_isempty(event->run_list)) { + void *val; + const char *command; + fprintf(f, "%sQueued commands:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { + UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); + + if (builtin_cmd != _UDEV_BUILTIN_INVALID) + fprintf(f, " RUN{builtin} : %s\n", command); + else + fprintf(f, " RUN{program} : %s\n", command); + } + } +} diff --git a/src/udev/udev-dump.h b/src/udev/udev-dump.h new file mode 100644 index 0000000000..514f8267a7 --- /dev/null +++ b/src/udev/udev-dump.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +#include <stdio.h> + +typedef struct UdevEvent UdevEvent; + +void event_cache_written_sysattr(UdevEvent *event, const char *attr, const char *value); +void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char *value); +void dump_event(UdevEvent *event, FILE *f); diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index e3661f5bf8..7d9153061f 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -52,8 +52,10 @@ static UdevEvent* udev_event_free(UdevEvent *event) { sd_device_unref(event->dev); sd_device_unref(event->dev_db_clone); sd_netlink_unref(event->rtnl); - ordered_hashmap_free_free_key(event->run_list); - ordered_hashmap_free_free_free(event->seclabel_list); + ordered_hashmap_free(event->run_list); + ordered_hashmap_free(event->seclabel_list); + hashmap_free(event->written_sysattrs); + hashmap_free(event->written_sysctls); free(event->program_result); free(event->name); strv_free(event->altnames); diff --git a/src/udev/udev-event.h b/src/udev/udev-event.h index b1ad2bf0c2..d18fb0978b 100644 --- a/src/udev/udev-event.h +++ b/src/udev/udev-event.h @@ -36,6 +36,8 @@ typedef struct UdevEvent { gid_t gid; OrderedHashmap *seclabel_list; OrderedHashmap *run_list; + Hashmap *written_sysattrs; + Hashmap *written_sysctls; usec_t birth_usec; unsigned builtin_run; unsigned builtin_ret; @@ -48,6 +50,7 @@ typedef struct UdevEvent { bool name_final; bool devlink_final; bool run_final; + bool trace; bool log_level_was_debug; int default_log_level; EventMode event_mode; diff --git a/src/udev/udev-manager-ctrl.c b/src/udev/udev-manager-ctrl.c new file mode 100644 index 0000000000..b39af06927 --- /dev/null +++ b/src/udev/udev-manager-ctrl.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "sd-daemon.h" +#include "sd-event.h" + +#include "syslog-util.h" +#include "udev-ctrl.h" +#include "udev-manager.h" +#include "udev-manager-ctrl.h" + +/* receive the udevd message from userspace */ +static int on_ctrl_msg(UdevCtrl *uctrl, UdevCtrlMessageType type, const UdevCtrlMessageValue *value, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(value); + + switch (type) { + case UDEV_CTRL_SET_LOG_LEVEL: + if (!log_level_is_valid(value->intval)) { + log_debug("Received invalid udev control message (SET_LOG_LEVEL, %i), ignoring.", value->intval); + break; + } + + log_debug("Received udev control message (SET_LOG_LEVEL), setting log_level=%i", value->intval); + + manager_set_log_level(manager, value->intval); + break; + case UDEV_CTRL_STOP_EXEC_QUEUE: + log_debug("Received udev control message (STOP_EXEC_QUEUE)"); + manager->stop_exec_queue = true; + break; + case UDEV_CTRL_START_EXEC_QUEUE: + log_debug("Received udev control message (START_EXEC_QUEUE)"); + manager->stop_exec_queue = false; + /* It is not necessary to call event_queue_start() here, as it will be called in on_post() if necessary. */ + break; + case UDEV_CTRL_RELOAD: + log_debug("Received udev control message (RELOAD)"); + manager_reload(manager, /* force = */ true); + break; + case UDEV_CTRL_SET_ENV: + if (!udev_property_assignment_is_valid(value->buf)) { + log_debug("Received invalid udev control message(SET_ENV, %s), ignoring.", value->buf); + break; + } + + log_debug("Received udev control message(SET_ENV, %s)", value->buf); + manager_set_environment(manager, STRV_MAKE(value->buf)); + break; + case UDEV_CTRL_SET_CHILDREN_MAX: + if (value->intval < 0) { + log_debug("Received invalid udev control message (SET_MAX_CHILDREN, %i), ignoring.", value->intval); + return 0; + } + + log_debug("Received udev control message (SET_MAX_CHILDREN), setting children_max=%i", value->intval); + + manager_set_children_max(manager, value->intval); + break; + case UDEV_CTRL_PING: + log_debug("Received udev control message (PING)"); + break; + case UDEV_CTRL_EXIT: + log_debug("Received udev control message (EXIT)"); + manager_exit(manager); + break; + default: + log_debug("Received unknown udev control message, ignoring"); + } + + return 1; +} + +int manager_init_ctrl(Manager *manager, int fd) { + int r; + + assert(manager); + + /* This takes passed file descriptor on success. */ + + if (fd >= 0) { + if (manager->ctrl) + return log_warning_errno(SYNTHETIC_ERRNO(EALREADY), "Received multiple control socket (%i), ignoring.", fd); + + r = sd_is_socket(fd, AF_UNIX, SOCK_SEQPACKET, -1); + if (r < 0) + return log_warning_errno(r, "Failed to verify socket type of %i, ignoring: %m", fd); + if (r == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Received invalid control socket (%i), ignoring.", fd); + } else { + if (manager->ctrl) + return 0; + } + + r = udev_ctrl_new_from_fd(&manager->ctrl, fd); + if (r < 0) + return log_error_errno(r, "Failed to initialize udev control socket: %m"); + + return 0; +} + +int manager_start_ctrl(Manager *manager) { + int r; + + assert(manager); + assert(manager->event); + + r = manager_init_ctrl(manager, -EBADF); + if (r < 0) + return r; + + r = udev_ctrl_enable_receiving(manager->ctrl); + if (r < 0) + return log_error_errno(r, "Failed to bind udev control socket: %m"); + + r = udev_ctrl_attach_event(manager->ctrl, manager->event); + if (r < 0) + return log_error_errno(r, "Failed to attach event to udev control: %m"); + + r = udev_ctrl_start(manager->ctrl, on_ctrl_msg, manager); + if (r < 0) + return log_error_errno(r, "Failed to start udev control: %m"); + + /* This needs to be after the inotify and uevent handling, to make sure that the ping is send back + * after fully processing the pending uevents (including the synthetic ones we may create due to + * inotify events). */ + r = sd_event_source_set_priority(udev_ctrl_get_event_source(manager->ctrl), SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return log_error_errno(r, "Failed to set IDLE event priority for udev control event source: %m"); + + return 0; +} diff --git a/src/udev/udev-manager-ctrl.h b/src/udev/udev-manager-ctrl.h new file mode 100644 index 0000000000..507072ce27 --- /dev/null +++ b/src/udev/udev-manager-ctrl.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + +typedef struct Manager Manager; + +int manager_init_ctrl(Manager *manager, int fd); +int manager_start_ctrl(Manager *manager); diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index b7f2b185c9..97465b796d 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -27,6 +27,7 @@ #include "udev-ctrl.h" #include "udev-event.h" #include "udev-manager.h" +#include "udev-manager-ctrl.h" #include "udev-node.h" #include "udev-rules.h" #include "udev-spawn.h" @@ -138,7 +139,7 @@ Manager* manager_free(Manager *manager) { udev_builtin_exit(); - hashmap_free_free_free(manager->properties); + hashmap_free(manager->properties); udev_rules_free(manager->rules); hashmap_free(manager->workers); @@ -283,7 +284,7 @@ void manager_reload(Manager *manager, bool force) { udev_builtin_reload(flags); if (FLAGS_SET(flags, UDEV_RELOAD_RULES)) { - r = udev_rules_load(&rules, manager->config.resolve_name_timing); + r = udev_rules_load(&rules, manager->config.resolve_name_timing, /* extra = */ NULL); if (r < 0) log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); else @@ -843,69 +844,6 @@ static int on_worker(sd_event_source *s, int fd, uint32_t revents, void *userdat return 1; } -/* receive the udevd message from userspace */ -static int on_ctrl_msg(UdevCtrl *uctrl, UdevCtrlMessageType type, const UdevCtrlMessageValue *value, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - - assert(value); - - switch (type) { - case UDEV_CTRL_SET_LOG_LEVEL: - if (LOG_PRI(value->intval) != value->intval) { - log_debug("Received invalid udev control message (SET_LOG_LEVEL, %i), ignoring.", value->intval); - break; - } - - log_debug("Received udev control message (SET_LOG_LEVEL), setting log_level=%i", value->intval); - - manager_set_log_level(manager, value->intval); - break; - case UDEV_CTRL_STOP_EXEC_QUEUE: - log_debug("Received udev control message (STOP_EXEC_QUEUE)"); - manager->stop_exec_queue = true; - break; - case UDEV_CTRL_START_EXEC_QUEUE: - log_debug("Received udev control message (START_EXEC_QUEUE)"); - manager->stop_exec_queue = false; - /* It is not necessary to call event_queue_start() here, as it will be called in on_post() if necessary. */ - break; - case UDEV_CTRL_RELOAD: - log_debug("Received udev control message (RELOAD)"); - manager_reload(manager, /* force = */ true); - break; - case UDEV_CTRL_SET_ENV: - if (!udev_property_assignment_is_valid(value->buf)) { - log_debug("Received invalid udev control message(SET_ENV, %s), ignoring.", value->buf); - break; - } - - log_debug("Received udev control message(SET_ENV, %s)", value->buf); - manager_set_environment(manager, STRV_MAKE(value->buf)); - break; - case UDEV_CTRL_SET_CHILDREN_MAX: - if (value->intval < 0) { - log_debug("Received invalid udev control message (SET_MAX_CHILDREN, %i), ignoring.", value->intval); - return 0; - } - - log_debug("Received udev control message (SET_MAX_CHILDREN), setting children_max=%i", value->intval); - - manager_set_children_max(manager, value->intval); - break; - case UDEV_CTRL_PING: - log_debug("Received udev control message (PING)"); - break; - case UDEV_CTRL_EXIT: - log_debug("Received udev control message (EXIT)"); - manager_exit(manager); - break; - default: - log_debug("Received unknown udev control message, ignoring"); - } - - return 1; -} - static int synthesize_change_one(sd_device *dev, sd_device *target) { int r; @@ -1135,118 +1073,73 @@ Manager* manager_new(void) { return manager; } -static int listen_fds(int *ret_ctrl, int *ret_netlink) { - _cleanup_strv_free_ char **names = NULL; - _cleanup_close_ int ctrl_fd = -EBADF, netlink_fd = -EBADF; - - assert(ret_ctrl); - assert(ret_netlink); - - int n = sd_listen_fds_with_names(/* unset_environment = */ true, &names); - if (n < 0) - return n; - - if (strv_length(names) != (size_t) n) - return -EIO; - - for (int i = 0; i < n; i++) { - int fd = SD_LISTEN_FDS_START + i; - - if (streq(names[i], "varlink")) - continue; /* The fd will be handled by sd_varlink_server_listen_auto(). */ - - if (sd_is_socket(fd, AF_UNIX, SOCK_SEQPACKET, -1) > 0) { - if (ctrl_fd >= 0) { - log_debug("Received multiple seqpacket socket (%s), ignoring.", names[i]); - goto unused; - } - - ctrl_fd = fd; - continue; - } - - if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) { - if (netlink_fd >= 0) { - log_debug("Received multiple netlink socket (%s), ignoring.", names[i]); - goto unused; - } - - netlink_fd = fd; - continue; - } - - log_debug("Received unexpected fd (%s), ignoring.", names[i]); - - unused: - close_and_notify_warn(fd, names[i]); - } - - *ret_ctrl = TAKE_FD(ctrl_fd); - *ret_netlink = TAKE_FD(netlink_fd); - return 0; -} - -static int manager_init_ctrl(Manager *manager, int fd_ctrl) { - _cleanup_(udev_ctrl_unrefp) UdevCtrl *ctrl = NULL; - _cleanup_close_ int fd = fd_ctrl; +static int manager_init_device_monitor(Manager *manager, int fd) { int r; assert(manager); - /* This consumes passed file descriptor. */ + /* This takes passed file descriptor on success. */ - r = udev_ctrl_new_from_fd(&ctrl, fd); - if (r < 0) - return log_error_errno(r, "Failed to initialize udev control socket: %m"); - TAKE_FD(fd); + if (fd >= 0) { + if (manager->monitor) + return log_warning_errno(SYNTHETIC_ERRNO(EALREADY), "Received multiple netlink socket (%i), ignoring.", fd); + + r = sd_is_socket(fd, AF_NETLINK, SOCK_RAW, /* listening = */ -1); + if (r < 0) + return log_warning_errno(r, "Failed to verify socket type of %i, ignoring: %m", fd); + if (r == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Received invalid netlink socket (%i), ignoring.", fd); + } else { + if (manager->monitor) + return 0; + } - r = udev_ctrl_enable_receiving(ctrl); + r = device_monitor_new_full(&manager->monitor, MONITOR_GROUP_KERNEL, fd); if (r < 0) - return log_error_errno(r, "Failed to bind udev control socket: %m"); + return log_error_errno(r, "Failed to initialize device monitor: %m"); - manager->ctrl = TAKE_PTR(ctrl); return 0; } -static int manager_init_device_monitor(Manager *manager, int fd_uevent) { - _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; - _cleanup_close_ int fd = fd_uevent; +static int manager_listen_fds(Manager *manager) { + _cleanup_strv_free_ char **names = NULL; int r; assert(manager); - /* This consumes passed file descriptor. */ + int n = sd_listen_fds_with_names(/* unset_environment = */ true, &names); + if (n < 0) + return n; - r = device_monitor_new_full(&monitor, MONITOR_GROUP_KERNEL, fd); - if (r < 0) - return log_error_errno(r, "Failed to initialize device monitor: %m"); - TAKE_FD(fd); + for (int i = 0; i < n; i++) { + int fd = SD_LISTEN_FDS_START + i; - (void) sd_device_monitor_set_description(monitor, "manager"); + if (streq(names[i], "varlink")) + r = 0; /* The fd will be handled by sd_varlink_server_listen_auto(). */ + else if (streq(names[i], "systemd-udevd-control.socket")) + r = manager_init_ctrl(manager, fd); + else if (streq(names[i], "systemd-udevd-kernel.socket")) + r = manager_init_device_monitor(manager, fd); + else + r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Received unexpected fd (%s), ignoring.", names[i]); + if (r < 0) + close_and_notify_warn(fd, names[i]); + } - manager->monitor = TAKE_PTR(monitor); return 0; } int manager_init(Manager *manager) { - _cleanup_close_ int fd_ctrl = -EBADF, fd_uevent = -EBADF; - _cleanup_free_ char *cgroup = NULL; int r; assert(manager); - r = listen_fds(&fd_ctrl, &fd_uevent); + r = manager_listen_fds(manager); if (r < 0) return log_error_errno(r, "Failed to listen on fds: %m"); - r = manager_init_ctrl(manager, TAKE_FD(fd_ctrl)); - if (r < 0) - return r; - - r = manager_init_device_monitor(manager, TAKE_FD(fd_uevent)); - if (r < 0) - return r; - + _cleanup_free_ char *cgroup = NULL; r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); if (r < 0) log_debug_errno(r, "Failed to get cgroup, ignoring: %m"); @@ -1258,35 +1151,16 @@ int manager_init(Manager *manager) { return 0; } -static int manager_start_ctrl(Manager *manager) { +static int manager_start_device_monitor(Manager *manager) { int r; assert(manager); - assert(manager->ctrl); - r = udev_ctrl_attach_event(manager->ctrl, manager->event); + r = manager_init_device_monitor(manager, -EBADF); if (r < 0) - return log_error_errno(r, "Failed to attach event to udev control: %m"); - - r = udev_ctrl_start(manager->ctrl, on_ctrl_msg, manager); - if (r < 0) - return log_error_errno(r, "Failed to start udev control: %m"); - - /* This needs to be after the inotify and uevent handling, to make sure that the ping is send back - * after fully processing the pending uevents (including the synthetic ones we may create due to - * inotify events). */ - r = sd_event_source_set_priority(udev_ctrl_get_event_source(manager->ctrl), SD_EVENT_PRIORITY_IDLE); - if (r < 0) - return log_error_errno(r, "Failed to set IDLE event priority for udev control event source: %m"); - - return 0; -} - -static int manager_start_device_monitor(Manager *manager) { - int r; + return r; - assert(manager); - assert(manager->monitor); + (void) sd_device_monitor_set_description(manager->monitor, "manager"); r = sd_device_monitor_attach_event(manager->monitor, manager->event); if (r < 0) @@ -1442,7 +1316,7 @@ int manager_main(Manager *manager) { udev_builtin_init(); - r = udev_rules_load(&manager->rules, manager->config.resolve_name_timing); + r = udev_rules_load(&manager->rules, manager->config.resolve_name_timing, /* extra = */ NULL); if (r < 0) return log_error_errno(r, "Failed to read udev rules: %m"); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 3687748993..3c0133d55e 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -18,6 +18,7 @@ #include "fs-util.h" #include "glob-util.h" #include "list.h" +#include "memstream-util.h" #include "mkdir.h" #include "netif-naming-scheme.h" #include "nulstr-util.h" @@ -32,6 +33,7 @@ #include "sysctl-util.h" #include "syslog-util.h" #include "udev-builtin.h" +#include "udev-dump.h" #include "udev-event.h" #include "udev-format.h" #include "udev-node.h" @@ -40,6 +42,7 @@ #include "udev-trace.h" #include "udev-util.h" #include "udev-worker.h" +#include "uid-classification.h" #include "user-util.h" #include "virt.h" @@ -117,6 +120,7 @@ typedef enum { #define _TK_A_MIN _TK_M_MAX /* lvalues which take one of assign operators */ + TK_A_OPTIONS_DUMP, /* no argument */ TK_A_OPTIONS_STRING_ESCAPE_NONE, /* no argument */ TK_A_OPTIONS_STRING_ESCAPE_REPLACE, /* no argument */ TK_A_OPTIONS_DB_PERSIST, /* no argument */ @@ -166,12 +170,15 @@ struct UdevRuleToken { const char *value; void *data; + const char *token_str; /* original token string for logging */ + UdevRuleLine *rule_line; LIST_FIELDS(UdevRuleToken, tokens); }; struct UdevRuleLine { char *line; + char *line_for_logging; unsigned line_number; UdevRuleLineType type; @@ -204,83 +211,172 @@ struct UdevRules { #define LINE_GET_RULES(line) \ ASSERT_PTR(ASSERT_PTR(ASSERT_PTR(line)->rule_file)->rules) +static bool token_is_for_parents(UdevRuleToken *token) { + return token->type >= TK_M_PARENTS_KERNEL && token->type <= TK_M_PARENTS_TAG; +} + /*** Logging helpers ***/ -#define log_udev_rule_internal(device, file, line_nr, level, error, fmt, ...) \ +#define _log_udev_rule_file_full(device, device_u, file, file_u, line_nr, level, level_u, error, fmt, ...) \ ({ \ - int _lv = (level); \ - sd_device *_dev = (device); \ - UdevRuleFile *_f = (file); \ - const char *_n = _f ? _f->filename : NULL; \ + int level_u = (level); \ + sd_device *device_u = (device); \ + UdevRuleFile *file_u = (file); \ \ - if (!_dev && _f) \ - _f->issues |= (1U << _lv); \ + if (!device_u && file_u) \ + file_u->issues |= (1U << level_u); \ \ log_device_full_errno_zerook( \ - _dev, _lv, error, "%s:%u " fmt, \ - strna(_n), line_nr, \ - ##__VA_ARGS__); \ + device_u, level_u, error, "%s:%u " fmt, \ + strna(file_u ? file_u->filename : NULL), \ + line_nr, ##__VA_ARGS__); \ }) -/* Mainly used when applying tokens to the event device. */ -#define log_event_full_errno_zerook(device, token, ...) \ +#define log_udev_rule_file_full(device, file, line_nr, level, error, fmt, ...) \ + _log_udev_rule_file_full(device, UNIQ_T(d, UNIQ), file, UNIQ_T(f, UNIQ), line_nr, level, UNIQ_T(l, UNIQ), error, fmt, ##__VA_ARGS__) + +#define _log_udev_rule_line_full(device, line, line_u, ...) \ ({ \ - UdevRuleToken *_t = (token); \ - UdevRuleLine *_l = _t ? _t->rule_line : NULL; \ + UdevRuleLine *line_u = ASSERT_PTR(line); \ \ - log_udev_rule_internal( \ + log_udev_rule_file_full( \ device, \ - _l ? _l->rule_file : NULL, \ - _l ? _l->line_number : 0, \ + line_u->rule_file, line_u->line_number, \ __VA_ARGS__); \ }) -#define log_event_full_errno(device, token, level, error, ...) \ +#define log_udev_rule_line_full(device, line, ...) \ + _log_udev_rule_line_full(device, line, UNIQ_T(l, UNIQ), __VA_ARGS__) + +/* Mainly used when applying tokens to the event device. */ +#define _log_event_full_errno_zerook(event, event_u, token, token_u, level, error, fmt, ...) \ ({ \ - int _error = (error); \ - ASSERT_NON_ZERO(_error); \ + UdevEvent *event_u = ASSERT_PTR(event); \ + UdevRuleToken *token_u = ASSERT_PTR(token); \ + \ + log_udev_rule_line_full( \ + token_is_for_parents(token_u) ? event_u->dev_parent : event_u->dev, \ + token_u->rule_line, \ + level, error, "%s: " fmt, \ + token_u->token_str, ##__VA_ARGS__); \ + }) + +#define log_event_full_errno_zerook(event, token, ...) \ + _log_event_full_errno_zerook(event, UNIQ_T(e, UNIQ), token, UNIQ_T(t, UNIQ), __VA_ARGS__) + +#define _log_event_full_errno(event, token, level, error, error_u, ...) \ + ({ \ + int error_u = (error); \ + ASSERT_NON_ZERO(error_u); \ log_event_full_errno_zerook( \ - device, token, level, _error, ##__VA_ARGS__); \ + event, token, level, error_u, \ + __VA_ARGS__); \ }) -#define log_event_full(device, token, level, ...) (void) log_event_full_errno_zerook(device, token, level, 0, __VA_ARGS__) +#define log_event_full_errno(event, token, level, error, ...) \ + _log_event_full_errno(event, token, level, error, UNIQ_T(e, UNIQ), __VA_ARGS__) -#define log_event_debug(device, token, ...) log_event_full(device, token, LOG_DEBUG, __VA_ARGS__) -#define log_event_info(device, token, ...) log_event_full(device, token, LOG_INFO, __VA_ARGS__) -#define log_event_notice(device, token, ...) log_event_full(device, token, LOG_NOTICE, __VA_ARGS__) -#define log_event_warning(device, token, ...) log_event_full(device, token, LOG_WARNING, __VA_ARGS__) -#define log_event_error(device, token, ...) log_event_full(device, token, LOG_ERR, __VA_ARGS__) +#define log_event_full(event, token, level, ...) (void) log_event_full_errno_zerook(event, token, level, 0, __VA_ARGS__) -#define log_event_debug_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_DEBUG, error, __VA_ARGS__) -#define log_event_info_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_INFO, error, __VA_ARGS__) -#define log_event_notice_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_NOTICE, error, __VA_ARGS__) -#define log_event_warning_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_WARNING, error, __VA_ARGS__) -#define log_event_error_errno(device, token, error, ...) log_event_full_errno(device, token, LOG_ERR, error, __VA_ARGS__) +#define log_event_debug(event, token, ...) log_event_full(event, token, LOG_DEBUG, __VA_ARGS__) +#define log_event_info(event, token, ...) log_event_full(event, token, LOG_INFO, __VA_ARGS__) +#define log_event_notice(event, token, ...) log_event_full(event, token, LOG_NOTICE, __VA_ARGS__) +#define log_event_warning(event, token, ...) log_event_full(event, token, LOG_WARNING, __VA_ARGS__) +#define log_event_error(event, token, ...) log_event_full(event, token, LOG_ERR, __VA_ARGS__) -/* Mainly used when parsing .rules files. */ -#define log_file_full_errno_zerook(...) \ - log_udev_rule_internal(NULL, __VA_ARGS__) +#define log_event_debug_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_DEBUG, error, __VA_ARGS__) +#define log_event_info_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_INFO, error, __VA_ARGS__) +#define log_event_notice_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_NOTICE, error, __VA_ARGS__) +#define log_event_warning_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_WARNING, error, __VA_ARGS__) +#define log_event_error_errno(event, token, error, ...) log_event_full_errno(event, token, LOG_ERR, error, __VA_ARGS__) -#define log_file_error(file, line_nr, ...) \ - log_file_full_errno_zerook(file, line_nr, LOG_ERR, 0, __VA_ARGS__) +#define _log_event_trace(event, event_u, ...) \ + ({ \ + UdevEvent *event_u = ASSERT_PTR(event); \ + \ + event_u->trace ? \ + log_event_debug(event_u, __VA_ARGS__) : \ + (void) 0; \ + }) + +#define log_event_trace(event, ...) \ + _log_event_trace(event, UNIQ_T(e, UNIQ), __VA_ARGS__) -#define log_line_full_errno_zerook(line, ...) \ +#define _log_event_result(event, token, result, result_u) \ ({ \ - UdevRuleLine *_l = (line); \ - log_file_full_errno_zerook( \ - _l ? _l->rule_file : NULL, \ - _l ? _l->line_number : 0, \ - __VA_ARGS__); \ + bool result_u = (result); \ + \ + log_event_trace(event, token, "%s", \ + result_u ? "PASS" : "FAIL"); \ + result_u; \ }) -#define log_line_full_errno(line, level, error, ...) \ +#define log_event_result(event, token, result) \ + _log_event_result(event, token, result, UNIQ_T(r, UNIQ)) + +#define log_event_done(event, token) \ ({ \ - int _error = (error); \ - ASSERT_NON_ZERO(_error); \ - log_line_full_errno_zerook( \ - line, level, _error, ##__VA_ARGS__); \ + log_event_trace(event, token, "DONE"); \ + true; \ }) +#define _log_event_final_set(event, token, token_u) \ + ({ \ + UdevRuleToken *token_u = ASSERT_PTR(token); \ + \ + log_event_trace(event, token_u, \ + "Already assigned final value, ignoring further %s.", \ + token_u->op == OP_REMOVE ? "modification" : "assignment"); \ + true; \ + }) + +#define log_event_final_set(event, token) \ + _log_event_final_set(event, token, UNIQ_T(t, UNIQ)) + +#define _log_event_truncated(event, token, token_u, what, format) \ + ({ \ + UdevRuleToken *token_u = ASSERT_PTR(token); \ + \ + token_u->type < _TK_M_MAX ? \ + log_event_debug(event, token_u, \ + "The %s is truncated while substituting into \"%s\", assuming the token fails.", \ + what, (const char*) format) : \ + log_event_warning( \ + event, token_u, \ + "The %s is truncated while substituting into \"%s\", refusing to apply the token.", \ + what, (const char*) format); \ + }) + +#define log_event_truncated(event, token, what, format) \ + _log_event_truncated(event, token, UNIQ_T(t, UNIQ), what, format) + +#define _log_event_line(event, event_u, line, ...) \ + ({ \ + UdevEvent *event_u = ASSERT_PTR(event); \ + \ + event_u->trace ? \ + log_udev_rule_line_full( \ + event_u->dev, line, \ + LOG_DEBUG, 0, __VA_ARGS__) : \ + 0; \ + }) + +#define log_event_line(event, line, ...) \ + _log_event_line(event, UNIQ_T(e, UNIQ), line, __VA_ARGS__) + +/* Mainly used when parsing .rules files. */ +#define log_file_full_errno_zerook(...) \ + log_udev_rule_file_full(NULL, __VA_ARGS__) + +#define log_file_error(file, line_nr, ...) \ + log_file_full_errno_zerook(file, line_nr, LOG_ERR, 0, __VA_ARGS__) + +#define log_line_full_errno_zerook(...) \ + log_udev_rule_line_full(NULL, __VA_ARGS__) + +#define log_line_full_errno(line, level, error, ...) \ + log_udev_rule_line_full(NULL, line, level, error, __VA_ARGS__) + #define log_line_full(line, level, ...) (void) log_line_full_errno_zerook(line, level, 0, __VA_ARGS__) #define log_line_debug(line, ...) log_line_full(line, LOG_DEBUG, __VA_ARGS__) @@ -312,38 +408,6 @@ struct UdevRules { "Invalid value \"%s\" for %s (char %zu: %s), ignoring.", \ value, key, offset, hint) -static void log_unknown_owner(sd_device *dev, UdevRuleLine *line, int error, const char *entity, const char *name) { - assert(line); - ASSERT_NON_ZERO(error); - - if (IN_SET(abs(error), ENOENT, ESRCH)) - log_udev_rule_internal(dev, line->rule_file, line->line_number, LOG_ERR, error, - "Unknown %s '%s', ignoring.", entity, name); - else - log_udev_rule_internal(dev, line->rule_file, line->line_number, LOG_ERR, error, - "Failed to resolve %s '%s', ignoring: %m", entity, name); -} - -static void log_event_truncated( - sd_device *dev, - UdevRuleToken *token, - const char *what, - const char *format, - const char *key, - bool is_match) { - - if (is_match) - log_event_debug(dev, token, - "The %s is truncated while substituting into '%s', " - "assuming the %s key does not match.", - what, format, key); - else - log_event_warning(dev, token, - "The %s is truncated while substituting into '%s', " - "refusing to apply the %s key.", - what, format, key); -} - /*** Other functions ***/ static UdevRuleToken* udev_rule_token_free(UdevRuleToken *token) { @@ -375,6 +439,7 @@ static UdevRuleLine* udev_rule_line_free(UdevRuleLine *rule_line) { LIST_REMOVE(rule_lines, rule_line->rule_file->rule_lines, rule_line); free(rule_line->line); + free(rule_line->line_for_logging); return mfree(rule_line); } @@ -403,8 +468,8 @@ UdevRules* udev_rules_free(UdevRules *rules) { LIST_FOREACH(rule_files, i, rules->rule_files) udev_rule_file_free(i); - hashmap_free_free_key(rules->known_users); - hashmap_free_free_key(rules->known_groups); + hashmap_free(rules->known_users); + hashmap_free(rules->known_groups); hashmap_free(rules->stats_by_path); return mfree(rules); } @@ -425,20 +490,28 @@ static int rule_resolve_user(UdevRuleLine *rule_line, const char *name, uid_t *r return 0; } - r = get_user_creds(&name, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); - if (r < 0) { - log_unknown_owner(NULL, rule_line, r, "user", name); - *ret = UID_INVALID; - return 0; - } + r = get_user_creds( + &name, + &uid, + /* ret_gid = */ NULL, + /* ret_home = */ NULL, + /* ret_shell = */ NULL, + USER_CREDS_ALLOW_MISSING); + if (r == -ESRCH) + return log_line_error_errno(rule_line, r, "Unknown user '%s', ignoring.", name); + if (r < 0) + return log_line_error_errno(rule_line, r, "Failed to resolve user '%s', ignoring: %m", name); + if (!uid_is_system(uid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "User '%s' is not a system user (UID="UID_FMT"), ignoring.", name, uid); n = strdup(name); if (!n) - return -ENOMEM; + return log_oom(); - r = hashmap_ensure_put(known_users, &string_hash_ops, n, UID_TO_PTR(uid)); + r = hashmap_ensure_put(known_users, &string_hash_ops_free, n, UID_TO_PTR(uid)); if (r < 0) - return r; + return log_oom(); TAKE_PTR(n); *ret = uid; @@ -462,19 +535,21 @@ static int rule_resolve_group(UdevRuleLine *rule_line, const char *name, gid_t * } r = get_group_creds(&name, &gid, USER_CREDS_ALLOW_MISSING); - if (r < 0) { - log_unknown_owner(NULL, rule_line, r, "group", name); - *ret = GID_INVALID; - return 0; - } + if (r == -ESRCH) + return log_line_error_errno(rule_line, r, "Unknown group '%s', ignoring.", name); + if (r < 0) + return log_line_error_errno(rule_line, r, "Failed to resolve group '%s', ignoring: %m", name); + if (!gid_is_system(gid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "Group '%s' is not a system group (GID="GID_FMT"), ignoring.", name, gid); n = strdup(name); if (!n) - return -ENOMEM; + return log_oom(); - r = hashmap_ensure_put(known_groups, &string_hash_ops, n, GID_TO_PTR(gid)); + r = hashmap_ensure_put(known_groups, &string_hash_ops_free, n, GID_TO_PTR(gid)); if (r < 0) - return r; + return log_oom(); TAKE_PTR(n); *ret = gid; @@ -495,7 +570,15 @@ static bool type_has_nulstr_value(UdevRuleTokenType type) { return type < TK_M_TEST || type == TK_M_RESULT; } -static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, UdevRuleOperatorType op, char *value, void *data, bool is_case_insensitive) { +static int rule_line_add_token( + UdevRuleLine *rule_line, + UdevRuleTokenType type, + UdevRuleOperatorType op, + char *value, + void *data, + bool is_case_insensitive, + const char *token_str) { + _cleanup_(udev_rule_token_freep) UdevRuleToken *token = NULL; UdevRuleMatchType match_type = _MATCH_TYPE_INVALID; UdevRuleSubstituteType subst_type = _SUBST_TYPE_INVALID; @@ -583,6 +666,7 @@ static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, .data = data, .match_type = match_type, .attr_subst_type = subst_type, + .token_str = token_str, .rule_line = rule_line, }; @@ -630,7 +714,15 @@ static int check_attr_format_and_warn(UdevRuleLine *line, const char *key, const return 0; } -static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, UdevRuleOperatorType op, char *value, bool is_case_insensitive) { +static int parse_token( + UdevRuleLine *rule_line, + const char *key, + char *attr, + UdevRuleOperatorType op, + char *value, + bool is_case_insensitive, + const char *token_str) { + ResolveNameTiming resolve_name_timing = LINE_GET_RULES(rule_line)->resolve_name_timing; bool is_match = IN_SET(op, OP_MATCH, OP_NOMATCH); int r; @@ -648,29 +740,29 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "DEVPATH")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "KERNEL")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "SYMLINK")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) { check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "NAME")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -690,9 +782,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude "Ignoring NAME=\"\", as udev will not delete any network interfaces."); check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "ENV")) { if (isempty(attr)) return log_line_invalid_attr(rule_line, key); @@ -710,15 +802,15 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "CONST")) { if (isempty(attr) || !STR_IN_SET(attr, "arch", "virt")) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_CONST, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_CONST, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "TAG")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -730,9 +822,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "SUBSYSTEM")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -742,14 +834,14 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (STR_IN_SET(value, "bus", "class")) log_line_warning(rule_line, "\"%s\" must be specified as \"subsystem\".", value); - r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "DRIVER")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "ATTR")) { r = check_attr_format_and_warn(rule_line, key, attr); if (r < 0) @@ -763,9 +855,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) { check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "SYSCTL")) { r = check_attr_format_and_warn(rule_line, key, attr); if (r < 0) @@ -779,30 +871,30 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) { check_value_format_and_warn(rule_line, key, value, false); - r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr, /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "KERNELS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "SUBSYSTEMS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "DRIVERS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "ATTRS")) { r = check_attr_format_and_warn(rule_line, key, attr); if (r < 0) @@ -815,14 +907,14 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (strstr(attr, "../")) log_line_warning(rule_line, "Direct reference to parent sysfs directory, may break in future kernels."); - r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr, is_case_insensitive, token_str); } else if (streq(key, "TAGS")) { if (attr) return log_line_invalid_attr(rule_line, key); if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "TEST")) { mode_t mode = MODE_INVALID; @@ -837,7 +929,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (is_case_insensitive) return log_line_invalid_prefix(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode), is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode), is_case_insensitive, token_str); } else if (streq(key, "PROGRAM")) { if (attr) return log_line_invalid_attr(rule_line, key); @@ -849,7 +941,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (is_case_insensitive) return log_line_invalid_prefix(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL, /* is_case_insensitive */ false); + r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL, /* is_case_insensitive */ false, token_str); } else if (streq(key, "IMPORT")) { if (isempty(attr)) return log_line_invalid_attr(rule_line, key); @@ -862,16 +954,16 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude return log_line_invalid_prefix(rule_line, key); if (streq(attr, "file")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "program")) { UdevBuiltinCommand cmd; cmd = udev_builtin_lookup(value); if (cmd >= 0) { log_line_debug(rule_line, "Found builtin command '%s' for %s, replacing attribute.", value, key); - r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false, token_str); } else - r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else if (streq(attr, "builtin")) { UdevBuiltinCommand cmd; @@ -879,13 +971,13 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (cmd < 0) return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), "Unknown builtin command: %s", value); - r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false, token_str); } else if (streq(attr, "db")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "cmdline")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "parent")) - r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL, /* is_case_insensitive = */ false, token_str); else return log_line_invalid_attr(rule_line, key); } else if (streq(key, "RESULT")) { @@ -894,7 +986,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (!is_match) return log_line_invalid_op(rule_line, key); - r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL, is_case_insensitive); + r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL, is_case_insensitive, token_str); } else if (streq(key, "OPTIONS")) { char *tmp; @@ -905,25 +997,27 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (op == OP_ADD) op = OP_ASSIGN; - if (streq(value, "string_escape=none")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL, /* is_case_insensitive = */ false); + if (streq(value, "dump")) + r = rule_line_add_token(rule_line, TK_A_OPTIONS_DUMP, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); + else if (streq(value, "string_escape=none")) + r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(value, "string_escape=replace")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(value, "db_persist")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(value, "watch")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1), /* is_case_insensitive = */ false, token_str); else if (streq(value, "nowatch")) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0), /* is_case_insensitive = */ false, token_str); else if ((tmp = startswith(value, "static_node="))) - r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL, /* is_case_insensitive = */ false, token_str); else if ((tmp = startswith(value, "link_priority="))) { int prio; r = safe_atoi(tmp, &prio); if (r < 0) return log_line_error_errno(rule_line, r, "Failed to parse link priority '%s': %m", tmp); - r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio), /* is_case_insensitive = */ false, token_str); } else if ((tmp = startswith(value, "log_level="))) { int level; @@ -934,7 +1028,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (level < 0) return log_line_error_errno(rule_line, level, "Failed to parse log level '%s': %m", tmp); } - r = rule_line_add_token(rule_line, TK_A_OPTIONS_LOG_LEVEL, op, NULL, INT_TO_PTR(level), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OPTIONS_LOG_LEVEL, op, NULL, INT_TO_PTR(level), /* is_case_insensitive = */ false, token_str); } else { log_line_warning(rule_line, "Invalid value for OPTIONS key, ignoring: '%s'", value); return 0; @@ -951,18 +1045,23 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude op = OP_ASSIGN; } - if (parse_uid(value, &uid) >= 0) - r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false); - else if (resolve_name_timing == RESOLVE_NAME_EARLY && + if (parse_uid(value, &uid) >= 0) { + if (!uid_is_system(uid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "UID="UID_FMT" is not in the system user range, ignoring.", uid); + + r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false, token_str); + } else if (resolve_name_timing == RESOLVE_NAME_EARLY && rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) { + r = rule_resolve_user(rule_line, value, &uid); if (r < 0) - return log_line_error_errno(rule_line, r, "Failed to resolve user name '%s': %m", value); + return r; - r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false, token_str); } else if (resolve_name_timing != RESOLVE_NAME_NEVER) { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else { log_line_debug(rule_line, "User name resolution is disabled, ignoring %s=\"%s\".", key, value); return 0; @@ -979,18 +1078,23 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude op = OP_ASSIGN; } - if (parse_gid(value, &gid) >= 0) - r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false); - else if (resolve_name_timing == RESOLVE_NAME_EARLY && + if (parse_gid(value, &gid) >= 0) { + if (!gid_is_system(gid)) + return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), + "GID="GID_FMT" is not in the system group range, ignoring.", gid); + + r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false, token_str); + } else if (resolve_name_timing == RESOLVE_NAME_EARLY && rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) { + r = rule_resolve_group(rule_line, value, &gid); if (r < 0) - return log_line_error_errno(rule_line, r, "Failed to resolve group name '%s': %m", value); + return r; - r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false, token_str); } else if (resolve_name_timing != RESOLVE_NAME_NEVER) { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL, /* is_case_insensitive = */ false, token_str); } else { log_line_debug(rule_line, "Resolving group name is disabled, ignoring GROUP=\"%s\".", value); return 0; @@ -1008,10 +1112,10 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude } if (parse_mode(value, &mode) >= 0) - r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode), /* is_case_insensitive = */ false, token_str); else { check_value_format_and_warn(rule_line, key, value, true); - r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL, /* is_case_insensitive = */ false, token_str); } } else if (streq(key, "SECLABEL")) { if (isempty(attr)) @@ -1024,13 +1128,13 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude op = OP_ASSIGN; } - r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, attr, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, attr, /* is_case_insensitive = */ false, token_str); } else if (streq(key, "RUN")) { if (is_match || op == OP_REMOVE) return log_line_invalid_op(rule_line, key); check_value_format_and_warn(rule_line, key, value, true); if (!attr || streq(attr, "program")) - r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false, token_str); else if (streq(attr, "builtin")) { UdevBuiltinCommand cmd; @@ -1038,7 +1142,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude if (cmd < 0) return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), "Unknown builtin command '%s', ignoring.", value); - r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false); + r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false, token_str); } else return log_line_invalid_attr(rule_line, key); } else if (streq(key, "GOTO")) { @@ -1298,32 +1402,30 @@ static void sort_tokens(UdevRuleLine *rule_line) { } } -static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr, bool extra_checks) { +static int rule_add_line(UdevRuleFile *rule_file, const char *line, unsigned line_nr, bool extra_checks) { _cleanup_(udev_rule_line_freep) UdevRuleLine *rule_line = NULL; - _cleanup_free_ char *line = NULL; char *p; int r; assert(rule_file); - assert(line_str); + assert(line); - if (isempty(line_str)) + if (isempty(line)) return 0; - line = strdup(line_str); - if (!line) - return log_oom(); - rule_line = new(UdevRuleLine, 1); if (!rule_line) return log_oom(); *rule_line = (UdevRuleLine) { - .line = TAKE_PTR(line), + .line = strdup(line), + .line_for_logging = strdup(line), .line_number = line_nr, - .rule_file = rule_file, }; + if (!rule_line->line || !rule_line->line_for_logging) + return log_oom(); + rule_line->rule_file = rule_file; LIST_APPEND(rule_lines, rule_file->rule_lines, rule_line); for (p = rule_line->line; !isempty(p); ) { @@ -1340,7 +1442,9 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned if (r == 0) break; - r = parse_token(rule_line, key, attr, op, value, is_case_insensitive); + char *token_str = rule_line->line_for_logging + (key - rule_line->line); + token_str[p - key] = '\0'; + r = parse_token(rule_line, key, attr, op, value, is_case_insensitive, token_str); if (r < 0) return r; } @@ -1540,10 +1644,8 @@ static void udev_check_rule_line(UdevRuleLine *line) { int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) { _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL; - _cleanup_free_ char *continuation = NULL, *name = NULL; + _cleanup_free_ char *name = NULL; _cleanup_fclose_ FILE *f = NULL; - bool ignore_line = false; - unsigned line_nr = 0; struct stat st; int r; @@ -1594,6 +1696,9 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che LIST_APPEND(rule_files, rules->rule_files, rule_file); + _cleanup_free_ char *continuation = NULL; + unsigned line_nr = 0, current_line_nr = 0; + bool ignore_line = false; for (;;) { _cleanup_free_ char *buf = NULL; size_t len; @@ -1605,7 +1710,10 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che if (r == 0) break; - line_nr++; + current_line_nr++; + if (!continuation) + line_nr = current_line_nr; + line = skip_leading_chars(buf, NULL); /* Lines beginning with '#' are ignored regardless of line continuation. */ @@ -1687,16 +1795,26 @@ UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) { return rules; } -int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing) { +int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; - _cleanup_strv_free_ char **files = NULL; + _cleanup_strv_free_ char **files = NULL, **directories = NULL; int r; rules = udev_rules_new(resolve_name_timing); if (!rules) return -ENOMEM; - r = conf_files_list_strv(&files, ".rules", NULL, 0, RULES_DIRS); + if (!strv_isempty(extra)) { + directories = strv_copy(extra); + if (!directories) + return -ENOMEM; + } + + r = strv_extend_strv(&directories, CONF_PATHS_STRV("udev/rules.d"), /* filter_duplicates = */ false); + if (r < 0) + return r; + + r = conf_files_list_strv(&files, ".rules", NULL, 0, (const char* const*) directories); if (r < 0) return log_debug_errno(r, "Failed to enumerate rules files: %m"); @@ -1731,10 +1849,59 @@ bool udev_rules_should_reload(UdevRules *rules) { return false; } -static bool token_match_string(UdevRuleToken *token, const char *str) { +static bool apply_format_full( + UdevEvent *event, + UdevRuleToken *token, + const char *format, + char *result, + size_t result_size, + bool replace_whitespace, + const char *what) { + + assert(event); + assert(token); + assert(format); + assert(result); + assert(what); + + bool truncated = false; + (void) udev_event_apply_format(event, format, result, result_size, replace_whitespace, &truncated); + if (truncated) { + log_event_truncated(event, token, what, format); + return false; + } + + if (event->trace && !streq(format, result)) + log_event_trace(event, token, "Format substitution: \"%s\" -> \"%s\"", format, result); + + return true; +} + +static bool apply_format_value( + UdevEvent *event, + UdevRuleToken *token, + char *result, + size_t result_size, + const char *what) { + + return apply_format_full(event, token, token->value, result, result_size, /* replace_whitespace = */ false, what); +} + +static bool apply_format_attr( + UdevEvent *event, + UdevRuleToken *token, + char *result, + size_t result_size, + const char *what) { + + return apply_format_full(event, token, token->data, result, result_size, /* replace_whitespace = */ false, what); +} + +static bool token_match_string(UdevEvent *event, UdevRuleToken *token, const char *str, bool log_result) { const char *value; bool match = false, case_insensitive; + assert(event); assert(token); assert(token->value); assert(token->type < _TK_M_MAX); @@ -1783,13 +1950,56 @@ static bool token_match_string(UdevRuleToken *token, const char *str) { assert_not_reached(); } - return token->op == (match ? OP_MATCH : OP_NOMATCH); + bool result = token->op == (match ? OP_MATCH : OP_NOMATCH); + + if (event->trace) + switch (token->match_type & _MATCH_TYPE_MASK) { + case MATCH_TYPE_EMPTY: + log_event_trace(event, token, "String \"%s\" is%s empty%s", + strempty(str), match ? "" : " not", + log_result ? result ? ": PASS" : ": FAIL" : "."); + break; + case MATCH_TYPE_SUBSYSTEM: + log_event_trace(event, token, + "String \"%s\" matches %s of \"subsystem\", \"class\", or \"bus\"%s", + strempty(str), match ? "one" : "neither", + log_result ? result ? ": PASS" : ": FAIL" : "."); + break; + case MATCH_TYPE_PLAIN_WITH_EMPTY: + case MATCH_TYPE_PLAIN: + case MATCH_TYPE_GLOB_WITH_EMPTY: + case MATCH_TYPE_GLOB: { + _cleanup_free_ char *joined = NULL; + unsigned c = 0; + + if (IN_SET(token->match_type & _MATCH_TYPE_MASK, MATCH_TYPE_PLAIN_WITH_EMPTY, MATCH_TYPE_GLOB_WITH_EMPTY)) { + (void) strextend_with_separator(&joined, ", ", "\"\""); + c++; + } + + NULSTR_FOREACH(i, value) { + (void) strextendf_with_separator(&joined, ", ", "\"%s\"", i); + c++; + } + + assert(c > 0); + log_event_trace(event, token, "String \"%s\" %s %s%s", + strempty(str), + match ? (c > 1 ? "matches one of" : "matches") : (c > 1 ? "matches neither of" : "does not match"), + strempty(joined), + log_result ? result ? ": PASS" : ": FAIL" : "."); + break; + } + default: + assert_not_reached(); + } + + return result; } static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *event) { char nbuf[UDEV_NAME_SIZE], vbuf[UDEV_NAME_SIZE]; const char *name, *value; - bool truncated; assert(token); assert(IN_SET(token->type, TK_M_ATTR, TK_M_PARENTS_ATTR)); @@ -1800,12 +2010,8 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev switch (token->attr_subst_type) { case SUBST_TYPE_FORMAT: - (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "sysfs attribute name", name, - token->type == TK_M_ATTR ? "ATTR" : "ATTRS", /* is_match = */ true); + if (!apply_format_attr(event, token, nbuf, sizeof(nbuf), "sysfs attribute name")) return false; - } name = nbuf; _fallthrough_; @@ -1819,7 +2025,7 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev value = delete_trailing_chars(vbuf, NULL); } - return token_match_string(token, value); + return token_match_string(event, token, value, /* log_result = */ true); case SUBST_TYPE_SUBSYS: if (udev_resolve_subsys_kernel(name, vbuf, sizeof(vbuf), true) < 0) @@ -1829,7 +2035,7 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev if (FLAGS_SET(token->match_type, MATCH_REMOVE_TRAILING_WHITESPACE)) delete_trailing_chars(vbuf, NULL); - return token_match_string(token, vbuf); + return token_match_string(event, token, vbuf, /* log_result = */ true); default: assert_not_reached(); @@ -1881,30 +2087,6 @@ static int get_property_from_string(char *line, char **ret_key, char **ret_value return 1; } -static int import_parent_into_properties(sd_device *dev, const char *filter) { - sd_device *parent; - int r; - - assert(dev); - assert(filter); - - r = sd_device_get_parent(dev, &parent); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - FOREACH_DEVICE_PROPERTY(parent, key, val) { - if (fnmatch(filter, key, 0) != 0) - continue; - r = device_add_property(dev, key, val); - if (r < 0) - return r; - } - - return 1; -} - static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) { _cleanup_closedir_ DIR *dir = NULL; char buf[UDEV_PATH_SIZE], *p; @@ -1949,7 +2131,7 @@ static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) { return -ENOENT; } -static size_t udev_replace_ifname(char *str) { +static size_t udev_replace_ifname_strict(char *str) { size_t replaced = 0; assert(str); @@ -1965,6 +2147,35 @@ static size_t udev_replace_ifname(char *str) { return replaced; } +static void udev_replace_ifname(UdevEvent *event, UdevRuleToken *token, char *buf) { + assert(event); + assert(token); + assert(buf); + + size_t count; + if (naming_scheme_has(NAMING_REPLACE_STRICTLY)) + count = udev_replace_ifname_strict(buf); + else + count = udev_replace_chars(buf, "/"); + if (count > 0) + log_event_trace(event, token, + "Replaced %zu character(s) from network interface name, results to \"%s\"", + count, buf); +} + +static void udev_replace_chars_and_log(UdevEvent *event, UdevRuleToken *token, char *buf, const char *allow, const char *what) { + assert(event); + assert(token); + assert(buf); + assert(what); + + size_t count = udev_replace_chars(buf, allow); + if (count > 0) + log_event_trace(event, token, + "Replaced %zu character(s) from %s, results to \"%s\"", + count, what, buf); +} + static int udev_rule_apply_token_to_event( UdevRuleToken *token, sd_device *dev, @@ -1987,18 +2198,18 @@ static int udev_rule_apply_token_to_event( r = sd_device_get_action(dev, &a); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get uevent action type: %m"); + return log_event_error_errno(event, token, r, "Failed to get uevent action type: %m"); - return token_match_string(token, device_action_to_string(a)); + return token_match_string(event, token, device_action_to_string(a), /* log_result = */ true); } case TK_M_DEVPATH: { const char *val; r = sd_device_get_devpath(dev, &val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get devpath: %m"); + return log_event_error_errno(event, token, r, "Failed to get devpath: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_KERNEL: case TK_M_PARENTS_KERNEL: { @@ -2006,23 +2217,25 @@ static int udev_rule_apply_token_to_event( r = sd_device_get_sysname(dev, &val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get sysname: %m"); + return log_event_error_errno(event, token, r, "Failed to get sysname: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_DEVLINK: FOREACH_DEVICE_DEVLINK(dev, val) - if (token_match_string(token, strempty(startswith(val, "/dev/"))) == (token->op == OP_MATCH)) - return token->op == OP_MATCH; - return token->op == OP_NOMATCH; + if (token_match_string(event, token, strempty(startswith(val, "/dev/")), /* log_result = */ false) == (token->op == OP_MATCH)) + return log_event_result(event, token, token->op == OP_MATCH); + return log_event_result(event, token, token->op == OP_NOMATCH); + case TK_M_NAME: - return token_match_string(token, event->name); + return token_match_string(event, token, event->name, /* log_result = */ true); + case TK_M_ENV: { const char *val = NULL; (void) device_get_property_value_with_fallback(dev, token->data, event->worker ? event->worker->properties : NULL, &val); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_CONST: { const char *val, *k = token->data; @@ -2035,14 +2248,15 @@ static int udev_rule_apply_token_to_event( val = confidential_virtualization_to_string(detect_confidential_virtualization()); else assert_not_reached(); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_TAG: case TK_M_PARENTS_TAG: FOREACH_DEVICE_CURRENT_TAG(dev, val) - if (token_match_string(token, val) == (token->op == OP_MATCH)) - return token->op == OP_MATCH; - return token->op == OP_NOMATCH; + if (token_match_string(event, token, val, /* log_result = */ false) == (token->op == OP_MATCH)) + return log_event_result(event, token, token->op == OP_MATCH); + return log_event_result(event, token, token->op == OP_NOMATCH); + case TK_M_SUBSYSTEM: case TK_M_PARENTS_SUBSYSTEM: { const char *val; @@ -2051,9 +2265,9 @@ static int udev_rule_apply_token_to_event( if (r == -ENOENT) val = NULL; else if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get subsystem: %m"); + return log_event_error_errno(event, token, r, "Failed to get subsystem: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_DRIVER: case TK_M_PARENTS_DRIVER: { @@ -2063,41 +2277,35 @@ static int udev_rule_apply_token_to_event( if (r == -ENOENT) val = NULL; else if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get driver: %m"); + return log_event_error_errno(event, token, r, "Failed to get driver: %m"); - return token_match_string(token, val); + return token_match_string(event, token, val, /* log_result = */ true); } case TK_M_ATTR: case TK_M_PARENTS_ATTR: return token_match_attr(token, dev, event); + case TK_M_SYSCTL: { _cleanup_free_ char *value = NULL; char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "sysctl entry name", token->data, "SYSCTL", /* is_match = */ true); + if (!apply_format_attr(event, token, buf, sizeof(buf), "sysctl entry name")) return false; - } r = sysctl_read(sysctl_normalize(buf), &value); if (r < 0 && r != -ENOENT) - return log_event_error_errno(dev, token, r, "Failed to read sysctl '%s': %m", buf); + return log_event_error_errno(event, token, r, "Failed to read sysctl \"%s\": %m", buf); - return token_match_string(token, strstrip(value)); + return token_match_string(event, token, strstrip(value), /* log_result = */ true); } case TK_M_TEST: { mode_t mode = PTR_TO_MODE(token->data); char buf[UDEV_PATH_SIZE]; struct stat statbuf; - bool match, truncated; + bool match; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "file name", token->value, "TEST", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "file name")) return false; - } if (!path_is_absolute(buf) && udev_resolve_subsys_kernel(buf, buf, sizeof(buf), false) < 0) { @@ -2106,81 +2314,73 @@ static int udev_rule_apply_token_to_event( r = sd_device_get_syspath(dev, &val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to get syspath: %m"); + return log_event_error_errno(event, token, r, "Failed to get syspath: %m"); + bool truncated; strscpy_full(tmp, sizeof(tmp), buf, &truncated); assert(!truncated); strscpyl_full(buf, sizeof(buf), &truncated, val, "/", tmp, NULL); if (truncated) - return false; + return log_event_result(event, token, false); } r = attr_subst_subdir(buf); if (r == -ENOENT) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to test for the existence of '%s': %m", buf); + return log_event_error_errno(event, token, r, "Failed to test for the existence of \"%s\": %m", buf); - if (stat(buf, &statbuf) < 0) - return token->op == OP_NOMATCH; + if (stat(buf, &statbuf) < 0) { + if (errno != ENOENT) + log_event_warning_errno(event, token, errno, "Failed to stat \"%s\", ignoring: %m", buf); + return log_event_result(event, token, token->op == OP_NOMATCH); + } if (mode == MODE_INVALID) - return token->op == OP_MATCH; + return log_event_result(event, token, token->op == OP_MATCH); match = (statbuf.st_mode & mode) > 0; - return token->op == (match ? OP_MATCH : OP_NOMATCH); + return log_event_result(event, token, token->op == (match ? OP_MATCH : OP_NOMATCH)); } case TK_M_PROGRAM: { char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE]; - bool truncated; - size_t count; event->program_result = mfree(event->program_result); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "command", token->value, "PROGRAM", /* is_match = */ true); + + if (!apply_format_value(event, token, buf, sizeof(buf), "command")) return false; - } - log_event_debug(dev, token, "Running PROGRAM=\"%s\"", buf); + log_event_debug(event, token, "Running command \"%s\"", buf); r = udev_event_spawn(event, /* accept_failure = */ true, buf, result, sizeof(result), NULL); if (r != 0) { if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to execute \"%s\": %m", buf); + log_event_warning_errno(event, token, r, "Failed to execute \"%s\": %m", buf); else /* returned value is positive when program fails */ - log_event_debug(dev, token, "Command \"%s\" returned %d (error)", buf, r); - return token->op == OP_NOMATCH; + log_event_debug(event, token, "Command \"%s\" returned %d (error)", buf, r); + return log_event_result(event, token, token->op == OP_NOMATCH); } delete_trailing_chars(result, "\n"); - count = udev_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT); - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu character(s) in result of \"%s\"", - count, buf); + udev_replace_chars_and_log(event, token, result, UDEV_ALLOWED_CHARS_INPUT, "command output"); event->program_result = strdup(result); - return token->op == OP_MATCH; + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_FILE: { _cleanup_fclose_ FILE *f = NULL; char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "file name to be imported", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "file name to be imported")) return false; - } - log_event_debug(dev, token, "Importing properties from '%s'", buf); + log_event_debug(event, token, "Importing properties from \"%s\"", buf); f = fopen(buf, "re"); if (!f) { if (errno != ENOENT) - return log_event_error_errno(dev, token, errno, "Failed to open '%s': %m", buf); - return token->op == OP_NOMATCH; + return log_event_error_errno(event, token, errno, "Failed to open \"%s\": %m", buf); + return log_event_result(event, token, token->op == OP_NOMATCH); } for (;;) { @@ -2189,16 +2389,16 @@ static int udev_rule_apply_token_to_event( r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) { - log_event_debug_errno(dev, token, r, "Failed to read '%s', ignoring: %m", buf); - return token->op == OP_NOMATCH; + log_event_debug_errno(event, token, r, "Failed to read \"%s\", ignoring: %m", buf); + return log_event_result(event, token, token->op == OP_NOMATCH); } if (r == 0) - break; + return log_event_result(event, token, token->op == OP_MATCH); r = get_property_from_string(line, &key, &value); if (r < 0) { - log_event_debug_errno(dev, token, r, - "Failed to parse key and value from '%s', ignoring: %m", + log_event_debug_errno(event, token, r, + "Failed to parse key and value from \"%s\", ignoring: %m", line); continue; } @@ -2207,54 +2407,55 @@ static int udev_rule_apply_token_to_event( r = device_add_property(dev, key, value); if (r < 0) - return log_event_error_errno(dev, token, r, + return log_event_error_errno(event, token, r, "Failed to add property %s=%s: %m", key, value); + log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); } - return token->op == OP_MATCH; + assert_not_reached(); } case TK_M_IMPORT_PROGRAM: { _cleanup_strv_free_ char **lines = NULL; char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "command", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "command")) return false; - } - log_event_debug(dev, token, "Importing properties from results of '%s'", buf); + log_event_debug(event, token, "Importing properties from results of \"%s\"", buf); r = udev_event_spawn(event, /* accept_failure = */ true, buf, result, sizeof result, &truncated); if (r != 0) { if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to execute '%s', ignoring: %m", buf); + log_event_warning_errno(event, token, r, "Failed to execute \"%s\", ignoring: %m", buf); else /* returned value is positive when program fails */ - log_event_debug(dev, token, "Command \"%s\" returned %d (error), ignoring", buf, r); - return token->op == OP_NOMATCH; + log_event_debug(event, token, "Command \"%s\" returned %d (error), ignoring", buf, r); + return log_event_result(event, token, token->op == OP_NOMATCH); } if (truncated) { - bool found = false; + log_event_debug(event, token, "Result of \"%s\" is too long and truncated, ignoring the last line of the result.", buf); /* Drop the last line. */ + bool found = false; for (char *p = PTR_SUB1(buf + strlen(buf), buf); p; p = PTR_SUB1(p, buf)) if (strchr(NEWLINE, *p)) { *p = '\0'; found = true; - } else if (found) break; + } + if (!found) + buf[0] = '\0'; } r = strv_split_newlines_full(&lines, result, EXTRACT_RETAIN_ESCAPE); if (r == -ENOMEM) return log_oom(); if (r < 0) { - log_event_warning_errno(dev, token, r, + log_event_warning_errno(event, token, r, "Failed to extract lines from result of command \"%s\", ignoring: %m", buf); - return false; + return log_event_result(event, token, false); } STRV_FOREACH(line, lines) { @@ -2262,8 +2463,8 @@ static int udev_rule_apply_token_to_event( r = get_property_from_string(*line, &key, &value); if (r < 0) { - log_event_debug_errno(dev, token, r, - "Failed to parse key and value from '%s', ignoring: %m", + log_event_debug_errno(event, token, r, + "Failed to parse key and value from \"%s\", ignoring: %m", *line); continue; } @@ -2272,123 +2473,168 @@ static int udev_rule_apply_token_to_event( r = device_add_property(dev, key, value); if (r < 0) - return log_event_error_errno(dev, token, r, + return log_event_error_errno(event, token, r, "Failed to add property %s=%s: %m", key, value); + log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); } - return token->op == OP_MATCH; + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_BUILTIN: { UdevBuiltinCommand cmd = PTR_TO_UDEV_BUILTIN_CMD(token->data); assert(cmd >= 0 && cmd < _UDEV_BUILTIN_MAX); unsigned mask = 1U << (int) cmd; char buf[UDEV_LINE_SIZE]; - bool truncated; if (udev_builtin_run_once(cmd)) { /* check if we ran already */ if (event->builtin_run & mask) { - log_event_debug(dev, token, "Skipping builtin '%s' in IMPORT key", + log_event_debug(event, token, "Builtin command \"%s\" has already run, skipping.", udev_builtin_name(cmd)); /* return the result from earlier run */ - return token->op == (event->builtin_ret & mask ? OP_NOMATCH : OP_MATCH); + return log_event_result(event, token, token->op == (event->builtin_ret & mask ? OP_NOMATCH : OP_MATCH)); } /* mark as ran */ event->builtin_run |= mask; } - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "builtin command", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "builtin command")) return false; - } - log_event_debug(dev, token, "Importing properties from results of builtin command '%s'", buf); + log_event_debug(event, token, "Importing properties from results of builtin command \"%s\".", buf); r = udev_builtin_run(event, cmd, buf); if (r < 0) { /* remember failure */ - log_event_debug_errno(dev, token, r, "Failed to run builtin '%s': %m", buf); + log_event_debug_errno(event, token, r, "Failed to run builtin \"%s\": %m", buf); event->builtin_ret |= mask; } - return token->op == (r >= 0 ? OP_MATCH : OP_NOMATCH); + return log_event_result(event, token, token->op == (r >= 0 ? OP_MATCH : OP_NOMATCH)); } case TK_M_IMPORT_DB: { const char *val; if (!event->dev_db_clone) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); r = sd_device_get_property_value(event->dev_db_clone, token->value, &val); if (r == -ENOENT) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); if (r < 0) - return log_event_error_errno(dev, token, r, - "Failed to get property '%s' from database: %m", + return log_event_error_errno(event, token, r, + "Failed to get property \"%s\" from database: %m", token->value); r = device_add_property(dev, token->value, val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to add property '%s=%s': %m", + return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", token->value, val); - return token->op == OP_MATCH; + log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); + + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_CMDLINE: { _cleanup_free_ char *value = NULL; r = proc_cmdline_get_key(token->value, PROC_CMDLINE_VALUE_OPTIONAL|PROC_CMDLINE_IGNORE_EFI_OPTIONS, &value); if (r < 0) - return log_event_error_errno(dev, token, r, - "Failed to read '%s' option from /proc/cmdline: %m", + return log_event_error_errno(event, token, r, + "Failed to read \"%s\" option from /proc/cmdline: %m", token->value); if (r == 0) - return token->op == OP_NOMATCH; + return log_event_result(event, token, token->op == OP_NOMATCH); - r = device_add_property(dev, token->value, value ?: "1"); + const char *val = value ?: "1"; + r = device_add_property(dev, token->value, val); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to add property '%s=%s': %m", - token->value, value ?: "1"); - return token->op == OP_MATCH; + return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", + token->value, val); + log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); + + return log_event_result(event, token, token->op == OP_MATCH); } case TK_M_IMPORT_PARENT: { char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "property name", token->value, "IMPORT", /* is_match = */ true); + if (!apply_format_value(event, token, buf, sizeof(buf), "property name")) return false; - } - r = import_parent_into_properties(dev, buf); + sd_device *parent; + r = sd_device_get_parent(dev, &parent); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) + return log_event_result(event, token, token->op == OP_NOMATCH); if (r < 0) - return log_event_error_errno(dev, token, r, - "Failed to import properties '%s' from parent: %m", - buf); - return token->op == (r > 0 ? OP_MATCH : OP_NOMATCH); + return log_event_error_errno(event, token, r, "Failed to get parent device: %m"); + + bool have = false; + FOREACH_DEVICE_PROPERTY(parent, key, val) { + if (fnmatch(buf, key, 0) != 0) + continue; + + r = device_add_property(dev, key, val); + if (r < 0) + return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", key, val); + log_event_trace(event, token, "Imported property \"%s=%s\".", key, val); + have = true; + } + + return log_event_result(event, token, token->op == (have ? OP_MATCH : OP_NOMATCH)); } case TK_M_RESULT: - return token_match_string(token, event->program_result); + return token_match_string(event, token, event->program_result, /* log_result = */ true); + + case TK_A_OPTIONS_DUMP: { + log_event_info(event, token, "Dumping current state:"); + + if (event->event_mode == EVENT_UDEV_WORKER) { + _cleanup_(memstream_done) MemStream m = {}; + FILE *f = memstream_init(&m); + if (!f) + return log_oom(); + + dump_event(event, f); + + _cleanup_free_ char *buf = NULL; + r = memstream_finalize(&m, &buf, NULL); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to finalize memory stream, ignoring: %m"); + else + log_info("%s", buf); + } else { + puts("============================"); + dump_event(event, NULL); + puts("============================"); + } + + log_event_info(event, token, "DONE"); + return true; + } case TK_A_OPTIONS_STRING_ESCAPE_NONE: event->esc = ESCAPE_NONE; - break; + return log_event_done(event, token); + case TK_A_OPTIONS_STRING_ESCAPE_REPLACE: event->esc = ESCAPE_REPLACE; - break; + return log_event_done(event, token); + case TK_A_OPTIONS_DB_PERSIST: device_set_db_persist(dev); - break; + return log_event_done(event, token); + case TK_A_OPTIONS_INOTIFY_WATCH: if (event->inotify_watch_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->inotify_watch_final = true; event->inotify_watch = token->data; - break; + return log_event_done(event, token); + case TK_A_OPTIONS_DEVLINK_PRIORITY: device_set_devlink_priority(dev, PTR_TO_INT(token->data)); - break; + return log_event_done(event, token); + case TK_A_OPTIONS_LOG_LEVEL: { int level = PTR_TO_INT(token->data); @@ -2400,7 +2646,7 @@ static int udev_rule_apply_token_to_event( else { _cleanup_free_ char *level_str = NULL; (void) log_level_to_string_alloc(level, &level_str); - log_event_debug(dev, token, "Running in test mode, skipping changing maximum log level to %s.", strna(level_str)); + log_event_debug(event, token, "Running in test mode, skipping changing maximum log level to %s.", strna(level_str)); } if (level == LOG_DEBUG && !event->log_level_was_debug) { @@ -2410,120 +2656,130 @@ static int udev_rule_apply_token_to_event( event->log_level_was_debug = true; } - break; + return log_event_done(event, token); } case TK_A_OWNER: { char owner[UDEV_NAME_SIZE]; const char *ow = owner; - bool truncated; if (event->owner_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->owner_final = true; - (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "user name", token->value, "OWNER", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, owner, sizeof(owner), "user name")) + return true; - r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); - if (r < 0) - log_unknown_owner(dev, token->rule_line, r, "user", owner); - else - log_event_debug(dev, token, "OWNER %s(%u)", owner, event->uid); - break; + uid_t uid; + r = get_user_creds( + &ow, + &uid, + /* ret_gid = */ NULL, + /* ret_home = */ NULL, + /* ret_shell = */ NULL, + USER_CREDS_ALLOW_MISSING); + if (r == -ESRCH) + log_event_error_errno(event, token, r, "Unknown user \"%s\", ignoring.", owner); + else if (r < 0) + log_event_error_errno(event, token, r, "Failed to resolve user \"%s\", ignoring: %m", owner); + else if (!uid_is_system(uid)) + log_event_error(event, token, "User \"%s\" is not a system user (UID="UID_FMT"), ignoring.", owner, uid); + else { + event->uid = uid; + log_event_debug(event, token, "Set owner: %s(%u)", owner, event->uid); + } + return true; } case TK_A_GROUP: { char group[UDEV_NAME_SIZE]; const char *gr = group; - bool truncated; if (event->group_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->group_final = true; - (void) udev_event_apply_format(event, token->value, group, sizeof(group), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "group name", token->value, "GROUP", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, group, sizeof(group), "group name")) + return true; - r = get_group_creds(&gr, &event->gid, USER_CREDS_ALLOW_MISSING); - if (r < 0) - log_unknown_owner(dev, token->rule_line, r, "group", group); - else - log_event_debug(dev, token, "GROUP %s(%u)", group, event->gid); - break; + gid_t gid; + r = get_group_creds(&gr, &gid, USER_CREDS_ALLOW_MISSING); + if (r == -ESRCH) + log_event_error_errno(event, token, r, "Unknown group \"%s\", ignoring.", group); + else if (r < 0) + log_event_error_errno(event, token, r, "Failed to resolve group \"%s\", ignoring: %m", group); + else if (!gid_is_system(gid)) + log_event_error(event, token, "Group \"%s\" is not a system group (GID="GID_FMT"), ignoring.", group, gid); + else { + event->gid = gid; + log_event_debug(event, token, "Set group: %s(%u)", group, event->gid); + } + return true; } case TK_A_MODE: { char mode_str[UDEV_NAME_SIZE]; - bool truncated; if (event->mode_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->mode_final = true; - (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "mode", token->value, "MODE", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, mode_str, sizeof(mode_str), "mode")) + return true; r = parse_mode(mode_str, &event->mode); if (r < 0) - log_event_error_errno(dev, token, r, "Failed to parse mode '%s', ignoring: %m", mode_str); + log_event_error_errno(event, token, r, "Failed to parse mode \"%s\", ignoring: %m", mode_str); else - log_event_debug(dev, token, "MODE %#o", event->mode); - break; + log_event_debug(event, token, "Set mode: %#o", event->mode); + return true; } case TK_A_OWNER_ID: if (event->owner_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->owner_final = true; - if (!token->data) - break; - event->uid = PTR_TO_UID(token->data); - log_event_debug(dev, token, "OWNER %u", event->uid); - break; + + event->uid = PTR_TO_UID(ASSERT_PTR(token->data)); + log_event_debug(event, token, "Set owner ID: %u", event->uid); + return true; + case TK_A_GROUP_ID: if (event->group_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->group_final = true; - if (!token->data) - break; - event->gid = PTR_TO_GID(token->data); - log_event_debug(dev, token, "GROUP %u", event->gid); - break; + + event->gid = PTR_TO_GID(ASSERT_PTR(token->data)); + log_event_debug(event, token, "Set group ID: %u", event->gid); + return true; + case TK_A_MODE_ID: if (event->mode_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->mode_final = true; - if (!token->data) - break; - event->mode = PTR_TO_MODE(token->data); - log_event_debug(dev, token, "MODE %#o", event->mode); - break; + + event->mode = PTR_TO_MODE(ASSERT_PTR(token->data)); + log_event_debug(event, token, "Set mode: %#o", event->mode); + return true; + case TK_A_SECLABEL: { _cleanup_free_ char *name = NULL, *label = NULL; char label_str[UDEV_LINE_SIZE] = {}; - bool truncated; name = strdup(token->data); if (!name) return log_oom(); - (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "security label", token->value, "SECLABEL", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, label_str, sizeof(label_str), "security label")) + return true; if (!isempty(label_str)) label = strdup(label_str); @@ -2533,163 +2789,141 @@ static int udev_rule_apply_token_to_event( return log_oom(); if (token->op == OP_ASSIGN) - ordered_hashmap_clear_free_free(event->seclabel_list); + ordered_hashmap_clear(event->seclabel_list); - r = ordered_hashmap_ensure_put(&event->seclabel_list, NULL, name, label); + r = ordered_hashmap_ensure_put(&event->seclabel_list, &trivial_hash_ops_free_free, name, label); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to store SECLABEL{%s}='%s': %m", name, label); + return log_event_error_errno(event, token, r, "Failed to store security label \"%s=%s\": %m", name, label); - log_event_debug(dev, token, "SECLABEL{%s}='%s'", name, label); + log_event_debug(event, token, "Set security label: %s=%s", name, label); TAKE_PTR(name); TAKE_PTR(label); - break; + return true; } case TK_A_ENV: { const char *val, *name = token->data; char value_new[UDEV_NAME_SIZE], *p = value_new; - size_t count, l = sizeof(value_new); - bool truncated; + size_t l = sizeof(value_new); if (isempty(token->value)) { if (token->op == OP_ADD) - break; + return log_event_done(event, token); + r = device_add_property(dev, name, NULL); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to remove property '%s': %m", name); - break; + return log_event_error_errno(event, token, r, "Failed to remove property \"%s\": %m", name); + log_event_trace(event, token, "Removed property \"%s\".", name); + return true; } if (token->op == OP_ADD && device_get_property_value_with_fallback(dev, name, event->worker ? event->worker->properties : NULL, &val) >= 0) { + bool truncated; l = strpcpyl_full(&p, l, &truncated, val, " ", NULL); if (truncated) { - log_event_warning(dev, token, - "The buffer for the property '%s' is full, " - "refusing to append the new value '%s'.", name, token->value); - break; + log_event_warning(event, token, + "The buffer for the property is full, refusing to append new property \"%s=%s\".", + name, token->value); + return true; } } - (void) udev_event_apply_format(event, token->value, p, l, false, &truncated); - if (truncated) { - _cleanup_free_ char *key_with_name = strjoin("ENV{", name, "}"); - log_event_truncated(dev, token, "property value", token->value, - key_with_name ?: "ENV", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, p, l, "property value")) + return true; - if (event->esc == ESCAPE_REPLACE) { - count = udev_replace_chars(p, NULL); - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu slash(es) from result of ENV{%s}%s=\"%s\"", - count, name, token->op == OP_ADD ? "+" : "", token->value); - } + if (event->esc == ESCAPE_REPLACE) + udev_replace_chars_and_log(event, token, p, /* allow = */ NULL, "property value"); r = device_add_property(dev, name, value_new); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to add property '%s=%s': %m", name, value_new); - break; + return log_event_error_errno(event, token, r, "Failed to set property \"%s=%s\": %m", name, value_new); + log_event_trace(event, token, "Set property \"%s=%s\".", name, value_new); + return true; } case TK_A_TAG: { char buf[UDEV_PATH_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "tag name", token->value, "TAG", /* is_match = */ false); - break; + if (!apply_format_value(event, token, buf, sizeof(buf), "tag name")) + return true; + + if (token->op == OP_REMOVE) { + device_remove_tag(dev, buf); + log_event_trace(event, token, "Removed tag \"%s\".", buf); + return true; } + assert(IN_SET(token->op, OP_ASSIGN, OP_ADD)); + if (token->op == OP_ASSIGN) device_cleanup_tags(dev); - if (token->op == OP_REMOVE) - device_remove_tag(dev, buf); - else { - r = device_add_tag(dev, buf, true); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to add tag '%s', ignoring: %m", buf); - } - break; + r = device_add_tag(dev, buf, /* both = */ true); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to %s tag \"%s\", ignoring: %m", + token->op == OP_ASSIGN ? "set" : "add", buf); + else + log_event_trace(event, token, "%s tag \"%s\".", token->op == OP_ASSIGN ? "Set" : "Added", buf); + return true; } case TK_A_NAME: { char buf[UDEV_PATH_SIZE]; - bool truncated; - size_t count; if (event->name_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->name_final = true; if (sd_device_get_ifindex(dev, NULL) < 0) { - log_event_error(dev, token, - "Only network interfaces can be renamed, ignoring NAME=\"%s\".", - token->value); - break; + log_event_warning(event, token, "Only network interfaces can be renamed, ignoring."); + return true; } - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "network interface name", token->value, "NAME", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, buf, sizeof(buf), "network interface name")) + return true; + + if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) + udev_replace_ifname(event, token, buf); - if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) { - if (naming_scheme_has(NAMING_REPLACE_STRICTLY)) - count = udev_replace_ifname(buf); - else - count = udev_replace_chars(buf, "/"); - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu character(s) from result of NAME=\"%s\"", - count, token->value); - } r = free_and_strdup_warn(&event->name, buf); if (r < 0) return r; - log_event_debug(dev, token, "NAME '%s'", event->name); - break; + log_event_debug(event, token, "Set network interface name: %s", event->name); + return true; } case TK_A_DEVLINK: { char buf[UDEV_PATH_SIZE]; - bool truncated; - size_t count; if (event->devlink_final) - break; - if (sd_device_get_devnum(dev, NULL) < 0) - break; + return log_event_final_set(event, token); + + if (sd_device_get_devnum(dev, NULL) < 0) { + log_event_debug(event, token, "Device does not have device node, ignoring to manage device node symlink."); + return true; + } + if (token->op == OP_ASSIGN_FINAL) event->devlink_final = true; + if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) device_cleanup_devlinks(dev); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), - /* replace_whitespace = */ event->esc != ESCAPE_NONE, &truncated); - if (truncated) { - log_event_truncated(dev, token, "symbolic link path", token->value, "SYMLINK", /* is_match = */ false); - break; - } + if (!apply_format_full(event, token, token->value, buf, sizeof(buf), + /* replace_whitespace = */ event->esc != ESCAPE_NONE, + "symbolic link path")) + return true; /* By default or string_escape=none, allow multiple symlinks separated by spaces. */ if (event->esc == ESCAPE_UNSET) - count = udev_replace_chars(buf, /* allow = */ "/ "); + udev_replace_chars_and_log(event, token, buf, /* allow = */ "/ ", "device node symlink"); else if (event->esc == ESCAPE_REPLACE) - count = udev_replace_chars(buf, /* allow = */ "/"); - else - count = 0; - if (count > 0) - log_event_debug(dev, token, - "Replaced %zu character(s) from result of SYMLINK=\"%s\"", - count, token->value); + udev_replace_chars_and_log(event, token, buf, /* allow = */ "/", "device node symlink"); for (const char *p = buf;;) { _cleanup_free_ char *path = NULL; @@ -2698,166 +2932,219 @@ static int udev_rule_apply_token_to_event( if (r == -ENOMEM) return log_oom(); if (r < 0) { - log_warning_errno(r, "Failed to extract first path in SYMLINK=, ignoring: %m"); - break; + log_warning_errno(r, "Failed to extract first path in device node symlinks, ignoring: %m"); + return true; } if (r == 0) - break; + return true; if (token->op == OP_REMOVE) { r = device_remove_devlink(dev, path); if (r == -ENOMEM) return log_oom(); if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to remove devlink '%s', ignoring: %m", path); + log_event_warning_errno(event, token, r, "Failed to remove device node symlink \"%s\", ignoring: %m", path); else if (r > 0) - log_event_debug(dev, token, "Dropped SYMLINK '%s'", path); + log_event_debug(event, token, "Removed device node symlink \"%s\"", path); } else { r = device_add_devlink(dev, path); if (r == -ENOMEM) return log_oom(); if (r < 0) - log_event_warning_errno(dev, token, r, "Failed to add devlink '%s', ignoring: %m", path); + log_event_warning_errno(event, token, r, "Failed to add device node symlink \"%s\", ignoring: %m", path); else if (r > 0) - log_event_debug(dev, token, "Added SYMLINK '%s'", path); + log_event_debug(event, token, "Added device node symlink \"%s\".", path); } } - break; + assert_not_reached(); } case TK_A_ATTR: { char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE]; - const char *val, *key_name = token->data; - bool truncated; + const char *key = token->data; + + /* First, try to resolve "[<SUBSYSTEM>/<KERNEL>]<attribute>" format. */ + r = udev_resolve_subsys_kernel(key, buf, sizeof(buf), false); + if (r == -ENOMEM) + return log_oom(); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) { + log_event_warning_errno(event, token, r, "Failed to resolve sysfs attribute \"%s\", ignoring: %m", key); + return true; + } + if (r < 0) { + /* If not, make the path to sysfs attribute absolute, to make '*' resolvable by attr_subst_subdir(). */ + const char *syspath; + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) + return log_event_error_errno(event, token, r, "Failed to get syspath: %m"); - if (udev_resolve_subsys_kernel(key_name, buf, sizeof(buf), false) < 0 && - sd_device_get_syspath(dev, &val) >= 0) { - strscpyl_full(buf, sizeof(buf), &truncated, val, "/", key_name, NULL); + bool truncated; + strscpyl_full(buf, sizeof(buf), &truncated, syspath, "/", key, NULL); if (truncated) { - log_event_warning(dev, token, - "The path to the attribute '%s/%s' is too long, refusing to set the attribute.", - val, key_name); - break; + log_event_warning(event, token, + "The path to the attribute \"%s/%s\" is too long, refusing to set the attribute.", + syspath, key); + return true; + } + + /* Resolve '*' in the path. */ + r = attr_subst_subdir(buf); + if (r < 0) { + log_event_error_errno(event, token, r, "Could not find file matches \"%s\", ignoring: %m", buf); + return true; } } - r = attr_subst_subdir(buf); + /* Then, make the path relative again. This also checks if the path being inside of the sysfs. */ + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = -EBADF; + r = device_chase(dev, buf, /* flags = */ 0, &resolved, &fd); if (r < 0) { - log_event_error_errno(dev, token, r, "Could not find file matches '%s', ignoring: %m", buf); - break; - } - (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "attribute value", token->value, "ATTR", /* is_match = */ false); - break; + log_event_error_errno(event, token, r, "Could not chase sysfs attribute \"%s\", ignoring: %m", buf); + return true; } + /* Apply formatting to the value. */ + if (!apply_format_value(event, token, value, sizeof(value), "attribute value")) + return true; + if (EVENT_MODE_DESTRUCTIVE(event)) { - log_event_debug(dev, token, "Writing ATTR{'%s'}=\"%s\".", buf, value); - r = write_string_file(buf, value, - WRITE_STRING_FILE_VERIFY_ON_FAILURE | - WRITE_STRING_FILE_DISABLE_BUFFER | - WRITE_STRING_FILE_AVOID_NEWLINE | - WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE); + log_event_debug(event, token, "Writing \"%s\" to sysfs attribute \"%s\".", value, resolved); + r = sd_device_set_sysattr_value(dev, resolved, value); if (r < 0) - log_event_error_errno(dev, token, r, "Failed to write ATTR{%s}=\"%s\", ignoring: %m", buf, value); - } else - log_event_debug(dev, token, "Running in test mode, skipping writing ATTR{%s}=\"%s\".", buf, value); + log_event_error_errno(event, token, r, "Failed to write \"%s\" to sysfs attribute \"%s\", ignoring: %m", value, resolved); + else { + event_cache_written_sysattr(event, resolved, value); + log_event_done(event, token); + } + } else { + log_event_debug(event, token, "Running in test mode, skipping writing \"%s\" to sysfs attribute \"%s\".", value, resolved); - break; + /* We assume the attribute is writable if the path points to a regular file, and cache + * the value to make it shown by OPTIONS="dump" or obtained by later ATTR match token. */ + r = fd_verify_regular(fd); + if (r < 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) + log_event_error_errno(event, token, r, "Failed to verify sysfs attribute \"%s\" is a regular file: %m", resolved); + else { + event_cache_written_sysattr(event, resolved, value); + + _cleanup_free_ char *copied = strdup(value); + if (!copied) + return log_oom(); + + r = device_cache_sysattr_value(dev, resolved, value, /* error = */ 0); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to cache sysfs attribute \"%s\", ignoring: %m", resolved); + else if (r > 0) { + TAKE_PTR(resolved); + TAKE_PTR(copied); + } + } + } + return true; } case TK_A_SYSCTL: { char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE]; - bool truncated; - (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "sysctl entry name", token->data, "SYSCTL", /* is_match = */ false); - break; - } + if (!apply_format_attr(event, token, buf, sizeof(buf), "sysctl entry name")) + return true; - (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated); - if (truncated) { - _cleanup_free_ char *key_with_name = strjoin("SYSCTL{", buf, "}"); - log_event_truncated(dev, token, "sysctl value", token->value, - key_with_name ?: "SYSCTL", /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, value, sizeof(value), "sysctl value")) + return true; sysctl_normalize(buf); if (EVENT_MODE_DESTRUCTIVE(event)) { - log_event_debug(dev, token, "Writing SYSCTL{%s}=\"%s\".", buf, value); + log_event_debug(event, token, "Writing \"%s\" to sysctl entry \"%s\".", value, buf); r = sysctl_write(buf, value); if (r < 0) - log_event_error_errno(dev, token, r, "Failed to write SYSCTL{%s}=\"%s\", ignoring: %m", buf, value); - } else - log_event_debug(dev, token, "Running in test mode, skipping writing SYSCTL{%s}=\"%s\".", buf, value); + log_event_error_errno(event, token, r, "Failed to write \"%s\" to sysctl entry \"%s\", ignoring: %m", value, buf); + else { + event_cache_written_sysctl(event, buf, value); + log_event_done(event, token); + } + } else { + log_event_debug(event, token, "Running in test mode, skipping writing \"%s\" to sysctl entry \"%s\".", value, buf); - break; + _cleanup_free_ char *path = path_join("/proc/sys/", buf); + if (!path) + return log_oom(); + + r = verify_regular_at(AT_FDCWD, path, /* follow = */ true); + if (r < 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) + log_event_error_errno(event, token, r, "Failed to verify sysctl entry \"%s\" is a regular file: %m", buf); + else + event_cache_written_sysctl(event, buf, value); + } + return true; } case TK_A_RUN_BUILTIN: case TK_A_RUN_PROGRAM: { _cleanup_free_ char *cmd = NULL; char buf[UDEV_LINE_SIZE]; - bool truncated; if (event->run_final) - break; + return log_event_final_set(event, token); + if (token->op == OP_ASSIGN_FINAL) event->run_final = true; if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) - ordered_hashmap_clear_free_key(event->run_list); + ordered_hashmap_clear(event->run_list); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); - if (truncated) { - log_event_truncated(dev, token, "command", token->value, - token->type == TK_A_RUN_BUILTIN ? "RUN{builtin}" : "RUN{program}", - /* is_match = */ false); - break; - } + if (!apply_format_value(event, token, buf, sizeof(buf), "command")) + return true; cmd = strdup(buf); if (!cmd) return log_oom(); - r = ordered_hashmap_ensure_put(&event->run_list, NULL, cmd, token->data); - if (r == -ENOMEM) - return log_oom(); + r = ordered_hashmap_ensure_put(&event->run_list, &trivial_hash_ops_free, cmd, token->data); if (r < 0) - return log_event_error_errno(dev, token, r, "Failed to store command '%s': %m", cmd); + return log_event_error_errno(event, token, r, "Failed to store command \"%s\": %m", cmd); + log_event_debug(event, token, "Set command: %s", cmd); TAKE_PTR(cmd); - - log_event_debug(dev, token, "RUN '%s'", token->value); - break; + return true; } case TK_A_OPTIONS_STATIC_NODE: /* do nothing for events. */ - break; + return true; + default: assert_not_reached(); } - return true; -} - -static bool token_is_for_parents(UdevRuleToken *token) { - return token->type >= TK_M_PARENTS_KERNEL && token->type <= TK_M_PARENTS_TAG; + assert_not_reached(); } static int udev_rule_apply_parent_token_to_event(UdevRuleToken *head_token, UdevEvent *event) { int r; assert(head_token); + assert(token_is_for_parents(head_token)); assert(event); + UdevRuleLine *line = head_token->rule_line; + if (event->trace) { + _cleanup_free_ char *joined = NULL; + + LIST_FOREACH(tokens, token, head_token) + if (token_is_for_parents(token)) + (void) strextend_with_separator(&joined, ", ", token->token_str); + else + break; + + log_event_line(event, line, "Checking conditions for parent devices: %s", strna(joined)); + } + event->dev_parent = ASSERT_PTR(event->dev); for (;;) { LIST_FOREACH(tokens, token, head_token) { - if (!token_is_for_parents(token)) - return true; /* All parent tokens match. */ + if (!token_is_for_parents(token)) { + r = 1; /* avoid false maybe-uninitialized warning */ + break; /* All parent tokens match. */ + } r = udev_rule_apply_token_to_event(token, event->dev_parent, event); if (r < 0) @@ -2865,12 +3152,18 @@ static int udev_rule_apply_parent_token_to_event(UdevRuleToken *head_token, Udev if (r == 0) break; } - if (r > 0) - /* All parent tokens match, and no more token (except for GOTO) in the line. */ + if (r > 0) { + if (event->trace) { + const char *s = NULL; + (void) sd_device_get_syspath(event->dev_parent, &s); + log_event_line(event, line, "Parent device \"%s\" passed all parent conditions.", strna(s)); + } return true; + } if (sd_device_get_parent(event->dev_parent, &event->dev_parent) < 0) { event->dev_parent = NULL; + log_event_line(event, line, "No parent device passed parent conditions."); return false; } } @@ -2927,8 +3220,10 @@ static int udev_rule_apply_line_to_event( return r; } - if (line->goto_line) + if (line->goto_line) { + log_event_line(event, line, "GOTO=%s", strna(line->goto_label)); *next_line = line->goto_line; /* update next_line only when the line has GOTO token. */ + } return 0; } diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 67d7e5b178..62dac5ba73 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -14,7 +14,7 @@ int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos, bool * int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret); unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file); UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing); -int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing); +int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra); UdevRules* udev_rules_free(UdevRules *rules); DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRules*, udev_rules_free); #define udev_rules_free_and_replace(a, b) free_and_replace_full(a, b, udev_rules_free) diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c index 8faadb8bcf..41f7fe08f9 100644 --- a/src/udev/udev-varlink.c +++ b/src/udev/udev-varlink.c @@ -11,10 +11,13 @@ #define UDEV_VARLINK_ADDRESS "/run/udev/io.systemd.Udev" static int vl_method_reload(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; log_debug("Received io.systemd.service.Reload()"); manager_reload(userdata, /* force = */ true); @@ -40,6 +43,26 @@ static int vl_method_set_log_level(sd_varlink *link, sd_json_variant *parameters return sd_varlink_reply(link, NULL); } +static int vl_method_set_trace(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + bool enable; + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "enable", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, 0, SD_JSON_MANDATORY }, + {} + }; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &enable); + if (r != 0) + return r; + + log_debug("Received io.systemd.service.SetTrace(%s)", yes_no(enable)); + manager_set_trace(userdata, enable); + return sd_varlink_reply(link, NULL); +} + static int vl_method_set_children_max(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { unsigned n; int r; @@ -87,8 +110,9 @@ static int vl_method_start_stop_exec_queue(sd_varlink *link, sd_json_variant *pa assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; r = sd_varlink_get_current_method(link, &method); if (r < 0) @@ -100,10 +124,13 @@ static int vl_method_start_stop_exec_queue(sd_varlink *link, sd_json_variant *pa } static int vl_method_exit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + assert(link); - if (sd_json_variant_elements(parameters) > 0) - return sd_varlink_error_invalid_parameter(link, parameters); + r = sd_varlink_dispatch(link, parameters, /* dispatch_table = */ NULL, /* userdata = */ NULL); + if (r != 0) + return r; /* Refuse further connections. */ _unused_ _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *v = sd_varlink_ref(link); @@ -149,14 +176,16 @@ int manager_start_varlink_server(Manager *manager) { r = sd_varlink_server_bind_method_many( v, - "io.systemd.service.Ping", varlink_method_ping, - "io.systemd.service.Reload", vl_method_reload, - "io.systemd.service.SetLogLevel", vl_method_set_log_level, - "io.systemd.Udev.SetChildrenMax", vl_method_set_children_max, - "io.systemd.Udev.SetEnvironment", vl_method_set_environment, - "io.systemd.Udev.StartExecQueue", vl_method_start_stop_exec_queue, - "io.systemd.Udev.StopExecQueue", vl_method_start_stop_exec_queue, - "io.systemd.Udev.Exit", vl_method_exit); + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.Reload", vl_method_reload, + "io.systemd.service.SetLogLevel", vl_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment, + "io.systemd.Udev.SetTrace", vl_method_set_trace, + "io.systemd.Udev.SetChildrenMax", vl_method_set_children_max, + "io.systemd.Udev.SetEnvironment", vl_method_set_environment, + "io.systemd.Udev.StartExecQueue", vl_method_start_stop_exec_queue, + "io.systemd.Udev.StopExecQueue", vl_method_start_stop_exec_queue, + "io.systemd.Udev.Exit", vl_method_exit); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); diff --git a/src/udev/udev-worker.c b/src/udev/udev-worker.c index 52b2203677..5c7583a9ff 100644 --- a/src/udev/udev-worker.c +++ b/src/udev/udev-worker.c @@ -182,6 +182,7 @@ static int worker_process_device(UdevWorker *worker, sd_device *dev) { udev_event = udev_event_new(dev, worker, EVENT_UDEV_WORKER); if (!udev_event) return -ENOMEM; + udev_event->trace = worker->config.trace; /* If this is a block device and the device is locked currently via the BSD advisory locks, * someone else is using it exclusively. We don't run our udev rules now to not interfere. diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c new file mode 100644 index 0000000000..2d7e86994d --- /dev/null +++ b/src/udev/udevadm-cat.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <getopt.h> + +#include "log.h" +#include "parse-argument.h" +#include "pretty-print.h" +#include "static-destruct.h" +#include "strv.h" +#include "udevadm.h" +#include "udevadm-util.h" + +static char *arg_root = NULL; +static CatFlags arg_cat_flags = 0; +static bool arg_config = false; + +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("udevadm", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s cat [OPTIONS] [FILE...]\n" + "\n%sShow udev rules files.%s\n\n" + " -h --help Show this help\n" + " -V --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --tldr Skip comments and empty lines\n" + " --config Show udev.conf rather than udev rules files\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_ROOT = 0x100, + ARG_TLDR, + ARG_CONFIG, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "tldr", no_argument, NULL, ARG_TLDR }, + { "config", no_argument, NULL, ARG_CONFIG }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + switch (c) { + case 'h': + return help(); + case 'V': + return print_version(); + case ARG_ROOT: + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; + break; + case ARG_TLDR: + arg_cat_flags = CAT_TLDR; + break; + case ARG_CONFIG: + arg_config = true; + break; + case '?': + return -EINVAL; + default: + assert_not_reached(); + } + + if (arg_config && optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Combination of --config and FILEs is not supported."); + + return 1; +} + +int cat_main(int argc, char *argv[], void *userdata) { + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (arg_config) + return conf_files_cat(arg_root, "udev/udev.conf", arg_cat_flags); + + _cleanup_strv_free_ char **files = NULL; + r = search_rules_files(strv_skip(argv, optind), arg_root, &files); + if (r < 0) + return r; + + /* udev rules file does not support dropin configs. So, we can safely pass multiple files as dropins. */ + return cat_files(/* file = */ NULL, /* dropins = */ files, arg_cat_flags); +} diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 3ccc621357..1fa5a42018 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -10,6 +10,7 @@ #include "creds-util.h" #include "errno-util.h" +#include "parse-argument.h" #include "parse-util.h" #include "process-util.h" #include "static-destruct.h" @@ -30,6 +31,7 @@ static bool arg_exit = false; static int arg_max_children = -1; static int arg_log_level = -1; static int arg_start_exec_queue = -1; +static int arg_trace = -1; static bool arg_load_credentials = false; STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep); @@ -42,7 +44,8 @@ static bool arg_has_control_commands(void) { arg_reload || !strv_isempty(arg_env) || arg_max_children >= 0 || - arg_ping; + arg_ping || + arg_trace >= 0; } static int help(void) { @@ -58,6 +61,7 @@ static int help(void) { " -p --property=KEY=VALUE Set a global property for all events\n" " -m --children-max=N Maximum number of children\n" " --ping Wait for udev to respond to a ping message\n" + " --trace=BOOL Enable/disable trace logging\n" " -t --timeout=SECONDS Maximum time to block for a reply\n" " --load-credentials Load udev rules from credentials\n", program_invocation_short_name); @@ -68,6 +72,7 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { enum { ARG_PING = 0x100, + ARG_TRACE, ARG_LOAD_CREDENTIALS, }; @@ -83,6 +88,7 @@ static int parse_argv(int argc, char *argv[]) { { "env", required_argument, NULL, 'p' }, /* alias for -p */ { "children-max", required_argument, NULL, 'm' }, { "ping", no_argument, NULL, ARG_PING }, + { "trace", required_argument, NULL, ARG_TRACE }, { "timeout", required_argument, NULL, 't' }, { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS }, { "version", no_argument, NULL, 'V' }, @@ -143,6 +149,14 @@ static int parse_argv(int argc, char *argv[]) { arg_ping = true; break; + case ARG_TRACE: + r = parse_boolean_argument("--trace=", optarg, NULL); + if (r < 0) + return r; + + arg_trace = r; + break; + case 't': r = parse_sec(optarg, &arg_timeout); if (r < 0) @@ -296,6 +310,13 @@ static int send_control_commands(void) { return r; } + if (arg_trace >= 0) { + r = varlink_callbo_and_log(link, "io.systemd.Udev.SetTrace", /* reply = */ NULL, + SD_JSON_BUILD_PAIR_BOOLEAN("enable", arg_trace)); + if (r < 0) + return r; + } + return 0; } diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index 9585ac892f..76b953c917 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -15,10 +15,11 @@ #include "hashmap.h" #include "set.h" #include "signal-util.h" +#include "static-destruct.h" #include "string-util.h" +#include "time-util.h" #include "udevadm.h" #include "virt.h" -#include "time-util.h" static bool arg_show_property = false; static bool arg_print_kernel = false; @@ -26,6 +27,9 @@ static bool arg_print_udev = false; static Set *arg_tag_filter = NULL; static Hashmap *arg_subsystem_filter = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_tag_filter, set_freep); +STATIC_DESTRUCTOR_REGISTER(arg_subsystem_filter, hashmap_freep); + static int device_monitor_handler(sd_device_monitor *monitor, sd_device *device, void *userdata) { sd_device_action_t action = _SD_DEVICE_ACTION_INVALID; const char *devpath = NULL, *subsystem = NULL; @@ -143,28 +147,27 @@ static int parse_argv(int argc, char *argv[]) { if (slash) { devtype = strdup(slash + 1); if (!devtype) - return -ENOMEM; + return log_oom(); subsystem = strndup(optarg, slash - optarg); } else subsystem = strdup(optarg); if (!subsystem) - return -ENOMEM; + return log_oom(); - r = hashmap_ensure_put(&arg_subsystem_filter, NULL, subsystem, devtype); + r = hashmap_ensure_put(&arg_subsystem_filter, &trivial_hash_ops_free_free, subsystem, devtype); if (r < 0) - return r; + return log_oom(); TAKE_PTR(subsystem); TAKE_PTR(devtype); break; } case 't': - /* optarg is stored in argv[], so we don't need to copy it */ - r = set_ensure_put(&arg_tag_filter, &string_hash_ops, optarg); + r = set_put_strdup(&arg_tag_filter, optarg); if (r < 0) - return r; + return log_oom(); break; case 'V': @@ -192,7 +195,7 @@ int monitor_main(int argc, char *argv[], void *userdata) { r = parse_argv(argc, argv); if (r <= 0) - goto finalize; + return r; if (running_in_chroot() > 0) { log_info("Running in chroot, ignoring request."); @@ -203,22 +206,18 @@ int monitor_main(int argc, char *argv[], void *userdata) { setlinebuf(stdout); r = sd_event_default(&event); - if (r < 0) { - log_error_errno(r, "Failed to initialize event: %m"); - goto finalize; - } + if (r < 0) + return log_error_errno(r, "Failed to initialize event: %m"); r = sd_event_set_signal_exit(event, true); - if (r < 0) { - log_error_errno(r, "Failed to install SIGINT/SIGTERM handling: %m"); - goto finalize; - } + if (r < 0) + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handling: %m"); printf("monitor will print the received events for:\n"); if (arg_print_udev) { r = setup_monitor(MONITOR_GROUP_UDEV, event, &udev_monitor); if (r < 0) - goto finalize; + return r; printf("UDEV - the event which udev sends out after rule processing\n"); } @@ -226,23 +225,15 @@ int monitor_main(int argc, char *argv[], void *userdata) { if (arg_print_kernel) { r = setup_monitor(MONITOR_GROUP_KERNEL, event, &kernel_monitor); if (r < 0) - goto finalize; + return r; printf("KERNEL - the kernel uevent\n"); } printf("\n"); r = sd_event_loop(event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finalize; - } - - r = 0; - -finalize: - hashmap_free_free_free(arg_subsystem_filter); - set_free(arg_tag_filter); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); - return r; + return 0; } diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 5db991b8a6..c3f56d2d81 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -3,37 +3,30 @@ * Copyright © 2003-2004 Greg Kroah-Hartman <greg@kroah.com> */ -#include <errno.h> #include <getopt.h> #include <signal.h> -#include <stddef.h> #include <stdio.h> -#include <stdlib.h> -#include <sys/signalfd.h> -#include <unistd.h> #include "sd-device.h" -#include "ansi-color.h" #include "device-private.h" -#include "device-util.h" -#include "format-util.h" -#include "path-util.h" -#include "string-util.h" +#include "parse-argument.h" +#include "static-destruct.h" #include "strv.h" -#include "strxcpyx.h" -#include "terminal-util.h" #include "udev-builtin.h" +#include "udev-dump.h" #include "udev-event.h" -#include "udev-format.h" #include "udev-rules.h" #include "udevadm-util.h" #include "udevadm.h" -#include "user-util.h" static sd_device_action_t arg_action = SD_DEVICE_ADD; static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; static const char *arg_syspath = NULL; +static char **arg_extra_rules_dir = NULL; +static bool arg_verbose = false; + +STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); static int help(void) { @@ -42,7 +35,9 @@ static int help(void) { " -h --help Show this help\n" " -V --version Show package version\n" " -a --action=ACTION|help Set action string\n" - " -N --resolve-names=early|late|never When to resolve names\n", + " -N --resolve-names=early|late|never When to resolve names\n" + " -D --extra-rules-dir=DIR Also load rules from the directory\n" + " -v --verbose Show verbose logs\n", program_invocation_short_name); return 0; @@ -50,16 +45,18 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, + { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "extra-rules-dir", required_argument, NULL, 'D' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, {} }; int r, c; - while ((c = getopt_long(argc, argv, "a:N:Vh", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "a:N:D:vVh", options, NULL)) >= 0) switch (c) { case 'a': r = parse_device_action(optarg, &arg_action); @@ -74,6 +71,21 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--resolve-names= must be early, late or never"); break; + case 'D': { + _cleanup_free_ char *p = NULL; + + r = parse_path_argument(optarg, /* suppress_root = */ false, &p); + if (r < 0) + return r; + + r = strv_consume(&arg_extra_rules_dir, TAKE_PTR(p)); + if (r < 0) + return log_oom(); + break; + } + case 'v': + arg_verbose = true; + break; case 'V': return print_version(); case 'h': @@ -105,20 +117,23 @@ int test_main(int argc, char *argv[], void *userdata) { if (r <= 0) return r; - printf("This program is for debugging only, it does not run any program\n" - "specified by a RUN key. It may show incorrect results, because\n" - "some values may be different, or not available at a simulation run.\n" - "\n"); + puts("This program is for debugging only, it does not run any program\n" + "specified by a RUN key. It may show incorrect results, because\n" + "some values may be different, or not available at a simulation run."); assert_se(sigprocmask(SIG_SETMASK, NULL, &sigmask_orig) >= 0); + puts("\nLoading builtins..."); udev_builtin_init(); + puts("Loading builtins done."); - r = udev_rules_load(&rules, arg_resolve_name_timing); + puts("\nLoading udev rules files..."); + r = udev_rules_load(&rules, arg_resolve_name_timing, arg_extra_rules_dir); if (r < 0) { log_error_errno(r, "Failed to read udev rules: %m"); goto out; } + puts("Loading udev rules files done."); r = find_device_with_action(arg_syspath, arg_action, &dev); if (r < 0) { @@ -134,89 +149,17 @@ int test_main(int argc, char *argv[], void *userdata) { log_oom(); goto out; } + event->trace = arg_verbose; assert_se(sigfillset(&mask) >= 0); assert_se(sigprocmask(SIG_SETMASK, &mask, &sigmask_orig) >= 0); + printf("\nProcessing udev rules%s...\n", arg_verbose ? "" : " (verbose logs can be shown by -v/--verbose)"); udev_event_execute_rules(event, rules); + puts("Processing udev rules done."); - printf("%sProperties:%s\n", ansi_highlight(), ansi_normal()); - FOREACH_DEVICE_PROPERTY(dev, key, value) - printf(" %s=%s\n", key, value); - - if (sd_device_get_tag_first(dev)) { - printf("%sTags:%s\n", ansi_highlight(), ansi_normal()); - FOREACH_DEVICE_TAG(dev, tag) - printf(" %s\n", tag); - } - - if (sd_device_get_devnum(dev, NULL) >= 0) { - - if (sd_device_get_devlink_first(dev)) { - int prio; - device_get_devlink_priority(dev, &prio); - printf("%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); - FOREACH_DEVICE_DEVLINK(dev, devlink) - printf(" %s\n", devlink); - } - - printf("%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); - - uid_t uid = event->uid; - if (!uid_is_valid(uid)) - (void) device_get_devnode_uid(dev, &uid); - if (uid_is_valid(uid)) { - _cleanup_free_ char *user = uid_to_name(uid); - printf("%sDevice node owner:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); - } - - gid_t gid = event->gid; - if (!gid_is_valid(uid)) - (void) device_get_devnode_gid(dev, &gid); - if (gid_is_valid(gid)) { - _cleanup_free_ char *group = gid_to_name(gid); - printf("%sDevice node group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); - } - - mode_t mode = event->mode; - if (mode == MODE_INVALID) - (void) device_get_devnode_mode(dev, &mode); - if (mode != MODE_INVALID) - printf("%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); - - if (!ordered_hashmap_isempty(event->seclabel_list)) { - const char *name, *label; - printf("%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); - ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) - printf(" %s : %s\n", name, label); - } - } - - if (sd_device_get_ifindex(dev, NULL) >= 0) { - if (!isempty(event->name)) - printf("%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); - - if (!strv_isempty(event->altnames)) { - bool space = true; - printf("%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); - fputstrv(stdout, event->altnames, "\n ", &space); - puts(""); - } - } - - if (!ordered_hashmap_isempty(event->run_list)) { - void *val; - const char *command; - printf("%sQueued commands:%s\n", ansi_highlight(), ansi_normal()); - ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { - UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); - - if (builtin_cmd != _UDEV_BUILTIN_INVALID) - printf(" RUN{builtin} : %s\n", command); - else - printf(" RUN{program} : %s\n", command); - } - } + puts(""); + dump_event(event, NULL); r = 0; out: diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index 2a3e974d04..641adb4f97 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -5,6 +5,9 @@ #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" +#include "chase.h" +#include "conf-files.h" +#include "constants.h" #include "device-private.h" #include "path-util.h" #include "udev-ctrl.h" @@ -168,3 +171,109 @@ int udev_ping(usec_t timeout_usec, bool ignore_connection_failure) { return 1; /* received reply */ } + +static int search_rules_file_in_conf_dirs(const char *s, const char *root, char ***files) { + _cleanup_free_ char *filename = NULL; + int r; + + assert(s); + + if (!endswith(s, ".rules")) + filename = strjoin(s, ".rules"); + else + filename = strdup(s); + if (!filename) + return log_oom(); + + if (!filename_is_valid(filename)) + return 0; + + STRV_FOREACH(p, CONF_PATHS_STRV("udev/rules.d")) { + _cleanup_free_ char *path = NULL, *resolved = NULL; + + path = path_join(*p, filename); + if (!path) + return log_oom(); + + r = chase(path, root, CHASE_PREFIX_ROOT | CHASE_MUST_BE_REGULAR, &resolved, /* ret_fd = */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to chase \"%s\": %m", path); + + r = strv_consume(files, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + + return 1; /* found */ + } + + return 0; +} + +static int search_rules_file(const char *s, const char *root, char ***files) { + int r; + + assert(s); + assert(files); + + /* If the input is a file name (e.g. 99-systemd.rules), then try to find it in udev/rules.d directories. */ + r = search_rules_file_in_conf_dirs(s, root, files); + if (r != 0) + return r; + + /* If not found, or if it is a path, then chase it. */ + struct stat st; + _cleanup_free_ char *resolved = NULL; + r = chase_and_stat(s, root, CHASE_PREFIX_ROOT, &resolved, &st); + if (r < 0) + return log_error_errno(r, "Failed to chase \"%s\": %m", s); + + r = stat_verify_regular(&st); + if (r == -EISDIR) { + _cleanup_strv_free_ char **files_in_dir = NULL; + + r = conf_files_list_strv(&files_in_dir, ".rules", root, 0, (const char* const*) STRV_MAKE_CONST(s)); + if (r < 0) + return log_error_errno(r, "Failed to enumerate rules files in '%s': %m", resolved); + + r = strv_extend_strv_consume(files, TAKE_PTR(files_in_dir), /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + return 0; + } + if (r < 0) + return log_error_errno(r, "'%s' is neither a regular file nor a directory: %m", resolved); + + r = strv_consume(files, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + + return 0; +} + +int search_rules_files(char * const *a, const char *root, char ***ret) { + _cleanup_strv_free_ char **files = NULL; + int r; + + assert(ret); + + if (strv_isempty(a)) { + r = conf_files_list_strv(&files, ".rules", root, 0, (const char* const*) CONF_PATHS_STRV("udev/rules.d")); + if (r < 0) + return log_error_errno(r, "Failed to enumerate rules files: %m"); + + if (root && strv_isempty(files)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No rules files found in %s.", root); + + } else + STRV_FOREACH(s, a) { + r = search_rules_file(*s, root, &files); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(files); + return 0; +} diff --git a/src/udev/udevadm-util.h b/src/udev/udevadm-util.h index 4b58efbe97..0a8b31ada7 100644 --- a/src/udev/udevadm-util.h +++ b/src/udev/udevadm-util.h @@ -7,3 +7,4 @@ int find_device(const char *id, const char *prefix, sd_device **ret); int find_device_with_action(const char *id, sd_device_action_t action, sd_device **ret); int parse_device_action(const char *str, sd_device_action_t *action); int udev_ping(usec_t timeout, bool ignore_connection_failure); +int search_rules_files(char * const *a, const char *root, char ***ret); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 32202508f3..fb8cdee4f2 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -7,16 +7,15 @@ #include <stdlib.h> #include <unistd.h> -#include "conf-files.h" -#include "constants.h" +#include "errno-util.h" #include "log.h" #include "parse-argument.h" #include "pretty-print.h" -#include "stat-util.h" #include "static-destruct.h" #include "strv.h" #include "udev-rules.h" #include "udevadm.h" +#include "udevadm-util.h" static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; static char *arg_root = NULL; @@ -109,10 +108,6 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - if (arg_root && optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Combination of --root= and FILEs is not supported."); - return 1; } @@ -139,57 +134,20 @@ static int verify_rules_file(UdevRules *rules, const char *fname) { return 0; } -static int verify_rules_filelist(UdevRules *rules, char **files, size_t *fail_count, size_t *success_count, bool walk_dirs); - -static int verify_rules_dir(UdevRules *rules, const char *dir, size_t *fail_count, size_t *success_count) { - int r; - _cleanup_strv_free_ char **files = NULL; - - assert(rules); - assert(dir); - assert(fail_count); - assert(success_count); - - r = conf_files_list(&files, ".rules", NULL, 0, dir); - if (r < 0) - return log_error_errno(r, "Failed to enumerate rules files: %m"); - - return verify_rules_filelist(rules, files, fail_count, success_count, /* walk_dirs */ false); -} - -static int verify_rules_filelist(UdevRules *rules, char **files, size_t *fail_count, size_t *success_count, bool walk_dirs) { - int r, rv = 0; - - assert(rules); - assert(files); - assert(fail_count); - assert(success_count); - - STRV_FOREACH(fp, files) { - if (walk_dirs && is_dir(*fp, /* follow = */ true) > 0) - r = verify_rules_dir(rules, *fp, fail_count, success_count); - else { - r = verify_rules_file(rules, *fp); - if (r < 0) - ++(*fail_count); - else - ++(*success_count); - } - if (r < 0 && rv >= 0) - rv = r; - } - - return rv; -} - static int verify_rules(UdevRules *rules, char **files) { size_t fail_count = 0, success_count = 0; - int r; + int r, ret = 0; assert(rules); - assert(files); - r = verify_rules_filelist(rules, files, &fail_count, &success_count, /* walk_dirs */ true); + STRV_FOREACH(fp, files) { + r = verify_rules_file(rules, *fp); + if (r < 0) + ++fail_count; + else + ++success_count; + RET_GATHER(ret, r); + } if (arg_summary) printf("\n%s%zu udev rules files have been checked.%s\n" @@ -203,7 +161,7 @@ static int verify_rules(UdevRules *rules, char **files) { fail_count, fail_count > 0 ? ansi_normal() : ""); - return r; + return ret; } int verify_main(int argc, char *argv[], void *userdata) { @@ -218,19 +176,10 @@ int verify_main(int argc, char *argv[], void *userdata) { if (!rules) return -ENOMEM; - if (optind == argc) { - const char* const* rules_dirs = STRV_MAKE_CONST(CONF_PATHS("udev/rules.d")); - _cleanup_strv_free_ char **files = NULL; - - r = conf_files_list_strv(&files, ".rules", arg_root, 0, rules_dirs); - if (r < 0) - return log_error_errno(r, "Failed to enumerate rules files: %m"); - if (arg_root && strv_isempty(files)) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), - "No rules files found in %s.", arg_root); - - return verify_rules(rules, files); - } + _cleanup_strv_free_ char **files = NULL; + r = search_rules_files(strv_skip(argv, optind), arg_root, &files); + if (r < 0) + return r; - return verify_rules(rules, strv_skip(argv, optind)); + return verify_rules(rules, files); } diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 30b6ddb728..ebf1e5190c 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -26,6 +26,7 @@ static int help(void) { { "test", "Test an event run" }, { "test-builtin", "Test a built-in command" }, { "verify", "Verify udev rules files" }, + { "cat", "Show udev rules files" }, { "wait", "Wait for device or device symlink" }, { "lock", "Lock a block device" }, }; @@ -97,6 +98,7 @@ static int help_main(int argc, char *argv[], void *userdata) { static int udevadm_main(int argc, char *argv[]) { static const Verb verbs[] = { + { "cat", VERB_ANY, VERB_ANY, 0, cat_main }, { "info", VERB_ANY, VERB_ANY, 0, info_main }, { "trigger", VERB_ANY, VERB_ANY, 0, trigger_main }, { "settle", VERB_ANY, VERB_ANY, 0, settle_main }, diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index 7920a70d5b..e39dbf655d 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -5,6 +5,7 @@ #include "macro.h" +int cat_main(int argc, char *argv[], void *userdata); int info_main(int argc, char *argv[], void *userdata); int trigger_main(int argc, char *argv[], void *userdata); int settle_main(int argc, char *argv[], void *userdata); diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 3ed21fc0ac..0f974b3558 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -242,6 +242,8 @@ def test_parse_args_many(): '--output=OUTPUT', '--measure', '--no-measure', + '--policy-digest', + '--no-policy-digest', ]) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] @@ -262,6 +264,7 @@ def test_parse_args_many(): assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is False + assert opts.policy_digest is False def test_parse_sections(): opts = ukify.parse_args( diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 6b532437ac..5b4f20aeda 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -255,6 +255,7 @@ class UkifyConfig: pcr_public_keys: list[str] pcrpkey: Optional[Path] phase_path_groups: Optional[list[str]] + policy_digest: bool profile: Union[str, Path, None] sb_cert: Union[str, Path, None] sb_cert_name: Optional[str] @@ -324,7 +325,7 @@ class Uname: filename, ] - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) try: notes = subprocess.check_output(cmd, stderr=subprocess.PIPE, text=True) except subprocess.CalledProcessError as e: @@ -355,7 +356,7 @@ class Uname: for func in (cls.scrape_x86, cls.scrape_elf, cls.scrape_generic): try: version = func(filename, opts=opts) - print(f'Found uname version: {version}') + print(f'Found uname version: {version}', file=sys.stderr) return version except ValueError as e: print(str(e)) @@ -496,7 +497,7 @@ class PeSign(SignTool): '-o', output_f, ] # fmt: skip - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) subprocess.check_call(cmd) @staticmethod @@ -506,7 +507,7 @@ class PeSign(SignTool): tool = find_tool('pesign', opts=opts) cmd = [tool, '-i', opts.linux, '-S'] - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) info = subprocess.check_output(cmd, text=True) return 'No signatures found.' in info @@ -528,7 +529,7 @@ class SbSign(SignTool): '--output', output_f, ] # fmt: skip - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) subprocess.check_call(cmd) @staticmethod @@ -538,7 +539,7 @@ class SbSign(SignTool): tool = find_tool('sbverify', opts=opts) cmd = [tool, '--list', opts.linux] - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) info = subprocess.check_output(cmd, text=True) return 'No signature table present' in info @@ -580,7 +581,7 @@ class SystemdSbSign(SignTool): '--output', output_f, ] # fmt: skip - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) subprocess.check_call(cmd) @staticmethod @@ -627,7 +628,7 @@ def check_splash(filename: Optional[Path]) -> None: return img = Image.open(filename, formats=['BMP']) - print(f'Splash image {filename} is {img.width}×{img.height} pixels') + print(f'Splash image {filename} is {img.width}×{img.height} pixels', file=sys.stderr) def check_inputs(opts: UkifyConfig) -> None: @@ -751,7 +752,7 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> unique_to_measure[section.name] = section - if opts.measure: + if opts.measure or opts.policy_digest: to_measure = unique_to_measure.copy() for dtbauto in dtbauto_to_measure: @@ -762,7 +763,9 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> cmd = [ measure_tool, - 'calculate', + 'calculate' if opts.measure else 'policy-digest', + '--json', + opts.json, *(f'--{s.name.removeprefix(".")}={s.content}' for s in to_measure.values()), *(f'--bank={bank}' for bank in banks), # For measurement, the keys are not relevant, so we can lump all the phase paths @@ -770,7 +773,12 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> *(f'--phase={phase_path}' for phase_path in itertools.chain.from_iterable(pp_groups)), ] - print('+', shell_join(cmd)) + # The JSON object will be used for offline signing, include the public key + # so that the fingerprint is included too. + if opts.policy_digest and opts.pcr_public_keys: + cmd += [f'--public-key={opts.pcr_public_keys[0]}'] + + print('+', shell_join(cmd), file=sys.stderr) subprocess.check_call(cmd) # PCR signing @@ -808,7 +816,7 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> extra += [f'--phase={phase_path}' for phase_path in group or ()] - print('+', shell_join(cmd + extra)) # type: ignore + print('+', shell_join(cmd + extra), file=sys.stderr) # type: ignore pcrsig = subprocess.check_output(cmd + extra, text=True) # type: ignore pcrsig = json.loads(pcrsig) pcrsigs += [pcrsig] @@ -1145,7 +1153,7 @@ def make_uki(opts: UkifyConfig) -> None: signtool.sign(os.fspath(opts.linux), os.fspath(linux), opts=opts) if opts.uname is None and opts.linux is not None: - print('Kernel version not specified, starting autodetection 😖.') + print('Kernel version not specified, starting autodetection 😖.', file=sys.stderr) opts.uname = Uname.scrape(opts.linux, opts=opts) uki = UKI(opts.stub) @@ -1163,7 +1171,7 @@ def make_uki(opts: UkifyConfig) -> None: if opts.certificate_provider: cmd += ['--certificate-source', f'provider:{opts.certificate_provider}'] - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) pcrpkey = subprocess.check_output(cmd) else: pcrpkey = Path(opts.pcr_public_keys[0]) @@ -1175,7 +1183,7 @@ def make_uki(opts: UkifyConfig) -> None: if opts.signing_provider: cmd += ['--private-key-source', f'provider:{opts.signing_provider}'] - print('+', shell_join(cmd)) + print('+', shell_join(cmd), file=sys.stderr) pcrpkey = subprocess.check_output(cmd) hwids = None @@ -1282,7 +1290,10 @@ def make_uki(opts: UkifyConfig) -> None: if n not in to_import: continue - print(f"Copying section '{n}' from '{profile}': {pesection.Misc_VirtualSize} bytes") + print( + f"Copying section '{n}' from '{profile}': {pesection.Misc_VirtualSize} bytes", + file=sys.stderr, + ) uki.add_section( Section.create(n, pesection.get_data(length=pesection.Misc_VirtualSize), measure=True) ) @@ -1311,7 +1322,7 @@ def make_uki(opts: UkifyConfig) -> None: os.umask(umask := os.umask(0)) os.chmod(opts.output, 0o777 & ~umask) - print(f'Wrote {"signed" if sign_args_present else "unsigned"} {opts.output}') + print(f'Wrote {"signed" if sign_args_present else "unsigned"} {opts.output}', file=sys.stderr) @contextlib.contextmanager @@ -1799,7 +1810,7 @@ CONFIG_ITEMS = [ ConfigItem( '--efi-arch', metavar='ARCH', - choices=('ia32', 'x64', 'arm', 'aa64', 'riscv64'), + choices=('ia32', 'x64', 'arm', 'aa64', 'riscv32', 'riscv64', 'loongarch32', 'loongarch64'), help='target EFI architecture', config_key='UKI/EFIArch', ), @@ -1936,6 +1947,11 @@ CONFIG_ITEMS = [ help='print systemd-measure output for the UKI', ), ConfigItem( + '--policy-digest', + action=argparse.BooleanOptionalAction, + help='print systemd-measure policy digests for the UKI', + ), + ConfigItem( '--json', choices=('pretty', 'short', 'off'), default='off', @@ -1963,14 +1979,14 @@ def apply_config(namespace: argparse.Namespace, filename: Union[str, Path, None] if namespace.config: # Config set by the user, use that. filename = namespace.config - print(f'Using config file: {filename}') + print(f'Using config file: {filename}', file=sys.stderr) else: # Try to look for a config file then use the first one found. for config_dir in DEFAULT_CONFIG_DIRS: filename = Path(config_dir) / DEFAULT_CONFIG_FILE if filename.is_file(): # Found a config file, use it. - print(f'Using found config file: {filename}') + print(f'Using found config file: {filename}', file=sys.stderr) break else: # No config file specified or found, nothing to do. @@ -2094,7 +2110,7 @@ def finalize_options(opts: argparse.Namespace) -> None: elif opts.linux or opts.initrd: raise ValueError('--linux=/--initrd= options cannot be used with positional arguments') else: - print("Assuming obsolete command line syntax with no verb. Please use 'build'.") + print("Assuming obsolete command line syntax with no verb. Please use 'build'.", file=sys.stderr) if opts.positional: opts.linux = Path(opts.positional[0]) # If we have initrds from parsing config files, append our positional args at the end @@ -2103,10 +2119,16 @@ def finalize_options(opts: argparse.Namespace) -> None: # Check that --pcr-public-key=, --pcr-private-key=, and --phases= # have either the same number of arguments or are not specified at all. + # But allow a single public key, for offline PCR signing, to pre-populate the JSON object + # with the certificate's fingerprint. n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys) n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys) n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups) - if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv: + if opts.policy_digest and n_pcr_priv is not None: + raise ValueError('--pcr-private-key= cannot be specified with --policy-digest') + if opts.policy_digest and (n_pcr_pub is None or n_pcr_pub != 1): + raise ValueError('--policy-digest requires exactly one --pcr-public-key=') + if n_pcr_pub is not None and n_pcr_priv is not None and n_pcr_pub != n_pcr_priv: raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=') if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv: raise ValueError('--phases= specifications must match --pcr-private-key=') diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index e47c3761d0..8a85778ef7 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1227,7 +1227,7 @@ static int parse_argv(int argc, char *argv[]) { { "chain", no_argument, NULL, ARG_CHAIN }, { "uid-min", required_argument, NULL, ARG_UID_MIN }, { "uid-max", required_argument, NULL, ARG_UID_MAX }, - { "fuzzy", required_argument, NULL, 'z' }, + { "fuzzy", no_argument, NULL, 'z' }, { "disposition", required_argument, NULL, ARG_DISPOSITION }, { "boundaries", required_argument, NULL, ARG_BOUNDARIES }, {} diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c index 1e36face40..c8fef87326 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -29,8 +29,7 @@ #define LISTEN_IDLE_USEC (90 * USEC_PER_SEC) typedef struct LookupParameters { - const char *user_name; - const char *group_name; + const char *name; union { uid_t uid; gid_t gid; @@ -135,9 +134,9 @@ static int userdb_flags_from_service(sd_varlink *link, const char *service, User static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - { "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 }, - { "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, user_name), SD_JSON_RELAX }, - { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + { "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 }, + { "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX }, + { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, {} }; @@ -162,8 +161,8 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete if (uid_is_valid(p.uid)) r = userdb_by_uid(p.uid, userdb_flags, &hr); - else if (p.user_name) - r = userdb_by_name(p.user_name, userdb_flags, &hr); + else if (p.name) + r = userdb_by_name(p.name, userdb_flags, &hr); else { _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL; @@ -215,7 +214,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete } if ((uid_is_valid(p.uid) && hr->uid != p.uid) || - (p.user_name && !streq(hr->user_name, p.user_name))) + (p.name && !user_record_matches_user_name(hr, p.name))) return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); r = build_user_json(link, hr, &v); @@ -272,9 +271,9 @@ static int build_group_json(sd_varlink *link, GroupRecord *gr, sd_json_variant * static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 }, - { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, group_name), SD_JSON_RELAX }, - { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 }, + { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX }, + { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, {} }; @@ -298,8 +297,8 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet if (gid_is_valid(p.gid)) r = groupdb_by_gid(p.gid, userdb_flags, &g); - else if (p.group_name) - r = groupdb_by_name(p.group_name, userdb_flags, &g); + else if (p.name) + r = groupdb_by_name(p.name, userdb_flags, &g); else { _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL; @@ -345,7 +344,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet } if ((uid_is_valid(p.gid) && g->gid != p.gid) || - (p.group_name && !streq(g->group_name, p.group_name))) + (p.name && !group_record_matches_group_name(g, p.name))) return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); r = build_group_json(link, g, &v); @@ -355,17 +354,23 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet return sd_varlink_reply(link, v); } +typedef struct MembershipLookupParameters { + const char *user_name; + const char *group_name; + const char *service; +} MembershipLookupParameters; + static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - { "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, user_name), SD_JSON_RELAX }, - { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, group_name), SD_JSON_RELAX }, - { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + { "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(MembershipLookupParameters, user_name), SD_JSON_RELAX }, + { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(MembershipLookupParameters, group_name), SD_JSON_RELAX }, + { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MembershipLookupParameters, service), 0 }, {} }; _cleanup_free_ char *last_user_name = NULL, *last_group_name = NULL; _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; - LookupParameters p = {}; + MembershipLookupParameters p = {}; UserDBFlags userdb_flags; int r; diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 3fa0cbba61..7142405657 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -575,7 +575,7 @@ static int verify_source_vc(char **ret_path, const char *src_vc) { /* setfont(8) silently ignores when the font can't be applied due to the vc being in * KD_GRAPHICS. Hence we continue to accept this case however we now let the user know that the vc - * will be initialized only partially.*/ + * will be initialized only partially. */ r = verify_vc_display_mode(fd); if (r < 0) log_notice_errno(r, "Virtual console %s is not in KD_TEXT, font settings likely won't be applied.", src_vc); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index d133572464..97f233c906 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -17,6 +17,7 @@ #include "process-util.h" #include "string-util.h" #include "terminal-util.h" +#include "verbs.h" static char *arg_hash = NULL; static bool arg_superblock = true; @@ -274,158 +275,161 @@ static int parse_options(const char *options) { return r; } -static int run(int argc, char *argv[]) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - const char *verb; + _cleanup_free_ void *m = NULL; + struct crypt_params_verity p = {}; + crypt_status_info status; + size_t l; int r; - if (argv_looks_like_help(argc, argv)) - return help(); - - if (argc < 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); + assert(argc >= 5); - log_setup(); + const char *volume = argv[1], + *data_device = argv[2], + *verity_device = argv[3], + *root_hash = argv[4], + *options = mangle_none(argc > 5 ? argv[5] : NULL); - cryptsetup_enable_logging(NULL); + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - umask(0022); + r = unhexmem(root_hash, &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %m"); - verb = argv[1]; + r = crypt_init(&cd, verity_device); + if (r < 0) + return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); - if (streq(verb, "attach")) { - const char *volume, *data_device, *verity_device, *root_hash, *options; - _cleanup_free_ void *m = NULL; - struct crypt_params_verity p = {}; - crypt_status_info status; - size_t l; + cryptsetup_enable_logging(cd); - if (argc < 6) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least four arguments."); + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } - volume = argv[2]; - data_device = argv[3]; - verity_device = argv[4]; - root_hash = argv[5]; - options = mangle_none(argc > 6 ? argv[6] : NULL); + if (options) { + r = parse_options(options); + if (r < 0) + return log_error_errno(r, "Failed to parse options: %m"); + } - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + if (arg_superblock) { + p = (struct crypt_params_verity) { + .fec_device = arg_fec_what, + .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, + }; - r = unhexmem(root_hash, &m, &l); + r = crypt_load(cd, CRYPT_VERITY, &p); if (r < 0) - return log_error_errno(r, "Failed to parse root hash: %m"); - - r = crypt_init(&cd, verity_device); + return log_error_errno(r, "Failed to load verity superblock: %m"); + } else { + p = (struct crypt_params_verity) { + .hash_name = arg_hash, + .data_device = data_device, + .fec_device = arg_fec_what, + .salt = arg_salt, + .salt_size = arg_salt_size, + .hash_type = arg_format, + .data_block_size = arg_data_block_size, + .hash_block_size = arg_hash_block_size, + .data_size = arg_data_blocks, + .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, + .flags = CRYPT_VERITY_NO_HEADER, + }; + + r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); if (r < 0) - return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); - - cryptsetup_enable_logging(cd); - - status = crypt_status(cd, volume); - if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { - log_info("Volume %s already active.", volume); - return 0; - } + return log_error_errno(r, "Failed to format verity superblock: %m"); + } - if (options) { - r = parse_options(options); - if (r < 0) - return log_error_errno(r, "Failed to parse options: %m"); - } + r = crypt_set_data_device(cd, data_device); + if (r < 0) + return log_error_errno(r, "Failed to configure data device: %m"); - if (arg_superblock) { - p = (struct crypt_params_verity) { - .fec_device = arg_fec_what, - .hash_area_offset = arg_hash_offset, - .fec_area_offset = arg_fec_offset, - .fec_roots = arg_fec_roots, - }; + if (arg_root_hash_signature) { +#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + _cleanup_free_ char *hash_sig = NULL; + size_t hash_sig_size; + char *value; - r = crypt_load(cd, CRYPT_VERITY, &p); + if ((value = startswith(arg_root_hash_signature, "base64:"))) { + r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size); if (r < 0) - return log_error_errno(r, "Failed to load verity superblock: %m"); + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); } else { - p = (struct crypt_params_verity) { - .hash_name = arg_hash, - .data_device = data_device, - .fec_device = arg_fec_what, - .salt = arg_salt, - .salt_size = arg_salt_size, - .hash_type = arg_format, - .data_block_size = arg_data_block_size, - .hash_block_size = arg_hash_block_size, - .data_size = arg_data_blocks, - .hash_area_offset = arg_hash_offset, - .fec_area_offset = arg_fec_offset, - .fec_roots = arg_fec_roots, - .flags = CRYPT_VERITY_NO_HEADER, - }; - - r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); + r = read_full_file_full( + AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &hash_sig, &hash_sig_size); if (r < 0) - return log_error_errno(r, "Failed to format verity superblock: %m"); + return log_error_errno(r, "Failed to read root hash signature: %m"); } - r = crypt_set_data_device(cd, data_device); - if (r < 0) - return log_error_errno(r, "Failed to configure data device: %m"); - - if (arg_root_hash_signature) { -#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY - _cleanup_free_ char *hash_sig = NULL; - size_t hash_sig_size; - char *value; - - if ((value = startswith(arg_root_hash_signature, "base64:"))) { - r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); - } else { - r = read_full_file_full( - AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &hash_sig, &hash_sig_size); - if (r < 0) - return log_error_errno(r, "Failed to read root hash signature: %m"); - } - - r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags); + r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags); #else - assert_not_reached(); + assert_not_reached(); #endif - } else - r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags); - if (r < 0) - return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); + } else + r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags); + if (r < 0) + return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); - } else if (streq(verb, "detach")) { - const char *volume; + return 0; +} - volume = argv[2]; +static int verb_detach(int argc, char *argv[], void *userdata) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + int r; - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + assert(argc == 2); - r = crypt_init_by_name(&cd, volume); - if (r == -ENODEV) { - log_info("Volume %s 'already' inactive.", volume); - return 0; - } - if (r < 0) - return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); + const char *volume = argv[1]; - cryptsetup_enable_logging(cd); + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = crypt_deactivate(cd, volume); - if (r < 0) - return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s 'already' inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); return 0; } +static int run(int argc, char *argv[]) { + if (argv_looks_like_help(argc, argv)) + return help(); + + log_setup(); + + cryptsetup_enable_logging(NULL); + + umask(0022); + + static const Verb verbs[] = { + { "attach", 5, 6, 0, verb_attach }, + { "detach", 2, 2, 0, verb_detach }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + DEFINE_MAIN_FUNCTION(run); |