diff options
Diffstat (limited to 'src')
156 files changed, 2958 insertions, 1253 deletions
diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c index 4cf015ab44..e7e273e218 100644 --- a/src/analyze/analyze-fdstore.c +++ b/src/analyze/analyze-fdstore.c @@ -81,7 +81,7 @@ static int dump_fdstore(sd_bus *bus, const char *arg) { if (r < 0) return r; - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) && table_isempty(table)) + if (table_isempty(table) && !sd_json_format_enabled(arg_json_format_flags)) log_info("No file descriptors in fdstore of '%s'.", unit); else { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */true); diff --git a/src/analyze/analyze-inspect-elf.c b/src/analyze/analyze-inspect-elf.c index 1ae7304163..0f78729c3a 100644 --- a/src/analyze/analyze-inspect-elf.c +++ b/src/analyze/analyze-inspect-elf.c @@ -4,6 +4,7 @@ #include "analyze.h" #include "analyze-inspect-elf.h" +#include "chase.h" #include "elf-util.h" #include "errno-util.h" #include "fd-util.h" @@ -19,23 +20,13 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { STRV_FOREACH(filename, filenames) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL; _cleanup_(table_unrefp) Table *t = NULL; - _cleanup_free_ char *abspath = NULL, *path = NULL, *stacktrace = NULL; + _cleanup_free_ char *abspath = NULL, *stacktrace = NULL; _cleanup_close_ int fd = -EBADF; bool coredump = false; - r = path_make_absolute_cwd(*filename, &abspath); - if (r < 0) - return log_error_errno(r, "Could not make an absolute path out of \"%s\": %m", *filename); - - path = path_join(empty_to_root(arg_root), abspath); - if (!path) - return log_oom(); - - path_simplify(path); - - fd = RET_NERRNO(open(path, O_RDONLY|O_CLOEXEC)); + fd = chase_and_open(*filename, arg_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, &abspath); if (fd < 0) - return log_error_errno(fd, "Could not open \"%s\": %m", path); + return log_error_errno(fd, "Could not open \"%s\": %m", *filename); r = parse_elf_object(fd, abspath, arg_root, /* fork_disable_dump= */false, &stacktrace, &package_metadata); if (r < 0) @@ -65,7 +56,7 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { * metadata is parsed recursively in core files, so there might be * multiple modules. */ if (STR_IN_SET(module_name, "elfType", "elfArchitecture")) { - if (streq(module_name, "elfType") && streq("coredump", sd_json_variant_string(module_json))) + if (streq(module_name, "elfType") && streq(sd_json_variant_string(module_json), "coredump")) coredump = true; r = table_add_many( @@ -118,12 +109,13 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) { return table_log_add_error(r); } - if (json_flags & SD_JSON_FORMAT_OFF) { + if (sd_json_format_enabled(json_flags)) + sd_json_variant_dump(package_metadata, json_flags, stdout, NULL); + else { r = table_print(t, NULL); if (r < 0) return table_log_print_error(r); - } else - sd_json_variant_dump(package_metadata, json_flags, stdout, NULL); + } } return 0; diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c index 78aeff1b62..c50343d71c 100644 --- a/src/analyze/analyze-plot.c +++ b/src/analyze/analyze-plot.c @@ -168,7 +168,9 @@ static void plot_tooltip(const UnitTimes *ut) { assert(ut->name); svg("%s:\n", ut->name); - + svg("Activating: %"PRI_USEC".%.3"PRI_USEC"\n", ut->activating / USEC_PER_SEC, ut->activating % USEC_PER_SEC); + svg("Activated: %"PRI_USEC".%.3"PRI_USEC"\n", ut->activated / USEC_PER_SEC, ut->activated % USEC_PER_SEC); + UnitDependency i; FOREACH_ARGUMENT(i, UNIT_AFTER, UNIT_BEFORE, UNIT_REQUIRES, UNIT_REQUISITE, UNIT_WANTS, UNIT_CONFLICTS, UNIT_UPHOLDS) if (!strv_isempty(ut->deps[i])) { @@ -417,7 +419,7 @@ static int show_table(Table *table, const char *word) { if (!table_isempty(table)) { table_set_header(table, arg_legend); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) r = table_print_json(table, NULL, arg_json_format_flags | SD_JSON_FORMAT_COLOR_AUTO); else r = table_print(table, NULL); @@ -494,7 +496,7 @@ int verb_plot(int argc, char *argv[], void *userdata) { typesafe_qsort(times, n, compare_unit_start); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) || arg_table) + if (sd_json_format_enabled(arg_json_format_flags) || arg_table) r = produce_plot_as_text(times, boot); else r = produce_plot_as_svg(times, host, boot, pretty_times); diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 874fff14b4..aefd4f6122 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -1872,7 +1872,7 @@ static int assess(const SecurityInfo *info, return log_error_errno(r, "Failed to update cell in table: %m"); } - if (json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(json_format_flags)) { r = table_hide_column_from_display(details_table, (size_t) 2); if (r < 0) return log_error_errno(r, "Failed to set columns to display: %m"); @@ -1891,7 +1891,7 @@ static int assess(const SecurityInfo *info, assert(i < ELEMENTSOF(badness_table)); - if (details_table && (json_format_flags & SD_JSON_FORMAT_OFF)) { + if (details_table && !sd_json_format_enabled(json_format_flags)) { _cleanup_free_ char *clickable = NULL; const char *name; diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 5aba273ca6..0db3547a49 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -592,7 +592,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= requires one or more units to perform a security review."); - if (arg_json_format_flags != SD_JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore", "pcrs", "architectures", "capability", "exit-status")) + if (sd_json_format_enabled(arg_json_format_flags) && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore", "pcrs", "architectures", "capability", "exit-status")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --json= is only supported for security, inspect-elf, plot, fdstore, pcrs, architectures, capability, exit-status right now."); @@ -631,13 +631,13 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used."); if ((!arg_legend && !STRPTR_IN_SET(argv[optind], "plot", "architectures")) || - (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF))) + (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && !sd_json_format_enabled(arg_json_format_flags))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=."); if (arg_table && !streq_ptr(argv[optind], "plot")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now."); - if (arg_table && !FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (arg_table && sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive."); if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(argv[optind], "capability")) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 4c06d1a165..99ce1a1842 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -266,7 +266,7 @@ static bool env_entry_has_name(const char *entry, const char *name) { return *t == '='; } -char **strv_env_delete(char **x, size_t n_lists, ...) { +char** strv_env_delete(char **x, size_t n_lists, ...) { size_t n, i = 0; _cleanup_strv_free_ char **t = NULL; va_list ap; @@ -564,7 +564,35 @@ char* strv_env_pairs_get(char **l, const char *name) { return result; } -char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { +int strv_env_get_merged(char **l, char ***ret) { + _cleanup_strv_free_ char **v = NULL; + size_t n = 0; + int r; + + assert(ret); + + /* This converts a strv with pairs of environment variable name + value into a strv of name and + * value concatenated with a "=" separator. E.g. + * input : { "NAME", "value", "FOO", "var" } + * output : { "NAME=value", "FOO=var" } */ + + STRV_FOREACH_PAIR(key, value, l) { + char *s; + + s = strjoin(*key, "=", *value); + if (!s) + return -ENOMEM; + + r = strv_consume_with_size(&v, &n, s); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +char** strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { int k = 0; STRV_FOREACH(p, e) { diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 5f20f1d69c..203ed65bd1 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -34,14 +34,14 @@ int replace_env_argv(char **argv, char **env, char ***ret, char ***ret_unset_var bool strv_env_is_valid(char **e); #define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) -char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); +char** strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); bool strv_env_name_is_valid(char **l); bool strv_env_name_or_assignment_is_valid(char **l); char** _strv_env_merge(char **first, ...); #define strv_env_merge(first, ...) _strv_env_merge(first, __VA_ARGS__, POINTER_MAX) -char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ +char** strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ char** strv_env_unset(char **l, const char *p); /* In place ... */ char** strv_env_unset_many_internal(char **l, ...) _sentinel_; @@ -60,6 +60,7 @@ static inline char* strv_env_get(char * const *x, const char *n) { } char* strv_env_pairs_get(char **l, const char *name) _pure_; +int strv_env_get_merged(char **l, char ***ret); int getenv_bool(const char *p); int secure_getenv_bool(const char *p); diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index b335acd108..5cc3cc2f93 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -9,22 +9,27 @@ struct iovec_wrapper *iovw_new(void) { return new0(struct iovec_wrapper, 1); } -void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors) { +void iovw_done(struct iovec_wrapper *iovw) { assert(iovw); - if (free_vectors) - for (size_t i = 0; i < iovw->count; i++) - free(iovw->iovec[i].iov_base); - iovw->iovec = mfree(iovw->iovec); iovw->count = 0; } +void iovw_done_free(struct iovec_wrapper *iovw) { + assert(iovw); + + FOREACH_ARRAY(i, iovw->iovec, iovw->count) + iovec_done(i); + + iovw_done(iovw); +} + struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) { if (!iovw) return NULL; - iovw_free_contents(iovw, /* free_vectors= */ true); + iovw_done_free(iovw); return mfree(iovw); } @@ -32,7 +37,7 @@ struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) { if (!iovw) return NULL; - iovw_free_contents(iovw, /* free_vectors= */ false); + iovw_done(iovw); return mfree(iovw); } @@ -124,7 +129,7 @@ int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source rollback: for (size_t i = original_count; i < target->count; i++) - free(target->iovec[i].iov_base); + iovec_done(target->iovec + i); target->count = original_count; return r; diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 05e220c1d0..4778fa6b27 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -17,7 +17,8 @@ struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free_free); -void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors); +void iovw_done_free(struct iovec_wrapper *iovw); +void iovw_done(struct iovec_wrapper *iovw); int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { diff --git a/src/basic/process-util.c b/src/basic/process-util.c index f30d9117a7..a85a1b35f0 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -2029,7 +2029,7 @@ int posix_spawn_wrapper( const char *cgroup, PidRef *ret_pidref) { - short flags = POSIX_SPAWN_SETSIGMASK|POSIX_SPAWN_SETSIGDEF; + short flags = POSIX_SPAWN_SETSIGMASK; posix_spawnattr_t attr; sigset_t mask; int r; diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index 670040f1ad..ad9ed049f1 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -301,3 +301,19 @@ const struct sigaction sigaction_default = { .sa_handler = SIG_DFL, .sa_flags = SA_RESTART, }; + +int parse_signo(const char *s, int *ret) { + int sig, r; + + r = safe_atoi(s, &sig); + if (r < 0) + return r; + + if (!SIGNAL_VALID(sig)) + return -EINVAL; + + if (ret) + *ret = sig; + + return 0; +} diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index 5739fe0559..559ce42949 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -68,3 +68,5 @@ void propagate_signal(int sig, siginfo_t *siginfo); extern const struct sigaction sigaction_ignore; extern const struct sigaction sigaction_default; + +int parse_signo(const char *s, int *ret); diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 1715d62a39..171a368059 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -698,7 +698,8 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { STATE_OTHER, STATE_ESCAPE, STATE_CSI, - STATE_CSO, + STATE_OSC, + STATE_OSC_CLOSING, } state = STATE_OTHER; _cleanup_(memstream_done) MemStream m = {}; size_t isz, shift[2] = {}, n_carriage_returns = 0; @@ -711,7 +712,7 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { * * 1. Replaces TABs by 8 spaces * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences - * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences + * 3. Strips ANSI operating system sequences (OSC), i.e. ESC ']' … ST sequences * 4. Strip trailing \r characters (since they would "move the cursor", but have no * other effect). * @@ -719,7 +720,7 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { * are any other special characters. Truncated ANSI sequences are left-as is too. This call is * supposed to suppress the most basic formatting noise, but nothing else. * - * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */ + * Why care for OSC sequences? Well, to undo what terminal_urlify() and friends generate. */ isz = _isz ? *_isz : strlen(*ibuf); @@ -766,8 +767,8 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { } else if (*i == '[') { /* ANSI CSI */ state = STATE_CSI; begin = i + 1; - } else if (*i == ']') { /* ANSI CSO */ - state = STATE_CSO; + } else if (*i == ']') { /* ANSI OSC */ + state = STATE_OSC; begin = i + 1; } else { fputc('\x1B', f); @@ -793,17 +794,35 @@ char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { break; - case STATE_CSO: + case STATE_OSC: assert(n_carriage_returns == 0); + /* There are three kinds of OSC terminators: \x07, \x1b\x5c or \x9c. We only support + * the first two, because the last one is a valid UTF-8 codepoint and hence creates + * an ambiguity (many Terminal emulators refuse to support it as well). */ if (i >= *ibuf + isz || /* EOT … */ - (*i != '\a' && (uint8_t) *i < 32U) || (uint8_t) *i > 126U) { /* … or invalid chars in sequence */ + (!IN_SET(*i, '\x07', '\x1b') && (uint8_t) *i < 32U) || (uint8_t) *i > 126U) { /* … or invalid chars in sequence */ fputc('\x1B', f); fputc(']', f); advance_offsets(i - *ibuf, highlight, shift, 2); state = STATE_OTHER; i = begin-1; - } else if (*i == '\a') + } else if (*i == '\x07') /* Single character ST */ + state = STATE_OTHER; + else if (*i == '\x1B') + state = STATE_OSC_CLOSING; + + break; + + case STATE_OSC_CLOSING: + if (i >= *ibuf + isz || /* EOT … */ + *i != '\x5c') { /* … or incomplete two-byte ST in sequence */ + fputc('\x1B', f); + fputc(']', f); + advance_offsets(i - *ibuf, highlight, shift, 2); + state = STATE_OTHER; + i = begin-1; + } else if (*i == '\x5c') state = STATE_OTHER; break; diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 8ded46f493..878c1ec06a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -323,7 +323,6 @@ int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) int open_terminal(const char *name, int mode) { _cleanup_close_ int fd = -EBADF; - unsigned c = 0; /* * If a TTY is in the process of being closed opening it might cause EIO. This is horribly awful, but @@ -333,10 +332,9 @@ int open_terminal(const char *name, int mode) { * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 */ - if ((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) != 0) - return -EINVAL; + assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0); - for (;;) { + for (unsigned c = 0;; c++) { fd = open(name, mode, 0); if (fd >= 0) break; @@ -346,10 +344,9 @@ int open_terminal(const char *name, int mode) { /* Max 1s in total */ if (c >= 20) - return -errno; + return -EIO; (void) usleep_safe(50 * USEC_PER_MSEC); - c++; } if (!isatty_safe(fd)) @@ -1296,16 +1293,16 @@ int ptsname_malloc(int fd, char **ret) { } } -int openpt_allocate(int flags, char **ret_slave) { +int openpt_allocate(int flags, char **ret_peer_path) { _cleanup_close_ int fd = -EBADF; - _cleanup_free_ char *p = NULL; int r; fd = posix_openpt(flags|O_NOCTTY|O_CLOEXEC); if (fd < 0) return -errno; - if (ret_slave) { + _cleanup_free_ char *p = NULL; + if (ret_peer_path) { r = ptsname_malloc(fd, &p); if (r < 0) return r; @@ -1317,20 +1314,22 @@ int openpt_allocate(int flags, char **ret_slave) { if (unlockpt(fd) < 0) return -errno; - if (ret_slave) - *ret_slave = TAKE_PTR(p); + if (ret_peer_path) + *ret_peer_path = TAKE_PTR(p); return TAKE_FD(fd); } static int ptsname_namespace(int pty, char **ret) { - int no = -1, r; + int no = -1; + + assert(pty >= 0); + assert(ret); /* Like ptsname(), but doesn't assume that the path is * accessible in the local namespace. */ - r = ioctl(pty, TIOCGPTN, &no); - if (r < 0) + if (ioctl(pty, TIOCGPTN, &no) < 0) return -errno; if (no < 0) @@ -1342,10 +1341,9 @@ static int ptsname_namespace(int pty, char **ret) { return 0; } -int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) { +int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_peer_path) { _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF, fd = -EBADF; _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - pid_t child; int r; assert(pid > 0); @@ -1354,17 +1352,27 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) { if (r < 0) return r; - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-openptns)", "(sd-openpt)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, - pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child); + r = namespace_fork( + "(sd-openptns)", + "(sd-openpt)", + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, + pidnsfd, + mntnsfd, + /* netns_fd= */ -EBADF, + usernsfd, + rootfd, + /* ret_pid= */ NULL); if (r < 0) return r; if (r == 0) { pair[0] = safe_close(pair[0]); - fd = openpt_allocate(flags, NULL); + fd = openpt_allocate(flags, /* ret_peer_path= */ NULL); if (fd < 0) _exit(EXIT_FAILURE); @@ -1376,18 +1384,12 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) { pair[1] = safe_close(pair[1]); - r = wait_for_terminate_and_check("(sd-openptns)", child, 0); - if (r < 0) - return r; - if (r != EXIT_SUCCESS) - return -EIO; - fd = receive_one_fd(pair[0], 0); if (fd < 0) return fd; - if (ret_slave) { - r = ptsname_namespace(fd, ret_slave); + if (ret_peer_path) { + r = ptsname_namespace(fd, ret_peer_path); if (r < 0) return r; } @@ -1398,30 +1400,40 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) { int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF; _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - pid_t child; int r; - r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd); + assert(pid > 0); + assert(name); + + r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd= */ NULL, &usernsfd, &rootfd); if (r < 0) return r; - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0) return -errno; - r = namespace_fork("(sd-terminalns)", "(sd-terminal)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, - pidnsfd, mntnsfd, -1, usernsfd, rootfd, &child); + r = namespace_fork( + "(sd-terminalns)", + "(sd-terminal)", + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, + pidnsfd, + mntnsfd, + /* netnsd_fd= */ -EBADF, + usernsfd, + rootfd, + /* ret_pid= */ NULL); if (r < 0) return r; if (r == 0) { - int master; - pair[0] = safe_close(pair[0]); - master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC); - if (master < 0) + int pty_fd = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC); + if (pty_fd < 0) _exit(EXIT_FAILURE); - if (send_one_fd(pair[1], master, 0) < 0) + if (send_one_fd(pair[1], pty_fd, 0) < 0) _exit(EXIT_FAILURE); _exit(EXIT_SUCCESS); @@ -1429,12 +1441,6 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { pair[1] = safe_close(pair[1]); - r = wait_for_terminate_and_check("(sd-terminalns)", child, 0); - if (r < 0) - return r; - if (r != EXIT_SUCCESS) - return -EIO; - return receive_one_fd(pair[0], 0); } @@ -1923,12 +1929,12 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret if (tcsetattr(STDIN_FILENO, TCSADRAIN, &new_termios) < 0) return -errno; - r = loop_write(STDOUT_FILENO, "\x1B]11;?\x07", SIZE_MAX); + r = loop_write(STDOUT_FILENO, ANSI_OSC "11;?" ANSI_ST, SIZE_MAX); if (r < 0) goto finish; usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC); - char buf[STRLEN("\x1B]11;rgb:0/0/0\x07")]; /* shortest possible reply */ + char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */ size_t buf_full = 0; BackgroundColorContext context = {}; @@ -2278,3 +2284,60 @@ int terminal_is_pty_fd(int fd) { return true; } + +int pty_open_peer_racefree(int fd, int mode) { + assert(fd >= 0); + + /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13). + * + * This is safe to be called on TTYs from other namespaces. */ + + assert((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) == 0); + + /* This replicates the EIO retry logic of open_terminal() in a modified way. */ + for (unsigned c = 0;; c++) { + int peer_fd = ioctl(fd, TIOCGPTPEER, mode); + if (peer_fd >= 0) + return peer_fd; + + if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL) /* new ioctl() is not supported, return a clear error */ + return -EOPNOTSUPP; + + if (errno != EIO) + return -errno; + + /* Max 1s in total */ + if (c >= 20) + return -EIO; + + (void) usleep_safe(50 * USEC_PER_MSEC); + } +} + +int pty_open_peer(int fd, int mode) { + int r; + + assert(fd >= 0); + + /* Opens the peer PTY using the new race-free TIOCGPTPEER ioctl() (kernel 4.13) if it is + * available. Otherwise falls back to the POSIX ptsname() + open() logic. + * + * Because of the fallback path this is not safe to be called on PTYs from other namespaces. (Because + * we open the peer PTY name there via a path in the file system.) */ + + // TODO: Remove fallback path once baseline is updated to >= 4.13, i.e. systemd v258 + + int peer_fd = pty_open_peer_racefree(fd, mode); + if (peer_fd >= 0) + return peer_fd; + if (!ERRNO_IS_NEG_NOT_SUPPORTED(peer_fd)) + return peer_fd; + + /* The racy fallback path */ + _cleanup_free_ char *peer_path = NULL; + r = ptsname_malloc(fd, &peer_path); + if (r < 0) + return r; + + return open_terminal(peer_path, mode); +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index b446e547d6..c30faf168c 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -27,6 +27,16 @@ #define ANSI_WINDOW_TITLE_PUSH "\x1b[22;2t" #define ANSI_WINDOW_TITLE_POP "\x1b[23;2t" +/* ANSI "string terminator" character ("ST"). Terminal emulators typically allow three different ones: 0x07, + * 0x9c, and 0x1B 0x5C. We'll avoid 0x07 (BEL, aka ^G) since it might trigger unexpected TTY signal + * handling. And we'll avoid 0x9c since that's also valid regular codepoint in UTF-8 and elsewhere, and + * creates ambiguities. Because of that some terminal emulators explicitly choose not to support it. Hence we + * use 0x1B 0x5c */ +#define ANSI_ST "\e\\" + +/* The "operating system command" ("OSC") start sequence */ +#define ANSI_OSC "\e]" + bool isatty_safe(int fd); int terminal_reset_defensive(int fd, bool switch_to_text); @@ -144,3 +154,6 @@ int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, un int terminal_fix_size(int input_fd, int output_fd); int terminal_is_pty_fd(int fd); + +int pty_open_peer_racefree(int fd, int mode); +int pty_open_peer(int fd, int mode); diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c index 61d76dd679..c548ddcee5 100644 --- a/src/boot/bootctl-status.c +++ b/src/boot/bootctl-status.c @@ -843,7 +843,7 @@ int verb_list(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (config.n_entries == 0 && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (config.n_entries == 0 && !sd_json_format_enabled(arg_json_format_flags)) { log_info("No boot loader entries found."); return 0; } diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index a8c7b6881f..87b0232860 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1926,14 +1926,14 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { /* profile= */ UINT_MAX, /* validate_base= */ 0, &vector); - if (vector.memory_size != sizeof(SD_MAGIC)) + if (vector.memory_size != STRLEN(SD_MAGIC)) return false; err = file_handle_read(handle, vector.file_offset, vector.file_size, &content, &read); if (err != EFI_SUCCESS || vector.file_size != read) return false; - return memcmp(content, SD_MAGIC, sizeof(SD_MAGIC)) == 0; + return memcmp(content, SD_MAGIC, STRLEN(SD_MAGIC)) == 0; } static BootEntry* config_add_entry_loader_auto( diff --git a/src/boot/measure.c b/src/boot/measure.c index 38bc2afda5..3ad49e9d74 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -748,7 +748,7 @@ static int verb_calculate(int argc, char *argv[], void *userdata) { return r; for (size_t i = 0; i < n; i++) { - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(arg_json_format_flags)) { _cleanup_free_ char *hd = NULL; if (i == 0) { @@ -789,7 +789,7 @@ static int verb_calculate(int argc, char *argv[], void *userdata) { pcr_states_restore(pcr_states, n); } - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(arg_json_format_flags)) { if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) pager_open(arg_pager_flags); @@ -1106,7 +1106,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(arg_json_format_flags)) { _cleanup_free_ char *f = NULL; f = hexmem(h, l); @@ -1150,7 +1150,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { } } - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(arg_json_format_flags)) { if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) pager_open(arg_pager_flags); diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 1e352a3729..8971d8c28f 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -65,7 +65,7 @@ static bool arg_augment_creds = true; static bool arg_watch_bind = false; static usec_t arg_timeout = 0; static const char *arg_destination = NULL; -static uint64_t arg_num_matches = UINT64_MAX; +static uint64_t arg_limit_messages = UINT64_MAX; STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); @@ -1268,6 +1268,9 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f if (r < 0) return r; + usec_t end = arg_timeout > 0 ? + usec_add(now(CLOCK_MONOTONIC), arg_timeout) : USEC_INFINITY; + /* upgrade connection; it's not used for anything else after this call */ r = sd_bus_message_new_method_call(bus, &message, @@ -1331,7 +1334,7 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f if (r < 0) return log_error_errno(r, "Failed to get unique name: %m"); - if (!arg_quiet && arg_json_format_flags == SD_JSON_FORMAT_OFF) + if (!arg_quiet && !sd_json_format_enabled(arg_json_format_flags)) log_info("Monitoring bus message stream."); (void) sd_notify(/* unset_environment=false */ false, "READY=1"); @@ -1364,14 +1367,18 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f dump(m, stdout); fflush(stdout); - if (arg_num_matches != UINT64_MAX && --arg_num_matches == 0) { - if (!arg_quiet && arg_json_format_flags == SD_JSON_FORMAT_OFF) - log_info("Received requested number of matching messages, exiting."); - return 0; + if (arg_limit_messages != UINT64_MAX) { + arg_limit_messages--; + + if (arg_limit_messages == 0) { + if (!arg_quiet && !sd_json_format_enabled(arg_json_format_flags)) + log_info("Received requested maximum number of messages, exiting."); + return 0; + } } if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) { - if (!arg_quiet && arg_json_format_flags == SD_JSON_FORMAT_OFF) + if (!arg_quiet && !sd_json_format_enabled(arg_json_format_flags)) log_info("Connection terminated, exiting."); return 0; } @@ -1382,9 +1389,9 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f if (r > 0) continue; - r = sd_bus_wait(bus, arg_timeout > 0 ? arg_timeout : UINT64_MAX); - if (r == 0 && arg_timeout > 0) { - if (!arg_quiet && arg_json_format_flags == SD_JSON_FORMAT_OFF) + r = sd_bus_wait(bus, arg_timeout > 0 ? usec_sub_unsigned(end, now(CLOCK_MONOTONIC)) : UINT64_MAX); + if (r == 0 && arg_timeout > 0 && now(CLOCK_MONOTONIC) >= end) { + if (!arg_quiet && !sd_json_format_enabled(arg_json_format_flags)) log_info("Timed out waiting for messages, exiting."); return 0; } @@ -1394,7 +1401,7 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f } static int verb_monitor(int argc, char **argv, void *userdata) { - return monitor(argc, argv, (arg_json_format_flags & SD_JSON_FORMAT_OFF) ? message_dump : message_json); + return monitor(argc, argv, sd_json_format_enabled(arg_json_format_flags) ? message_json : message_dump); } static int verb_capture(int argc, char **argv, void *userdata) { @@ -2146,7 +2153,7 @@ static int call(int argc, char **argv, void *userdata) { if (r > 0 || arg_quiet) return 0; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) @@ -2256,7 +2263,7 @@ static int get_property(int argc, char **argv, void *userdata) { if (r < 0) return bus_log_parse_error(r); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) @@ -2448,9 +2455,9 @@ static int help(void) { pager_open(arg_pager_flags); - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sIntrospect the D-Bus IPC bus.%s\n" - "\nCommands:\n" + printf("%1$s [OPTIONS...] COMMAND ...\n\n" + "%5$sIntrospect the D-Bus IPC bus.%6$s\n" + "\n%3$sCommands%4$s:\n" " list List bus names\n" " status [SERVICE] Show bus service, process or bus owner credentials\n" " monitor [SERVICE...] Show bus traffic\n" @@ -2468,7 +2475,7 @@ static int help(void) { " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" " Set property value\n" " help Show this help\n" - "\nOptions:\n" + "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" @@ -2500,13 +2507,16 @@ static int help(void) { " --watch-bind=BOOL Wait for bus AF_UNIX socket to be bound in the file\n" " system\n" " --destination=SERVICE Destination service of a signal\n" - " --num-matches=NUMBER Exit after receiving a number of matches while\n" - " monitoring\n" - "\nSee the %s for details.\n", + " -N --limit-messages=NUMBER\n" + " Stop monitoring after receiving the specified number\n" + " of messages\n" + "\nSee the %2$s for details.\n", program_invocation_short_name, - ansi_highlight(), + link, + ansi_underline(), ansi_normal(), - link); + ansi_highlight(), + ansi_normal()); return 0; } @@ -2541,7 +2551,6 @@ static int parse_argv(int argc, char *argv[]) { ARG_WATCH_BIND, ARG_JSON, ARG_DESTINATION, - ARG_NUM_MATCHES, }; static const struct option options[] = { @@ -2574,7 +2583,7 @@ static int parse_argv(int argc, char *argv[]) { { "watch-bind", required_argument, NULL, ARG_WATCH_BIND }, { "json", required_argument, NULL, ARG_JSON }, { "destination", required_argument, NULL, ARG_DESTINATION }, - { "num-matches", required_argument, NULL, ARG_NUM_MATCHES }, + { "limit-messages", required_argument, NULL, 'N' }, {}, }; @@ -2583,7 +2592,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:C:J:qjl", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hH:M:C:J:qjlN:", options, NULL)) >= 0) switch (c) { @@ -2710,6 +2719,11 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TIMEOUT: + if (isempty(optarg)) { + arg_timeout = 0; /* Reset to default */ + break; + } + r = parse_sec(optarg, &arg_timeout); if (r < 0) return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", optarg); @@ -2743,12 +2757,17 @@ static int parse_argv(int argc, char *argv[]) { arg_destination = optarg; break; - case ARG_NUM_MATCHES: - r = safe_atou64(optarg, &arg_num_matches); + case 'N': + if (isempty(optarg)) { + arg_limit_messages = UINT64_MAX; /* Reset to default */ + break; + } + + r = safe_atou64(optarg, &arg_limit_messages); if (r < 0) - return log_error_errno(r, "Failed to parse --num-matches= parameter '%s': %m", optarg); - if (arg_num_matches == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--num-matches= parameter cannot be 0"); + return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", optarg); + if (arg_limit_messages == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--limit-messages= parameter cannot be 0"); break; diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 9e06785428..a9a73b599b 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -5,6 +5,7 @@ #include "af-list.h" #include "alloc-util.h" #include "bus-get-properties.h" +#include "bus-unit-util.h" #include "bus-util.h" #include "cap-list.h" #include "capability-util.h" @@ -61,6 +62,7 @@ static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_priority, "i", ExecContext, static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_string, "s", NULL); static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_tmp_ex, "s", PrivateTmp, private_tmp_to_string); static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_users_ex, "s", PrivateUsers, private_users_to_string); +static BUS_DEFINE_PROPERTY_GET_REF(property_get_protect_control_groups_ex, "s", ProtectControlGroups, protect_control_groups_to_string); static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_level, "i", int, LOG_PRI); static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_facility, "i", int, LOG_FAC); static BUS_DEFINE_PROPERTY_GET(property_get_cpu_affinity_from_numa, "b", ExecContext, exec_context_get_cpu_affinity_from_numa); @@ -980,11 +982,18 @@ static int property_get_exec_dir_symlink( return r; FOREACH_ARRAY(i, d->items, d->n_items) - STRV_FOREACH(dst, i->symlinks) { - r = sd_bus_message_append(reply, "(sst)", i->path, *dst, UINT64_C(0) /* flags, unused for now */); + if (strv_isempty(i->symlinks)) { + /* The old exec directory properties cannot represent flags, so list them here with no + * destination */ + r = sd_bus_message_append(reply, "(sst)", i->path, "", (uint64_t) (i->flags & _EXEC_DIRECTORY_FLAGS_PUBLIC)); if (r < 0) return r; - } + } else + STRV_FOREACH(dst, i->symlinks) { + r = sd_bus_message_append(reply, "(sst)", i->path, *dst, (uint64_t) (i->flags & _EXEC_DIRECTORY_FLAGS_PUBLIC)); + if (r < 0) + return r; + } return sd_bus_message_close_container(reply); } @@ -1043,6 +1052,21 @@ static int property_get_private_users( return sd_bus_message_append_basic(reply, 'b', &b); } +static int property_get_protect_control_groups( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ProtectControlGroups *p = ASSERT_PTR(userdata); + int b = *p != PROTECT_CONTROL_GROUPS_NO; + + return sd_bus_message_append_basic(reply, 'b', &b); +} + const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1163,7 +1187,8 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("ProtectKernelTunables", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_tunables), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectKernelModules", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_modules), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectKernelLogs", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_logs), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ProtectControlGroups", "b", bus_property_get_bool, offsetof(ExecContext, protect_control_groups), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectControlGroups", "b", property_get_protect_control_groups, offsetof(ExecContext, protect_control_groups), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectControlGroupsEx", "s", property_get_protect_control_groups_ex, offsetof(ExecContext, protect_control_groups), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateUsers", "b", property_get_private_users, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateUsersEx", "s", property_get_private_users_ex, offsetof(ExecContext, private_users), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1909,6 +1934,42 @@ int bus_exec_context_set_transient_property( return 1; } + if (streq(name, "ProtectControlGroups")) { + int v; + + r = sd_bus_message_read(message, "b", &v); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->protect_control_groups = v ? PROTECT_CONTROL_GROUPS_YES : PROTECT_CONTROL_GROUPS_NO; + (void) unit_write_settingf(u, flags, name, "%s=%s", name, yes_no(v)); + } + + return 1; + } + + if (streq(name, "ProtectControlGroupsEx")) { + const char *s; + ProtectControlGroups t; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + t = protect_control_groups_from_string(s); + if (t < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s setting: %s", name, s); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->protect_control_groups = t; + (void) unit_write_settingf(u, flags, name, "ProtectControlGroups=%s", + protect_control_groups_to_string(c->protect_control_groups)); + } + + return 1; + } + if (streq(name, "PrivateDevices")) return bus_set_transient_bool(u, name, &c->private_devices, message, flags, error); @@ -1960,9 +2021,6 @@ int bus_exec_context_set_transient_property( if (streq(name, "ProtectClock")) return bus_set_transient_bool(u, name, &c->protect_clock, message, flags, error); - if (streq(name, "ProtectControlGroups")) - return bus_set_transient_bool(u, name, &c->protect_control_groups, message, flags, error); - if (streq(name, "CPUSchedulingResetOnFork")) return bus_set_transient_bool(u, name, &c->cpu_sched_reset_on_fork, message, flags, error); @@ -3394,7 +3452,7 @@ int bus_exec_context_set_transient_property( _cleanup_free_ char *joined = NULL; STRV_FOREACH(source, l) { - r = exec_directory_add(d, *source, NULL); + r = exec_directory_add(d, *source, /* symlink= */ NULL, /* flags= */ 0); if (r < 0) return log_oom(); } @@ -3812,7 +3870,7 @@ int bus_exec_context_set_transient_property( } else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) { char *source, *destination; ExecDirectory *directory; - uint64_t symlink_flags; /* No flags for now, reserved for future uses. */ + uint64_t symlink_flags; ExecDirectoryType i; assert_se((i = exec_directory_type_symlink_from_string(name)) >= 0); @@ -3823,40 +3881,50 @@ int bus_exec_context_set_transient_property( return r; while ((r = sd_bus_message_read(message, "(sst)", &source, &destination, &symlink_flags)) > 0) { + if ((symlink_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid 'flags' parameter '%" PRIu64 "'", symlink_flags); if (!path_is_valid(source)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not valid.", source); if (path_is_absolute(source)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is absolute.", source); if (!path_is_normalized(source)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not normalized.", source); - if (!path_is_valid(destination)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not valid.", destination); - if (path_is_absolute(destination)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is absolute.", destination); - if (!path_is_normalized(destination)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination); - if (symlink_flags != 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero."); + if (isempty(destination)) + destination = NULL; + else { + if (!path_is_valid(destination)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not valid.", destination); + if (path_is_absolute(destination)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is absolute.", destination); + if (!path_is_normalized(destination)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination); + } if (!UNIT_WRITE_FLAGS_NOOP(flags)) { _cleanup_free_ char *destination_escaped = NULL, *source_escaped = NULL; - r = exec_directory_add(directory, source, destination); + r = exec_directory_add(directory, source, destination, symlink_flags); if (r < 0) return r; /* Need to store them in the unit with the escapes, so that they can be parsed again */ source_escaped = xescape(source, ":"); - destination_escaped = xescape(destination, ":"); - if (!source_escaped || !destination_escaped) + if (!source_escaped) return -ENOMEM; + if (destination) { + destination_escaped = xescape(destination, ":"); + if (!destination_escaped) + return -ENOMEM; + } unit_write_settingf( u, flags|UNIT_ESCAPE_SPECIFIERS, exec_directory_type_to_string(i), - "%s=%s:%s", + "%s=%s%s%s%s", exec_directory_type_to_string(i), source_escaped, - destination_escaped); + destination_escaped || FLAGS_SET(symlink_flags, EXEC_DIRECTORY_READ_ONLY) ? ":" : "", + destination_escaped, + FLAGS_SET(symlink_flags, EXEC_DIRECTORY_READ_ONLY) ? ":ro" : ""); } } if (r < 0) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index c71d6b4c2a..968e9bd7c4 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -2452,7 +2452,7 @@ static int setup_exec_directory( goto fail; } - if (!i->only_create) { + if (!FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)) { /* And link it up from the original place. * Notes * 1) If a mount namespace is going to be used, then this symlink remains on @@ -2474,7 +2474,7 @@ static int setup_exec_directory( } else { _cleanup_free_ char *target = NULL; - if (type != EXEC_DIRECTORY_CONFIGURATION && + if (EXEC_DIRECTORY_TYPE_SHALL_CHOWN(type) && readlink_and_make_absolute(p, &target) >= 0) { _cleanup_free_ char *q = NULL, *q_resolved = NULL, *target_resolved = NULL; @@ -2526,7 +2526,7 @@ static int setup_exec_directory( if (r != -EEXIST) goto fail; - if (type == EXEC_DIRECTORY_CONFIGURATION) { + if (!EXEC_DIRECTORY_TYPE_SHALL_CHOWN(type)) { struct stat st; /* Don't change the owner/access mode of the configuration directory, @@ -2643,7 +2643,7 @@ static int compile_bind_mounts( continue; FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items) - n += !i->only_create; + n += !FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE) || FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY); } if (n <= 0) { @@ -2691,8 +2691,10 @@ static int compile_bind_mounts( _cleanup_free_ char *s = NULL, *d = NULL; /* When one of the parent directories is in the list, we cannot create the symlink - * for the child directory. See also the comments in setup_exec_directory(). */ - if (i->only_create) + * for the child directory. See also the comments in setup_exec_directory(). + * But if it needs to be read only, then we have to create a bind mount anyway to + * make it so. */ + if (FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE) && !FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY)) continue; if (exec_directory_is_private(context, t)) @@ -2718,6 +2720,7 @@ static int compile_bind_mounts( .destination = TAKE_PTR(d), .nosuid = context->dynamic_user, /* don't allow suid/sgid when DynamicUser= is on */ .recursive = true, + .read_only = FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY), }; } } @@ -2766,7 +2769,7 @@ static int compile_symlinks( if (!exec_directory_is_private(context, dt) || exec_context_with_rootfs(context) || - i->only_create) + FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)) continue; private_path = path_join(params->prefix[dt], "private", i->path); @@ -3098,7 +3101,7 @@ static int apply_mount_namespace( /* We need to make the pressure path writable even if /sys/fs/cgroups is made read-only, as the * service will need to write to it in order to start the notifications. */ - if (context->protect_control_groups && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) { + if (exec_is_cgroup_mount_read_only(context, params) && memory_pressure_path && !streq(memory_pressure_path, "/dev/null")) { read_write_paths_cleanup = strv_copy(context->read_write_paths); if (!read_write_paths_cleanup) return -ENOMEM; @@ -3242,7 +3245,7 @@ static int apply_mount_namespace( * sandbox inside the mount namespace. */ .ignore_protect_paths = !needs_sandboxing && !context->dynamic_user && root_dir, - .protect_control_groups = needs_sandboxing && context->protect_control_groups, + .protect_control_groups = needs_sandboxing ? exec_get_protect_control_groups(context, params) : PROTECT_CONTROL_GROUPS_NO, .protect_kernel_tunables = needs_sandboxing && context->protect_kernel_tunables, .protect_kernel_modules = needs_sandboxing && context->protect_kernel_modules, .protect_kernel_logs = needs_sandboxing && context->protect_kernel_logs, @@ -3636,7 +3639,8 @@ static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p * directories. */ for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) { - if (t == EXEC_DIRECTORY_CONFIGURATION) + + if (!EXEC_DIRECTORY_TYPE_SHALL_CHOWN(t)) continue; if (!p->prefix[t]) @@ -3886,7 +3890,7 @@ static bool exec_context_need_unprivileged_private_users( context->protect_kernel_tunables || context->protect_kernel_modules || context->protect_kernel_logs || - context->protect_control_groups || + exec_needs_cgroup_mount(context, params) || context->protect_clock || context->protect_hostname || !strv_isempty(context->read_write_paths) || @@ -4580,6 +4584,10 @@ int exec_invoke( } } + /* We need sandboxing if the caller asked us to apply it and the command isn't explicitly excepted + * from it. */ + needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command->flags & EXEC_COMMAND_FULLY_PRIVILEGED); + if (params->cgroup_path) { /* If delegation is enabled we'll pass ownership of the cgroup to the user of the new process. On cgroup v1 * this is only about systemd's own hierarchy, i.e. not the controller hierarchies, simply because that's not @@ -4623,6 +4631,18 @@ int exec_invoke( "Failed to adjust ownership of '%s', ignoring: %m", memory_pressure_path); memory_pressure_path = mfree(memory_pressure_path); } + /* First we use the current cgroup path to chmod and chown the memory pressure path, then pass the path relative + * to the cgroup namespace to environment variables and mounts. If chown/chmod fails, we should not pass memory + * pressure path environment variable or read-write mount to the unit. This is why we check if + * memory_pressure_path != NULL in the conditional below. */ + if (memory_pressure_path && needs_sandboxing && exec_needs_cgroup_namespace(context, params)) { + memory_pressure_path = mfree(memory_pressure_path); + r = cg_get_path("memory", "", "memory.pressure", &memory_pressure_path); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return log_oom(); + } + } } else if (cgroup_context->memory_pressure_watch == CGROUP_PRESSURE_WATCH_NO) { memory_pressure_path = strdup("/dev/null"); /* /dev/null is explicit indicator for turning of memory pressure watch */ if (!memory_pressure_path) { @@ -4709,10 +4729,6 @@ int exec_invoke( return log_exec_error_errno(context, params, r, "Failed to set up kernel keyring: %m"); } - /* We need sandboxing if the caller asked us to apply it and the command isn't explicitly excepted - * from it. */ - needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command->flags & EXEC_COMMAND_FULLY_PRIVILEGED); - /* We need the ambient capability hack, if the caller asked us to apply it and the command is marked * for it, and the kernel doesn't actually support ambient caps. */ needs_ambient_hack = (params->flags & EXEC_APPLY_SANDBOXING) && (command->flags & EXEC_COMMAND_AMBIENT_MAGIC) && !ambient_capabilities_supported(); @@ -4853,6 +4869,14 @@ int exec_invoke( log_exec_warning(context, params, "PrivateIPC=yes is configured, but the kernel does not support IPC namespaces, ignoring."); } + if (needs_sandboxing && exec_needs_cgroup_namespace(context, params)) { + r = unshare(CLONE_NEWCGROUP); + if (r < 0) { + *exit_status = EXIT_NAMESPACE; + return log_exec_error_errno(context, params, r, "Failed to set up cgroup namespacing: %m"); + } + } + if (needs_mount_namespace) { _cleanup_free_ char *error_path = NULL; diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index 1b44c49238..6fa0b21968 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -1910,7 +1910,7 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; - r = serialize_bool_elide(f, "exec-context-protect-control-groups", c->protect_control_groups); + r = serialize_item(f, "exec-context-protect-control-groups", protect_control_groups_to_string(c->protect_control_groups)); if (r < 0) return r; @@ -1998,7 +1998,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (!strextend(&value, " ", path_escaped)) return log_oom_debug(); - if (!strextend(&value, ":", yes_no(i->only_create))) + if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_ONLY_CREATE)))) + return log_oom_debug(); + + if (!strextend(&value, ":", yes_no(FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY)))) return log_oom_debug(); STRV_FOREACH(d, i->symlinks) { @@ -2792,7 +2795,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { return r; c->protect_clock = r; } else if ((val = startswith(l, "exec-context-protect-control-groups="))) { - r = parse_boolean(val); + r = protect_control_groups_from_string(val); if (r < 0) return r; c->protect_control_groups = r; @@ -2893,7 +2896,8 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { return r; for (;;) { - _cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL; + _cleanup_free_ char *tuple = NULL, *path = NULL, *only_create = NULL, *read_only = NULL; + ExecDirectoryFlags exec_directory_flags = 0; const char *p; /* Use EXTRACT_UNESCAPE_RELAX here, as we unescape the colons in subsequent calls */ @@ -2904,20 +2908,27 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { break; p = tuple; - r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create); + r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create, &read_only); if (r < 0) return r; if (r < 2) continue; - r = exec_directory_add(&c->directories[dt], path, NULL); + r = parse_boolean(only_create); + if (r < 0) + return r; + if (r > 0) + exec_directory_flags |= EXEC_DIRECTORY_ONLY_CREATE; + + r = parse_boolean(read_only); if (r < 0) return r; + if (r > 0) + exec_directory_flags |= EXEC_DIRECTORY_READ_ONLY; - r = parse_boolean(only_create); + r = exec_directory_add(&c->directories[dt], path, /* symlink= */ NULL, exec_directory_flags); if (r < 0) return r; - c->directories[dt].items[c->directories[dt].n_items - 1].only_create = r; if (isempty(p)) continue; diff --git a/src/core/execute.c b/src/core/execute.c index 0c2c278d69..1c41b39a2f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -210,6 +210,50 @@ bool exec_needs_ipc_namespace(const ExecContext *context) { return context->private_ipc || context->ipc_namespace_path; } +static bool can_apply_cgroup_namespace(const ExecContext *context, const ExecParameters *params) { + return cg_all_unified() > 0 && ns_type_supported(NAMESPACE_CGROUP); +} + +static bool needs_cgroup_namespace(ProtectControlGroups i) { + return IN_SET(i, PROTECT_CONTROL_GROUPS_PRIVATE, PROTECT_CONTROL_GROUPS_STRICT); +} + +ProtectControlGroups exec_get_protect_control_groups(const ExecContext *context, const ExecParameters *params) { + assert(context); + + /* If cgroup namespace is configured via ProtectControlGroups=private or strict but we can't actually + * use cgroup namespace, either from not having unified hierarchy or kernel support, we ignore the + * setting and do not unshare the namespace. ProtectControlGroups=private and strict get downgraded + * to no and yes respectively. This ensures that strict always gets a read-only mount of /sys/fs/cgroup. + * + * TODO: Remove fallback once cgroupv1 support is removed in v258. */ + if (needs_cgroup_namespace(context->protect_control_groups) && !can_apply_cgroup_namespace(context, params)) { + if (context->protect_control_groups == PROTECT_CONTROL_GROUPS_PRIVATE) + return PROTECT_CONTROL_GROUPS_NO; + if (context->protect_control_groups == PROTECT_CONTROL_GROUPS_STRICT) + return PROTECT_CONTROL_GROUPS_YES; + } + return context->protect_control_groups; +} + +bool exec_needs_cgroup_namespace(const ExecContext *context, const ExecParameters *params) { + assert(context); + + return needs_cgroup_namespace(exec_get_protect_control_groups(context, params)); +} + +bool exec_needs_cgroup_mount(const ExecContext *context, const ExecParameters *params) { + assert(context); + + return exec_get_protect_control_groups(context, params) != PROTECT_CONTROL_GROUPS_NO; +} + +bool exec_is_cgroup_mount_read_only(const ExecContext *context, const ExecParameters *params) { + assert(context); + + return IN_SET(exec_get_protect_control_groups(context, params), PROTECT_CONTROL_GROUPS_YES, PROTECT_CONTROL_GROUPS_STRICT); +} + bool exec_needs_mount_namespace( const ExecContext *context, const ExecParameters *params, @@ -259,7 +303,7 @@ bool exec_needs_mount_namespace( context->protect_kernel_tunables || context->protect_kernel_modules || context->protect_kernel_logs || - context->protect_control_groups || + exec_needs_cgroup_mount(context, params) || context->protect_proc != PROTECT_PROC_DEFAULT || context->proc_subset != PROC_SUBSET_ALL || exec_needs_ipc_namespace(context)) @@ -287,6 +331,11 @@ bool exec_needs_mount_namespace( if (exec_context_get_effective_bind_log_sockets(context)) return true; + for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) + FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items) + if (FLAGS_SET(i->flags, EXEC_DIRECTORY_READ_ONLY)) + return true; + return false; } @@ -296,7 +345,7 @@ bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType typ if (!context->dynamic_user) return false; - if (type == EXEC_DIRECTORY_CONFIGURATION) + if (!EXEC_DIRECTORY_TYPE_SHALL_CHOWN(type)) return false; if (type == EXEC_DIRECTORY_RUNTIME && context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO) @@ -1000,7 +1049,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { prefix, yes_no(c->protect_kernel_modules), prefix, yes_no(c->protect_kernel_logs), prefix, yes_no(c->protect_clock), - prefix, yes_no(c->protect_control_groups), + prefix, protect_control_groups_to_string(c->protect_control_groups), prefix, yes_no(c->private_network), prefix, private_users_to_string(c->private_users), prefix, protect_home_to_string(c->protect_home), @@ -1074,7 +1123,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { fprintf(f, "%s%sMode: %04o\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].mode); for (size_t i = 0; i < c->directories[dt].n_items; i++) { - fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].items[i].path); + fprintf(f, + "%s%s: %s%s\n", + prefix, + exec_directory_type_to_string(dt), + c->directories[dt].items[i].path, + FLAGS_SET(c->directories[dt].items[i].flags, EXEC_DIRECTORY_READ_ONLY) ? " (ro)" : ""); STRV_FOREACH(d, c->directories[dt].items[i].symlinks) fprintf(f, "%s%s: %s:%s\n", prefix, exec_directory_type_symlink_to_string(dt), c->directories[dt].items[i].path, *d); @@ -1595,7 +1649,7 @@ int exec_context_get_clean_directories( return r; /* Also remove private directories unconditionally. */ - if (t != EXEC_DIRECTORY_CONFIGURATION) { + if (EXEC_DIRECTORY_TYPE_SHALL_CHOWN(t)) { j = path_join(prefix[t], "private", i->path); if (!j) return -ENOMEM; @@ -2687,7 +2741,7 @@ static ExecDirectoryItem *exec_directory_find(ExecDirectory *d, const char *path return NULL; } -int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) { +int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink, ExecDirectoryFlags flags) { _cleanup_strv_free_ char **s = NULL; _cleanup_free_ char *p = NULL; ExecDirectoryItem *existing; @@ -2702,6 +2756,8 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) if (r < 0) return r; + existing->flags |= flags; + return 0; /* existing item is updated */ } @@ -2721,6 +2777,7 @@ int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink) d->items[d->n_items++] = (ExecDirectoryItem) { .path = TAKE_PTR(p), .symlinks = TAKE_PTR(s), + .flags = flags, }; return 1; /* new item is added */ @@ -2738,7 +2795,7 @@ void exec_directory_sort(ExecDirectory *d) { /* Sort the exec directories to make always parent directories processed at first in * setup_exec_directory(), e.g., even if StateDirectory=foo/bar foo, we need to create foo at first, - * then foo/bar. Also, set .only_create flag if one of the parent directories is contained in the + * then foo/bar. Also, set the ONLY_CREATE flag if one of the parent directories is contained in the * list. See also comments in setup_exec_directory() and issue #24783. */ if (d->n_items <= 1) @@ -2749,7 +2806,7 @@ void exec_directory_sort(ExecDirectory *d) { for (size_t i = 1; i < d->n_items; i++) for (size_t j = 0; j < i; j++) if (path_startswith(d->items[i].path, d->items[j].path)) { - d->items[i].only_create = true; + d->items[i].flags |= EXEC_DIRECTORY_ONLY_CREATE; break; } } diff --git a/src/core/execute.h b/src/core/execute.h index 29c08d48b9..2f5abc9785 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -15,6 +15,7 @@ typedef struct Manager Manager; #include <stdio.h> #include <sys/capability.h> +#include "bus-unit-util.h" #include "cgroup-util.h" #include "coredump-util.h" #include "cpu-set-util.h" @@ -152,10 +153,16 @@ typedef enum ExecDirectoryType { _EXEC_DIRECTORY_TYPE_INVALID = -EINVAL, } ExecDirectoryType; +static inline bool EXEC_DIRECTORY_TYPE_SHALL_CHOWN(ExecDirectoryType t) { + /* Returns true for the ExecDirectoryTypes that we shall chown()ing for the user to. We do this for + * all of them, except for configuration */ + return t >= 0 && t < _EXEC_DIRECTORY_TYPE_MAX && t != EXEC_DIRECTORY_CONFIGURATION; +} + typedef struct ExecDirectoryItem { char *path; char **symlinks; - bool only_create; + ExecDirectoryFlags flags; } ExecDirectoryItem; typedef struct ExecDirectory { @@ -324,7 +331,7 @@ struct ExecContext { bool protect_kernel_modules; bool protect_kernel_logs; bool protect_clock; - bool protect_control_groups; + ProtectControlGroups protect_control_groups; ProtectSystem protect_system; ProtectHome protect_home; bool protect_hostname; @@ -579,7 +586,7 @@ void exec_params_deep_clear(ExecParameters *p); bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c); void exec_directory_done(ExecDirectory *d); -int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink); +int exec_directory_add(ExecDirectory *d, const char *path, const char *symlink, ExecDirectoryFlags flags); void exec_directory_sort(ExecDirectory *d); bool exec_directory_is_private(const ExecContext *context, ExecDirectoryType type); @@ -616,6 +623,11 @@ bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters bool exec_needs_network_namespace(const ExecContext *context); bool exec_needs_ipc_namespace(const ExecContext *context); +ProtectControlGroups exec_get_protect_control_groups(const ExecContext *context, const ExecParameters *params); +bool exec_needs_cgroup_namespace(const ExecContext *context, const ExecParameters *params); +bool exec_needs_cgroup_mount(const ExecContext *context, const ExecParameters *params); +bool exec_is_cgroup_mount_read_only(const ExecContext *context, const ExecParameters *params); + /* These logging macros do the same logging as those in unit.h, but using ExecContext and ExecParameters * instead of the unit object, so that it can be used in the sd-executor context (where the unit object is * not available). */ diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index ea82a578ba..f5cbb319d7 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -125,7 +125,7 @@ {{type}}.ProtectKernelModules, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_modules) {{type}}.ProtectKernelLogs, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_logs) {{type}}.ProtectClock, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_clock) -{{type}}.ProtectControlGroups, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_control_groups) +{{type}}.ProtectControlGroups, config_parse_protect_control_groups, 0, offsetof({{type}}, exec_context.protect_control_groups) {{type}}.NetworkNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.network_namespace_path) {{type}}.IPCNamespacePath, config_parse_unit_path_printf, 0, offsetof({{type}}, exec_context.ipc_namespace_path) {{type}}.LogNamespace, config_parse_log_namespace, 0, offsetof({{type}}, exec_context) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index fa8909e1d1..1d813332b1 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -135,6 +135,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc); DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset); DEFINE_CONFIG_PARSE_ENUM(config_parse_private_tmp, private_tmp, PrivateTmp); DEFINE_CONFIG_PARSE_ENUM(config_parse_private_users, private_users, PrivateUsers); +DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_control_groups, protect_control_groups, ProtectControlGroups); DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess); @@ -4697,9 +4698,9 @@ int config_parse_exec_directories( if (r == 0) return 0; - _cleanup_free_ char *src = NULL, *dest = NULL; + _cleanup_free_ char *src = NULL, *dest = NULL, *flags = NULL; const char *q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS, &src, &dest, &flags); if (r == -ENOMEM) return log_oom(); if (r <= 0) { @@ -4726,20 +4727,20 @@ int config_parse_exec_directories( continue; } + if (!isempty(dest) && streq(lvalue, "ConfigurationDirectory")) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Additional parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple); + continue; + } + /* For State and Runtime directories we support an optional destination parameter, which * will be used to create a symlink to the source. */ _cleanup_free_ char *dresolved = NULL; if (!isempty(dest)) { - if (streq(lvalue, "ConfigurationDirectory")) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Destination parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple); - continue; - } - r = unit_path_printf(u, dest, &dresolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest); + "Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest); continue; } @@ -4748,7 +4749,14 @@ int config_parse_exec_directories( continue; } - r = exec_directory_add(ed, sresolved, dresolved); + ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags); + if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid flags for %s=, ignoring: %s", lvalue, flags); + continue; + } + + r = exec_directory_add(ed, sresolved, dresolved, exec_directory_flags); if (r < 0) return log_oom(); } diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index e8b2eaee52..9b95f0c24e 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -114,6 +114,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv); CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems); CONFIG_PARSER_PROTOTYPE(config_parse_private_tmp); CONFIG_PARSER_PROTOTYPE(config_parse_private_users); +CONFIG_PARSER_PROTOTYPE(config_parse_protect_control_groups); CONFIG_PARSER_PROTOTYPE(config_parse_cpu_quota); CONFIG_PARSER_PROTOTYPE(config_parse_allowed_cpuset); CONFIG_PARSER_PROTOTYPE(config_parse_protect_home); diff --git a/src/core/mount.c b/src/core/mount.c index 19dd09c58f..92e0d7737a 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -876,6 +876,9 @@ static int mount_spawn(Mount *m, ExecCommand *c, PidRef *ret_pid) { if (r < 0) return r; + /* Assume the label inherited from systemd as the fallback */ + exec_params.fallback_smack_process_label = NULL; + r = exec_spawn(UNIT(m), c, &m->exec_context, diff --git a/src/core/namespace.c b/src/core/namespace.c index c45be457a8..a0d3dc0cbb 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -65,6 +65,7 @@ typedef enum MountMode { MOUNT_PRIVATE_SYSFS, MOUNT_BIND_SYSFS, MOUNT_PROCFS, + MOUNT_PRIVATE_CGROUP2FS, MOUNT_READ_ONLY, MOUNT_READ_WRITE, MOUNT_NOEXEC, @@ -199,6 +200,22 @@ static const MountEntry protect_home_yes_table[] = { { "/root", MOUNT_INACCESSIBLE, true }, }; +/* ProtectControlGroups=yes table */ +static const MountEntry protect_control_groups_yes_table[] = { + { "/sys/fs/cgroup", MOUNT_READ_ONLY, false }, +}; + +/* ProtectControlGroups=private table. Note mount_private_apivfs() always use MS_NOSUID|MS_NOEXEC|MS_NODEV so + * flags is not set here. nsdelegate has been supported since kernels >= 4.13 so it is safe to use. */ +static const MountEntry protect_control_groups_private_table[] = { + { "/sys/fs/cgroup", MOUNT_PRIVATE_CGROUP2FS, false, .read_only = false, .nosuid = true, .noexec = true, .options_const = "nsdelegate" }, +}; + +/* ProtectControlGroups=strict table */ +static const MountEntry protect_control_groups_strict_table[] = { + { "/sys/fs/cgroup", MOUNT_PRIVATE_CGROUP2FS, false, .read_only = true, .nosuid = true, .noexec = true, .options_const = "nsdelegate" }, +}; + /* ProtectSystem=yes table */ static const MountEntry protect_system_yes_table[] = { { "/usr", MOUNT_READ_ONLY, false }, @@ -247,6 +264,7 @@ static const char * const mount_mode_table[_MOUNT_MODE_MAX] = { [MOUNT_EMPTY_DIR] = "empty-dir", [MOUNT_PRIVATE_SYSFS] = "private-sysfs", [MOUNT_BIND_SYSFS] = "bind-sysfs", + [MOUNT_PRIVATE_CGROUP2FS] = "private-cgroup2fs", [MOUNT_PROCFS] = "procfs", [MOUNT_READ_ONLY] = "read-only", [MOUNT_READ_WRITE] = "read-write", @@ -727,6 +745,28 @@ static int append_static_mounts(MountList *ml, const MountEntry *mounts, size_t return 0; } +static int append_protect_control_groups(MountList *ml, ProtectControlGroups protect_control_groups, bool ignore_protect) { + assert(ml); + + switch (protect_control_groups) { + + case PROTECT_CONTROL_GROUPS_NO: + return 0; + + case PROTECT_CONTROL_GROUPS_YES: + return append_static_mounts(ml, protect_control_groups_yes_table, ELEMENTSOF(protect_control_groups_yes_table), ignore_protect); + + case PROTECT_CONTROL_GROUPS_PRIVATE: + return append_static_mounts(ml, protect_control_groups_private_table, ELEMENTSOF(protect_control_groups_private_table), ignore_protect); + + case PROTECT_CONTROL_GROUPS_STRICT: + return append_static_mounts(ml, protect_control_groups_strict_table, ELEMENTSOF(protect_control_groups_strict_table), ignore_protect); + + default: + assert_not_reached(); + } +} + static int append_protect_home(MountList *ml, ProtectHome protect_home, bool ignore_protect) { assert(ml); @@ -1269,10 +1309,14 @@ static int mount_private_apivfs( r = mount_nofollow_verbose(LOG_DEBUG, fstype, temporary_mount, fstype, MS_NOSUID|MS_NOEXEC|MS_NODEV, opts); if (r == -EINVAL && opts) - /* If this failed with EINVAL then this likely means the textual hidepid= stuff for procfs is - * not supported by the kernel, and thus the per-instance hidepid= neither, which means we - * really don't want to use it, since it would affect our host's /proc mount. Hence let's - * gracefully fallback to a classic, unrestricted version. */ + /* If this failed with EINVAL then this likely means either: + * 1. the textual hidepid= stuff for procfs is not supported by the kernel, and thus the + * per-instance hidepid= neither, which means we really don't want to use it, since it + * would affect our host's /proc mount. + * 2. nsdelegate for cgroup2 is not supported by the kernel even though CLONE_NEWCGROUP + * is supported. + * + * Hence let's gracefully fallback to a classic, unrestricted version. */ r = mount_nofollow_verbose(LOG_DEBUG, fstype, temporary_mount, fstype, MS_NOSUID|MS_NOEXEC|MS_NODEV, /* opts = */ NULL); if (ERRNO_IS_NEG_PRIVILEGE(r)) { /* When we do not have enough privileges to mount a new instance, fall back to use an @@ -1318,6 +1362,39 @@ static int mount_private_sysfs(const MountEntry *m, const NamespaceParameters *p return mount_private_apivfs("sysfs", mount_entry_path(m), "/sys", /* opts = */ NULL, p->runtime_scope); } +static bool check_recursiveprot_supported(void) { + int r; + + /* memory_recursiveprot is only supported for kernels >= 5.7. Note mount_option_supported uses fsopen() + * and fsconfig() which are supported for kernels >= 5.2. So if mount_option_supported() returns an + * error, we can assume memory_recursiveprot is not supported. */ + r = mount_option_supported("cgroup2", "memory_recursiveprot", NULL); + if (r < 0) + log_debug_errno(r, "Failed to determine whether the 'memory_recursiveprot' mount option is supported, assuming not: %m"); + else if (r == 0) + log_debug("This kernel version does not support 'memory_recursiveprot', not using mount option."); + + return r > 0; +} + +static int mount_private_cgroup2fs(const MountEntry *m, const NamespaceParameters *p) { + _cleanup_free_ char *opts = NULL; + + assert(m); + assert(p); + + if (check_recursiveprot_supported()) { + opts = strdup(strempty(mount_entry_options(m))); + if (!opts) + return -ENOMEM; + + if (!strextend_with_separator(&opts, ",", "memory_recursiveprot")) + return -ENOMEM; + } + + return mount_private_apivfs("cgroup2", mount_entry_path(m), "/sys/fs/cgroup", opts ?: mount_entry_options(m), p->runtime_scope); +} + static int mount_procfs(const MountEntry *m, const NamespaceParameters *p) { _cleanup_free_ char *opts = NULL; @@ -1763,6 +1840,9 @@ static int apply_one_mount( case MOUNT_PROCFS: return mount_procfs(m, p); + case MOUNT_PRIVATE_CGROUP2FS: + return mount_private_cgroup2fs(m, p); + case MOUNT_RUN: return mount_run(m); @@ -1933,7 +2013,7 @@ static bool namespace_parameters_mount_apivfs(const NamespaceParameters *p) { */ return p->mount_apivfs || - p->protect_control_groups || + p->protect_control_groups != PROTECT_CONTROL_GROUPS_NO || p->protect_kernel_tunables || p->protect_proc != PROTECT_PROC_DEFAULT || p->proc_subset != PROC_SUBSET_ALL; @@ -2490,16 +2570,9 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) { return r; } - if (p->protect_control_groups) { - MountEntry *me = mount_list_extend(&ml); - if (!me) - return log_oom_debug(); - - *me = (MountEntry) { - .path_const = "/sys/fs/cgroup", - .mode = MOUNT_READ_ONLY, - }; - } + r = append_protect_control_groups(&ml, p->protect_control_groups, false); + if (r < 0) + return r; r = append_protect_home(&ml, p->protect_home, p->ignore_protect_paths); if (r < 0) @@ -3195,6 +3268,15 @@ static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = { DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_system, ProtectSystem, PROTECT_SYSTEM_YES); +static const char *const protect_control_groups_table[_PROTECT_CONTROL_GROUPS_MAX] = { + [PROTECT_CONTROL_GROUPS_NO] = "no", + [PROTECT_CONTROL_GROUPS_YES] = "yes", + [PROTECT_CONTROL_GROUPS_PRIVATE] = "private", + [PROTECT_CONTROL_GROUPS_STRICT] = "strict", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_control_groups, ProtectControlGroups, PROTECT_CONTROL_GROUPS_YES); + static const char* const namespace_type_table[] = { [NAMESPACE_MOUNT] = "mnt", [NAMESPACE_CGROUP] = "cgroup", diff --git a/src/core/namespace.h b/src/core/namespace.h index 2c8f7987fe..fa4f7c7cf6 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -69,6 +69,15 @@ typedef enum PrivateUsers { _PRIVATE_USERS_INVALID = -EINVAL, } PrivateUsers; +typedef enum ProtectControlGroups { + PROTECT_CONTROL_GROUPS_NO, + PROTECT_CONTROL_GROUPS_YES, + PROTECT_CONTROL_GROUPS_PRIVATE, + PROTECT_CONTROL_GROUPS_STRICT, + _PROTECT_CONTROL_GROUPS_MAX, + _PROTECT_CONTROL_GROUPS_INVALID = -EINVAL, +} ProtectControlGroups; + struct BindMount { char *source; char *destination; @@ -151,7 +160,6 @@ struct NamespaceParameters { bool ignore_protect_paths; - bool protect_control_groups; bool protect_kernel_tunables; bool protect_kernel_modules; bool protect_kernel_logs; @@ -165,6 +173,7 @@ struct NamespaceParameters { bool bind_log_sockets; bool mount_nosuid; + ProtectControlGroups protect_control_groups; ProtectHome protect_home; ProtectSystem protect_system; ProtectProc protect_proc; @@ -210,6 +219,9 @@ PrivateTmp private_tmp_from_string(const char *s) _pure_; const char* private_users_to_string(PrivateUsers i) _const_; PrivateUsers private_users_from_string(const char *s) _pure_; +const char* protect_control_groups_to_string(ProtectControlGroups i) _const_; +ProtectControlGroups protect_control_groups_from_string(const char *s) _pure_; + void bind_mount_free_many(BindMount *b, size_t n); int bind_mount_add(BindMount **b, size_t *n, const BindMount *item); diff --git a/src/core/service.c b/src/core/service.c index 8cea17f853..737dc9905a 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -4551,6 +4551,72 @@ static bool service_notify_message_authorized(Service *s, PidRef *pid) { } } +static int service_notify_message_parse_new_pid( + Unit *u, + char * const *tags, + FDSet *fds, + PidRef *ret) { + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + const char *e; + int r; + + assert(u); + assert(ret); + + /* MAINPIDFD=1 always takes precedence */ + if (strv_contains(tags, "MAINPIDFD=1")) { + unsigned n_fds = fdset_size(fds); + if (n_fds != 1) + return log_unit_warning_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Got MAINPIDFD=1 with %s fd, ignoring.", n_fds == 0 ? "no" : "more than one"); + + r = pidref_set_pidfd_consume(&pidref, ASSERT_FD(fdset_steal_first(fds))); + if (r < 0) + return log_unit_warning_errno(u, r, "Failed to create reference to received new main pidfd: %m"); + + goto finish; + } + + e = strv_find_startswith(tags, "MAINPID="); + if (!e) { + *ret = PIDREF_NULL; + return 0; + } + + r = pidref_set_pidstr(&pidref, e); + if (r < 0) + return log_unit_warning_errno(u, r, "Failed to parse MAINPID=%s field in notification message, ignoring: %m", e); + + e = strv_find_startswith(tags, "MAINPIDFDID="); + if (!e) + goto finish; + + uint64_t pidfd_id; + + r = safe_atou64(e, &pidfd_id); + if (r < 0) + return log_unit_warning_errno(u, r, "Failed to parse MAINPIDFDID= in notification message, refusing: %s", e); + + r = pidref_acquire_pidfd_id(&pidref); + if (r < 0) { + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + log_unit_warning_errno(u, r, + "Failed to acquire pidfd id of process " PID_FMT ", not validating MAINPIDFDID=%" PRIu64 ": %m", + pidref.pid, pidfd_id); + goto finish; + } + + if (pidref.fd_id != pidfd_id) + return log_unit_warning_errno(u, SYNTHETIC_ERRNO(ESRCH), + "PIDFD ID of process " PID_FMT " (%" PRIu64 ") mismatches with received MAINPIDFDID=%" PRIu64 ", not changing main PID.", + pidref.pid, pidref.fd_id, pidfd_id); + +finish: + *ret = TAKE_PIDREF(pidref); + return 1; +} + static void service_notify_message( Unit *u, PidRef *pidref, @@ -4576,38 +4642,34 @@ static void service_notify_message( bool notify_dbus = false; const char *e; - /* Interpret MAINPID= */ - e = strv_find_startswith(tags, "MAINPID="); - if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, - SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, - SERVICE_STOP, SERVICE_STOP_SIGTERM)) { - - _cleanup_(pidref_done) PidRef new_main_pid = PIDREF_NULL; + /* Interpret MAINPID= (+ MAINPIDFDID=) / MAINPIDFD=1 */ + _cleanup_(pidref_done) PidRef new_main_pid = PIDREF_NULL; - r = pidref_set_pidstr(&new_main_pid, e); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to parse MAINPID=%s field in notification message, ignoring: %m", e); - else if (!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid)) { + r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid); + if (r > 0 && + IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_SIGTERM) && + (!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) { - r = service_is_suitable_main_pid(s, &new_main_pid, LOG_WARNING); - if (r == 0) { - /* The new main PID is a bit suspicious, which is OK if the sender is privileged. */ + r = service_is_suitable_main_pid(s, &new_main_pid, LOG_WARNING); + if (r == 0) { + /* The new main PID is a bit suspicious, which is OK if the sender is privileged. */ - if (ucred->uid == 0) { - log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, but we'll accept it as the request to change it came from a privileged process.", new_main_pid.pid); - r = 1; - } else - log_unit_warning(u, "New main PID "PID_FMT" does not belong to service, refusing.", new_main_pid.pid); - } - if (r > 0) { - (void) service_set_main_pidref(s, TAKE_PIDREF(new_main_pid), /* start_timestamp = */ NULL); + if (ucred->uid == 0) { + log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, but we'll accept it as the request to change it came from a privileged process.", new_main_pid.pid); + r = 1; + } else + log_unit_warning(u, "New main PID "PID_FMT" does not belong to service, refusing.", new_main_pid.pid); + } + if (r > 0) { + (void) service_set_main_pidref(s, TAKE_PIDREF(new_main_pid), /* start_timestamp = */ NULL); - r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false); - if (r < 0) - log_unit_warning_errno(UNIT(s), r, "Failed to watch new main PID "PID_FMT" for service: %m", s->main_pid.pid); + r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false); + if (r < 0) + log_unit_warning_errno(UNIT(s), r, "Failed to watch new main PID "PID_FMT" for service: %m", s->main_pid.pid); - notify_dbus = true; - } + notify_dbus = true; } } diff --git a/src/core/socket.c b/src/core/socket.c index 9b99ab2c35..e4a19285c9 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2024,6 +2024,14 @@ static int socket_chown(Socket *s, PidRef *ret_pid) { path = socket_address_get_path(&p->address); else if (p->type == SOCKET_FIFO) path = p->path; + else if (p->type == SOCKET_MQUEUE) { + /* Use fchown on the fd since /dev/mqueue might not be mounted. */ + if (fchown(p->fd, uid, gid) < 0) { + log_unit_error_errno(UNIT(s), errno, "Failed to fchown(): %m"); + _exit(EXIT_CHOWN); + } + continue; + } if (!path) continue; diff --git a/src/core/swap.c b/src/core/swap.c index ff6c4255ab..312db05db5 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -642,6 +642,9 @@ static int swap_spawn(Swap *s, ExecCommand *c, PidRef *ret_pid) { if (r < 0) return r; + /* Assume the label inherited from systemd as the fallback */ + exec_params.fallback_smack_process_label = NULL; + r = exec_spawn(UNIT(s), c, &s->exec_context, diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 722b5b7529..6970a6a898 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -2,11 +2,15 @@ #include <errno.h> #include <stdio.h> +#include <sys/mount.h> #include <sys/prctl.h> #include <sys/statvfs.h> #include <sys/auxv.h> #include <sys/xattr.h> #include <unistd.h> +#if WANT_LINUX_FS_H +# include <linux/fs.h> +#endif #include "sd-daemon.h" #include "sd-journal.h" @@ -86,6 +90,8 @@ * size. See DATA_SIZE_MAX in journal-importer.h. */ assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX); +#define MOUNT_TREE_ROOT "/run/systemd/mount-rootfs" + enum { /* We use these as array indexes for our process metadata cache. * @@ -134,15 +140,28 @@ static const char * const meta_field_names[_META_MAX] = { }; typedef struct Context { - const char *meta[_META_MAX]; - size_t meta_size[_META_MAX]; - pid_t pid; + PidRef pidref; uid_t uid; gid_t gid; + int signo; + uint64_t rlimit; bool is_pid1; bool is_journald; + int mount_tree_fd; + + /* These point into external memory, are not owned by this object */ + const char *meta[_META_MAX]; + size_t meta_size[_META_MAX]; } Context; +#define CONTEXT_NULL \ + (Context) { \ + .pidref = PIDREF_NULL, \ + .uid = UID_INVALID, \ + .gid = GID_INVALID, \ + .mount_tree_fd = -EBADF, \ + } + typedef enum CoredumpStorage { COREDUMP_STORAGE_NONE, COREDUMP_STORAGE_EXTERNAL, @@ -167,7 +186,16 @@ static uint64_t arg_external_size_max = EXTERNAL_SIZE_MAX; static uint64_t arg_journal_size_max = JOURNAL_SIZE_MAX; static uint64_t arg_keep_free = UINT64_MAX; static uint64_t arg_max_use = UINT64_MAX; -static bool arg_access_container = false; +#if HAVE_DWFL_SET_SYSROOT +static bool arg_enter_namespace = false; +#endif + +static void context_done(Context *c) { + assert(c); + + pidref_done(&c->pidref); + c->mount_tree_fd = safe_close(c->mount_tree_fd); +} static int parse_config(void) { static const ConfigTableItem items[] = { @@ -179,9 +207,9 @@ static int parse_config(void) { { "Coredump", "KeepFree", config_parse_iec_uint64, 0, &arg_keep_free }, { "Coredump", "MaxUse", config_parse_iec_uint64, 0, &arg_max_use }, #if HAVE_DWFL_SET_SYSROOT - { "Coredump", "AccessContainer", config_parse_bool, 0, &arg_access_container }, + { "Coredump", "EnterNamespace", config_parse_bool, 0, &arg_enter_namespace }, #else - { "Coredump", "AccessContainer", config_parse_warn_compat, DISABLED_CONFIGURATION, 0 }, + { "Coredump", "EnterNamespace", config_parse_warn_compat, DISABLED_CONFIGURATION, 0 }, #endif {} }; @@ -428,7 +456,7 @@ static int save_external_coredump( _cleanup_(unlink_and_freep) char *tmp = NULL; _cleanup_free_ char *fn = NULL; _cleanup_close_ int fd = -EBADF; - uint64_t rlimit, process_limit, max_size; + uint64_t process_limit, max_size; bool truncated, storage_on_tmpfs; struct stat st; int r; @@ -441,11 +469,7 @@ static int save_external_coredump( assert(ret_compressed_size); assert(ret_truncated); - r = safe_atou64(context->meta[META_ARGV_RLIMIT], &rlimit); - if (r < 0) - return log_error_errno(r, "Failed to parse resource limit '%s': %m", - context->meta[META_ARGV_RLIMIT]); - if (rlimit < page_size()) + if (context->rlimit < page_size()) /* Is coredumping disabled? Then don't bother saving/processing the * coredump. Anything below PAGE_SIZE cannot give a readable coredump * (the kernel uses ELF_EXEC_PAGESIZE which is not easily accessible, but @@ -460,7 +484,7 @@ static int save_external_coredump( "Limits for coredump processing and storage are both 0, not dumping core."); /* Never store more than the process configured, or than we actually shall keep or process */ - max_size = MIN(rlimit, process_limit); + max_size = MIN(context->rlimit, process_limit); r = make_filename(context, &fn); if (r < 0) @@ -765,9 +789,12 @@ static int get_process_container_parent_cmdline(pid_t pid, char** cmdline) { } static int change_uid_gid(const Context *context) { + int r; + + assert(context); + uid_t uid = context->uid; gid_t gid = context->gid; - int r; if (uid_is_system(uid)) { const char *user = "systemd-coredump"; @@ -782,45 +809,43 @@ static int change_uid_gid(const Context *context) { return drop_privileges(uid, gid, 0); } -static int setup_container_mount_tree(int mount_tree_fd, char **container_root) { - _cleanup_free_ char *root = NULL; +static int attach_mount_tree(int mount_tree_fd) { int r; assert(mount_tree_fd >= 0); - assert(container_root); - r = unshare(CLONE_NEWNS); + r = detach_mount_namespace(); if (r < 0) - return log_warning_errno(errno, "Failed to unshare mount namespace: %m"); + return log_warning_errno(r, "Failed to detach mount namespace: %m"); - r = mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL); + r = mkdir_p_label(MOUNT_TREE_ROOT, 0555); if (r < 0) - return log_warning_errno(errno, "Failed to disable mount propagation: %m"); + return log_warning_errno(r, "Failed to create directory: %m"); - r = mkdtemp_malloc("/tmp/systemd-coredump-root-XXXXXX", &root); + r = mount_setattr(mount_tree_fd, "", AT_EMPTY_PATH, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_RDONLY|MOUNT_ATTR_NOSUID|MOUNT_ATTR_NODEV|MOUNT_ATTR_NOEXEC|MOUNT_ATTR_NOSYMFOLLOW, + .propagation = MS_SLAVE, + }, sizeof(struct mount_attr)); if (r < 0) - return log_warning_errno(r, "Failed to create temporary directory: %m"); + return log_warning_errno(errno, "Failed to change properties of mount tree: %m"); - r = move_mount(mount_tree_fd, "", -EBADF, root, MOVE_MOUNT_F_EMPTY_PATH); + r = move_mount(mount_tree_fd, "", -EBADF, MOUNT_TREE_ROOT, MOVE_MOUNT_F_EMPTY_PATH); if (r < 0) - return log_warning_errno(errno, "Failed to move mount tree: %m"); + return log_warning_errno(errno, "Failed to attach mount tree: %m"); - *container_root = TAKE_PTR(root); return 0; } static int submit_coredump( const Context *context, struct iovec_wrapper *iovw, - int input_fd, - int mount_tree_fd) { + int input_fd) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json_metadata = NULL; _cleanup_close_ int coredump_fd = -EBADF, coredump_node_fd = -EBADF; - _cleanup_free_ char *filename = NULL, *coredump_data = NULL; - _cleanup_free_ char *stacktrace = NULL; - _cleanup_free_ char *root = NULL; - const char *module_name; + _cleanup_free_ char *filename = NULL, *coredump_data = NULL, *stacktrace = NULL; + const char *module_name, *root = NULL; uint64_t coredump_size = UINT64_MAX, coredump_compressed_size = UINT64_MAX; bool truncated = false, written = false; sd_json_variant *module_json; @@ -856,11 +881,8 @@ static int submit_coredump( (void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); } - if (mount_tree_fd >= 0 && arg_access_container) { - r = setup_container_mount_tree(mount_tree_fd, &root); - if (r < 0) - log_warning_errno(r, "Failed to setup container mount tree, ignoring: %m"); - } + if (context->mount_tree_fd >= 0 && attach_mount_tree(context->mount_tree_fd) >= 0) + root = MOUNT_TREE_ROOT; /* Now, let's drop privileges to become the user who owns the segfaulted process and allocate the * coredump memory under the user's uid. This also ensures that the credentials journald will see are @@ -869,6 +891,7 @@ static int submit_coredump( r = change_uid_gid(context); if (r < 0) return log_error_errno(r, "Failed to drop privileges: %m"); + if (written) { /* Try to get a stack trace if we can */ if (coredump_size > arg_process_size_max) @@ -991,7 +1014,7 @@ static int submit_coredump( return 0; } -static int save_context(Context *context, const struct iovec_wrapper *iovw) { +static int context_parse_iovw(Context *context, struct iovec_wrapper *iovw) { const char *unit; int r; @@ -999,33 +1022,46 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) { assert(iovw); assert(iovw->count >= _META_ARGV_MAX); - /* The context does not allocate any memory on its own */ - - for (size_t n = 0; n < iovw->count; n++) { - struct iovec *iovec = iovw->iovec + n; + /* Converts the data in the iovec array iovw into separate fields. Fills in context->meta[] (for + * which no memory is allocated, it just contains direct pointers into the iovec array memory). */ + bool have_signal_name = false; + FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) { for (size_t i = 0; i < ELEMENTSOF(meta_field_names); i++) { /* Note that these strings are NUL terminated, because we made sure that a * trailing NUL byte is in the buffer, though not included in the iov_len * count (see process_socket() and gather_pid_metadata_*()) */ assert(((char*) iovec->iov_base)[iovec->iov_len] == 0); - const char *p = startswith(iovec->iov_base, meta_field_names[i]); + const char *p = memory_startswith(iovec->iov_base, iovec->iov_len, meta_field_names[i]); if (p) { context->meta[i] = p; context->meta_size[i] = iovec->iov_len - strlen(meta_field_names[i]); break; } } + + have_signal_name = have_signal_name || + memory_startswith(iovec->iov_base, iovec->iov_len, "COREDUMP_SIGNAL_NAME="); } - if (!context->meta[META_ARGV_PID]) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to find the PID of crashing process"); + /* The basic fields from argv[] should always be there, refuse early if not */ + for (int i = 0; i < _META_ARGV_MAX; i++) + if (!context->meta[i]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A required (%s) has not been sent, aborting.", meta_field_names[i]); - r = parse_pid(context->meta[META_ARGV_PID], &context->pid); + pid_t parsed_pid; + r = parse_pid(context->meta[META_ARGV_PID], &parsed_pid); if (r < 0) return log_error_errno(r, "Failed to parse PID \"%s\": %m", context->meta[META_ARGV_PID]); + if (pidref_is_set(&context->pidref)) { + if (context->pidref.pid != parsed_pid) + return log_error_errno(r, "Passed PID " PID_FMT " does not match passed " PID_FMT ": %m", parsed_pid, context->pidref.pid); + } else { + r = pidref_set_pid(&context->pidref, parsed_pid); + if (r < 0) + return log_error_errno(r, "Failed to initialize pidref from pid " PID_FMT ": %m", parsed_pid); + } r = parse_uid(context->meta[META_ARGV_UID], &context->uid); if (r < 0) @@ -1035,25 +1071,42 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) { if (r < 0) return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]); + r = parse_signo(context->meta[META_ARGV_SIGNAL], &context->signo); + if (r < 0) + log_warning_errno(r, "Failed to parse signal number \"%s\", ignoring: %m", context->meta[META_ARGV_SIGNAL]); + + r = safe_atou64(context->meta[META_ARGV_RLIMIT], &context->rlimit); + if (r < 0) + log_warning_errno(r, "Failed to parse resource limit \"%s\", ignoring: %m", context->meta[META_ARGV_RLIMIT]); + unit = context->meta[META_UNIT]; context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE); context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE); + /* After parsing everything, let's also synthesize a new iovw field for the textual signal name if it + * isn't already set. */ + if (SIGNAL_VALID(context->signo) && !have_signal_name) + (void) iovw_put_string_field(iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo)); + return 0; } static int process_socket(int fd) { - _cleanup_close_ int input_fd = -EBADF, mount_tree_fd = -EBADF; - Context context = {}; - struct iovec_wrapper iovw = {}; - bool first = true; + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + _cleanup_(context_done) Context context = CONTEXT_NULL; + _cleanup_close_ int input_fd = -EBADF; + enum { + STATE_PAYLOAD, + STATE_INPUT_FD_DONE, + STATE_PID_FD_DONE, + } state = STATE_PAYLOAD; int r; assert(fd >= 0); log_setup(); - log_debug("Processing coredump received on stdin..."); + log_debug("Processing coredump received via socket..."); for (;;) { CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int))) control; @@ -1065,72 +1118,95 @@ static int process_socket(int fd) { ssize_t n, l; l = next_datagram_size_fd(fd); - if (l < 0) { - r = log_error_errno(l, "Failed to determine datagram size to read: %m"); - goto finish; - } + if (l < 0) + return log_error_errno(l, "Failed to determine datagram size to read: %m"); _cleanup_(iovec_done) struct iovec iovec = { .iov_len = l, .iov_base = malloc(l + 1), }; - if (!iovec.iov_base) { - r = log_oom(); - goto finish; - } + if (!iovec.iov_base) + return log_oom(); mh.msg_iov = &iovec; n = recvmsg_safe(fd, &mh, MSG_CMSG_CLOEXEC); - if (n < 0) { - r = log_error_errno(n, "Failed to receive datagram: %m"); - goto finish; - } - - /* The final zero-length datagram carries the file descriptor and tells us - * that we're done. */ + if (n < 0) + return log_error_errno(n, "Failed to receive datagram: %m"); + + /* The final zero-length datagrams ("sentinels") carry file descriptors and tell us that + * we're done. There are three sentinels: one with just the coredump fd, followed by one with + * the pidfd, and finally one with the mount tree fd. The latter two or the last one may be + * omitted (which is supported for compatibility with older systemd version, in particular to + * facilitate cross-container coredumping). */ if (n == 0) { struct cmsghdr *found; - found = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int) * 2)); - if (found) { - int fds[2] = EBADF_PAIR; - - memcpy(fds, CMSG_TYPED_DATA(found, int), sizeof(int) * 2); + found = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int))); + if (!found) { + /* This is zero length message but it either doesn't carry a single + * descriptor, or it has more than one. This is a protocol violation so let's + * bail out. + * + * Well, not quite! In practice there's one more complication: EOF on + * SOCK_SEQPACKET is not distinguishable from a zero length datagram. Hence + * if we get a zero length datagram without fds we consider it EOF, and + * that's permissible for the final two fds. Hence let's be strict on the + * first fd, but lenient on the other two. */ + + if (!cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1) && state != STATE_PAYLOAD) /* no fds, and already got the first fd → we are done */ + break; + + cmsg_close_all(&mh); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Received zero length message with zero or more than one file descriptor(s), expected one."); + } - assert(mount_tree_fd < 0); + switch (state) { - /* Maybe we already got coredump FD in previous iteration? */ - safe_close(input_fd); + case STATE_PAYLOAD: + assert(input_fd < 0); + input_fd = *CMSG_TYPED_DATA(found, int); + state = STATE_INPUT_FD_DONE; + continue; - input_fd = fds[0]; - mount_tree_fd = fds[1]; + case STATE_INPUT_FD_DONE: + assert(!pidref_is_set(&context.pidref)); - /* We have all FDs we need let's take a shortcut here. */ - break; - } else { - found = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int))); - if (found) - input_fd = *CMSG_TYPED_DATA(found, int); - } + r = pidref_set_pidfd_consume(&context.pidref, *CMSG_TYPED_DATA(found, int)); + if (r < 0) + return log_error_errno(r, "Failed to initialize pidref: %m"); - /* This is the first message that carries file descriptors, maybe there will be one more that actually contains array of descriptors. */ - if (first) { - first = false; + state = STATE_PID_FD_DONE; continue; + + case STATE_PID_FD_DONE: + assert(context.mount_tree_fd < 0); + context.mount_tree_fd = *CMSG_TYPED_DATA(found, int); + /* We have all FDs we need so we are done. */ + break; } break; - } else - cmsg_close_all(&mh); + } + + cmsg_close_all(&mh); + + /* Only zero length messages are allowed after the first message that carried a file descriptor. */ + if (state != STATE_PAYLOAD) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Received unexpected message with non-zero length."); + + /* Payload messages should not carry fds */ + if (cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, (socklen_t) -1)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Received payload message with file descriptor(s), expected none."); /* Add trailing NUL byte, in case these are strings */ ((char*) iovec.iov_base)[n] = 0; iovec.iov_len = (size_t) n; - r = iovw_put(&iovw, iovec.iov_base, iovec.iov_len); - if (r < 0) - goto finish; + if (iovw_put(&iovw, iovec.iov_base, iovec.iov_len) < 0) + return log_oom(); TAKE_STRUCT(iovec); } @@ -1138,27 +1214,19 @@ static int process_socket(int fd) { /* Make sure we got all data we really need */ assert(input_fd >= 0); - r = save_context(&context, &iovw); + r = context_parse_iovw(&context, &iovw); if (r < 0) - goto finish; + return r; /* Make sure we received at least all fields we need. */ for (int i = 0; i < _META_MANDATORY_MAX; i++) - if (!context.meta[i]) { - r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "A mandatory argument (%i) has not been sent, aborting.", - i); - goto finish; - } - - r = submit_coredump(&context, &iovw, input_fd, mount_tree_fd); + if (!context.meta[i]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A mandatory argument (%i) has not been sent, aborting.", i); -finish: - iovw_free_contents(&iovw, true); - return r; + return submit_coredump(&context, &iovw, input_fd); } -static int send_iovec(const struct iovec_wrapper *iovw, int input_fd, int mounts_fd) { +static int send_iovec(const struct iovec_wrapper *iovw, int input_fd, PidRef *pidref, int mount_tree_fd) { _cleanup_close_ int fd = -EBADF; int r; @@ -1197,7 +1265,7 @@ static int send_iovec(const struct iovec_wrapper *iovw, int input_fd, int mounts * what we want to send, and the second one contains * the trailing dots. */ copy[0] = iovw->iovec[i]; - copy[1] = IOVEC_MAKE(((char[]){'.', '.', '.'}), 3); + copy[1] = IOVEC_MAKE(((const char[]){'.', '.', '.'}), 3); mh.msg_iov = copy; mh.msg_iovlen = 2; @@ -1211,15 +1279,26 @@ static int send_iovec(const struct iovec_wrapper *iovw, int input_fd, int mounts } } + /* First sentinel: the coredump fd */ r = send_one_fd(fd, input_fd, 0); if (r < 0) return log_error_errno(r, "Failed to send coredump fd: %m"); - if (mounts_fd >= 0) { - r = send_many_fds(fd, (int[]) { input_fd, mounts_fd }, 2, 0); - if (r < 0) - return log_error_errno(r, "Failed to send coredump fds: %m"); - } + /* The optional second sentinel: the pidfd */ + if (!pidref_is_set(pidref) || pidref->fd < 0) /* If we have no pidfd, stop now */ + return 0; + + r = send_one_fd(fd, pidref->fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send pidfd: %m"); + + /* The optional third sentinel: the mount tree fd */ + if (mount_tree_fd < 0) /* If we have no mount tree, stop now */ + return 0; + + r = send_one_fd(fd, mount_tree_fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send mount tree fd: %m"); return 0; } @@ -1229,9 +1308,7 @@ static int gather_pid_metadata_from_argv( Context *context, int argc, char **argv) { - _cleanup_free_ char *free_timestamp = NULL; - int r, signo; - char *t; + int r; assert(iovw); assert(context); @@ -1245,30 +1322,19 @@ static int gather_pid_metadata_from_argv( argc, _META_ARGV_MAX); for (int i = 0; i < _META_ARGV_MAX; i++) { + _cleanup_free_ char *buf = NULL; + const char *t = argv[i]; - t = argv[i]; - - switch (i) { - - case META_ARGV_TIMESTAMP: + if (i == META_ARGV_TIMESTAMP) { /* The journal fields contain the timestamp padded with six * zeroes, so that the kernel-supplied 1s granularity timestamps * becomes 1μs granularity, i.e. the granularity systemd usually * operates in. */ - t = free_timestamp = strjoin(argv[i], "000000"); - if (!t) + buf = strjoin(argv[i], "000000"); + if (!buf) return log_oom(); - break; - - case META_ARGV_SIGNAL: - /* For signal, record its pretty name too */ - if (safe_atoi(argv[i], &signo) >= 0 && SIGNAL_VALID(signo)) - (void) iovw_put_string_field(iovw, "COREDUMP_SIGNAL_NAME=SIG", - signal_to_string(signo)); - break; - default: - break; + t = buf; } r = iovw_put_string_field(iovw, meta_field_names[i], t); @@ -1278,7 +1344,7 @@ static int gather_pid_metadata_from_argv( /* Cache some of the process metadata we collected so far and that we'll need to * access soon */ - return save_context(context, iovw); + return context_parse_iovw(context, iovw); } static int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context *context) { @@ -1295,10 +1361,10 @@ static int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context * /* Note that if we fail on oom later on, we do not roll-back changes to the iovec * structure. (It remains valid, with the first iovec fields initialized.) */ - pid = context->pid; + pid = context->pidref.pid; /* The following is mandatory */ - r = pid_get_comm(pid, &t); + r = pidref_get_comm(&context->pidref, &t); if (r < 0) return log_error_errno(r, "Failed to get COMM: %m"); @@ -1313,7 +1379,7 @@ static int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context * if (r < 0) log_warning_errno(r, "Failed to get EXE, ignoring: %m"); - if (cg_pid_get_unit(pid, &t) >= 0) + if (cg_pidref_get_unit(&context->pidref, &t) >= 0) (void) iovw_put_string_field_free(iovw, "COREDUMP_UNIT=", t); if (cg_pid_get_user_unit(pid, &t) >= 0) @@ -1331,7 +1397,7 @@ static int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context * if (sd_pid_get_slice(pid, &t) >= 0) (void) iovw_put_string_field_free(iovw, "COREDUMP_SLICE=", t); - if (pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &t) >= 0) + if (pidref_get_cmdline(&context->pidref, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &t) >= 0) (void) iovw_put_string_field_free(iovw, "COREDUMP_CMDLINE=", t); if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) @@ -1365,7 +1431,7 @@ static int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context * if (read_full_virtual_file(p, &t, &size) >= 0) { char *buf = malloc(strlen("COREDUMP_PROC_AUXV=") + size + 1); if (buf) { - /* Add a dummy terminator to make save_context() happy. */ + /* Add a dummy terminator to make context_parse_iovw() happy. */ *mempcpy_typesafe(stpcpy(buf, "COREDUMP_PROC_AUXV="), t, size) = '\0'; (void) iovw_consume(iovw, buf, size + strlen("COREDUMP_PROC_AUXV=")); } @@ -1393,10 +1459,10 @@ static int gather_pid_metadata_from_procfs(struct iovec_wrapper *iovw, Context * (void) iovw_put_string_field_free(iovw, "COREDUMP_ENVIRON=", t); /* we successfully acquired all metadata */ - return save_context(context, iovw); + return context_parse_iovw(context, iovw); } -static int send_ucred(int transport_fd, struct ucred *ucred) { +static int send_ucred(int transport_fd, const struct ucred *ucred) { CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {}; struct msghdr mh = { .msg_control = &control, @@ -1405,6 +1471,7 @@ static int send_ucred(int transport_fd, struct ucred *ucred) { struct cmsghdr *cmsg; assert(transport_fd >= 0); + assert(ucred); cmsg = CMSG_FIRSTHDR(&mh); *cmsg = (struct cmsghdr) { @@ -1427,6 +1494,7 @@ static int receive_ucred(int transport_fd, struct ucred *ret_ucred) { struct ucred *ucred = NULL; ssize_t n; + assert(transport_fd >= 0); assert(ret_ucred); n = recvmsg_safe(transport_fd, &mh, 0); @@ -1483,19 +1551,21 @@ static int can_forward_coredump(pid_t pid) { static int forward_coredump_to_container(Context *context) { _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, netnsfd = -EBADF, usernsfd = -EBADF, rootfd = -EBADF; _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - pid_t pid, child; + pid_t leader_pid, child; struct ucred ucred = { - .pid = context->pid, + .pid = context->pidref.pid, .uid = context->uid, .gid = context->gid, }; int r; - r = namespace_get_leader(context->pid, NAMESPACE_PID, &pid); + assert(context); + + r = namespace_get_leader(context->pidref.pid, NAMESPACE_PID, &leader_pid); if (r < 0) return log_debug_errno(r, "Failed to get namespace leader: %m"); - r = can_forward_coredump(pid); + r = can_forward_coredump(leader_pid); if (r < 0) return log_debug_errno(r, "Failed to check if coredump can be forwarded: %m"); if (r == 0) @@ -1510,19 +1580,16 @@ static int forward_coredump_to_container(Context *context) { if (r < 0) return log_debug_errno(r, "Failed to set SO_PASSCRED: %m"); - r = namespace_open(pid, &pidnsfd, &mntnsfd, &netnsfd, &usernsfd, &rootfd); + r = namespace_open(leader_pid, &pidnsfd, &mntnsfd, &netnsfd, &usernsfd, &rootfd); if (r < 0) - return log_debug_errno(r, "Failed to join namespaces of PID " PID_FMT ": %m", pid); + return log_debug_errno(r, "Failed to join namespaces of PID " PID_FMT ": %m", leader_pid); r = namespace_fork("(sd-coredumpns)", "(sd-coredump)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, pidnsfd, mntnsfd, netnsfd, usernsfd, rootfd, &child); if (r < 0) - return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", pid); + return log_debug_errno(r, "Failed to fork into namespaces of PID " PID_FMT ": %m", leader_pid); if (r == 0) { - _cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL; - Context child_context = {}; - pair[0] = safe_close(pair[0]); r = access_nofollow("/run/systemd/coredump", W_OK); @@ -1537,7 +1604,7 @@ static int forward_coredump_to_container(Context *context) { _exit(EXIT_FAILURE); } - iovw = iovw_new(); + _cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = iovw_new(); if (!iovw) { log_oom(); _exit(EXIT_FAILURE); @@ -1548,16 +1615,15 @@ static int forward_coredump_to_container(Context *context) { (void) iovw_put_string_field(iovw, "COREDUMP_FORWARDED=", "1"); for (int i = 0; i < _META_ARGV_MAX; i++) { - int signo; char buf[DECIMAL_STR_MAX(pid_t)]; const char *t = context->meta[i]; + /* Patch some of the fields with the translated ucred data */ switch (i) { case META_ARGV_PID: xsprintf(buf, PID_FMT, ucred.pid); t = buf; - break; case META_ARGV_UID: @@ -1570,13 +1636,6 @@ static int forward_coredump_to_container(Context *context) { t = buf; break; - case META_ARGV_SIGNAL: - if (safe_atoi(t, &signo) >= 0 && SIGNAL_VALID(signo)) - (void) iovw_put_string_field(iovw, - "COREDUMP_SIGNAL_NAME=SIG", - signal_to_string(signo)); - break; - default: break; } @@ -1588,7 +1647,8 @@ static int forward_coredump_to_container(Context *context) { } } - r = save_context(&child_context, iovw); + _cleanup_(context_done) Context child_context = CONTEXT_NULL; + r = context_parse_iovw(&child_context, iovw); if (r < 0) { log_debug_errno(r, "Failed to save context: %m"); _exit(EXIT_FAILURE); @@ -1600,7 +1660,7 @@ static int forward_coredump_to_container(Context *context) { _exit(EXIT_FAILURE); } - r = send_iovec(iovw, STDIN_FILENO, -EBADF); + r = send_iovec(iovw, STDIN_FILENO, &context->pidref, /* mount_tree_fd= */ -EBADF); if (r < 0) { log_debug_errno(r, "Failed to send iovec to coredump socket: %m"); _exit(EXIT_FAILURE); @@ -1628,42 +1688,60 @@ static int forward_coredump_to_container(Context *context) { return 0; } -static int gather_pid_mount_tree_fd(const Context *context) { - _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF; +static int acquire_pid_mount_tree_fd(const Context *context, int *ret_fd) { + /* Don't bother preparing environment if we can't pass it to libdwfl. */ +#if !HAVE_DWFL_SET_SYSROOT + *ret_fd = -EOPNOTSUPP; + log_debug("dwfl_set_sysroot() is not supported."); +#else + _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, fd = -EBADF; _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - int fd = -EBADF, r; - pid_t child; + int r; assert(context); + assert(ret_fd); - /* Don't bother preparing environment if we can't pass it to libdwfl. */ -#if !HAVE_DWFL_SET_SYSROOT - return -EBADF; -#endif - - if (!arg_access_container) - return -EBADF; + if (!arg_enter_namespace) { + *ret_fd = -EHOSTDOWN; + log_debug("EnterNamespace=no so we won't use mount tree of the crashed process for generating backtrace."); + return 0; + } if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pair) < 0) return log_error_errno(errno, "Failed to create socket pair: %m"); - r = namespace_open(context->pid, NULL, &mntns_fd, NULL, NULL, &root_fd); + r = namespace_open(context->pidref.pid, + /* ret_pidns_fd= */ NULL, + &mntns_fd, + /* ret_netns_fd= */ NULL, + /* ret_userns_fd= */ NULL, + &root_fd); if (r < 0) return log_error_errno(r, "Failed to open mount namespace of crashing process: %m"); - r = namespace_fork("(sd-mount-tree-ns)", "(sd-mount-tree)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, -1, mntns_fd, -1, -1, root_fd, &child); + r = namespace_fork("(sd-mount-tree-ns)", + "(sd-mount-tree)", + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, + /* pidns_fd= */ -EBADF, + mntns_fd, + /* netns_fd= */ -EBADF, + /* userns_fd= */ -EBADF, + root_fd, + NULL); if (r < 0) - return log_error_errno(r, "Failed to fork(): %m"); + return r; if (r == 0) { pair[0] = safe_close(pair[0]); - r = open_tree(-EBADF, "/", AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); - if (r < 0) { + fd = open_tree(-EBADF, "/", AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); + if (fd < 0) { log_error_errno(errno, "Failed to clone mount tree: %m"); _exit(EXIT_FAILURE); } - r = send_one_fd(pair[1], r, 0); + r = send_one_fd(pair[1], fd, 0); if (r < 0) { log_error_errno(r, "Failed to send mount tree to parent: %m"); _exit(EXIT_FAILURE); @@ -1674,24 +1752,19 @@ static int gather_pid_mount_tree_fd(const Context *context) { pair[1] = safe_close(pair[1]); - r = wait_for_terminate_and_check("(sd-mount-tree-ns)", child, 0); - if (r < 0) - return log_error_errno(r, "Failed to wait for child: %m"); - if (r != EXIT_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ECHILD), "Child died abnormally."); - fd = receive_one_fd(pair[0], MSG_DONTWAIT); if (fd < 0) return log_error_errno(fd, "Failed to receive mount tree: %m"); - return fd; + *ret_fd = TAKE_FD(fd); +#endif + return 0; } static int process_kernel(int argc, char* argv[]) { _cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL; - _cleanup_close_ int mount_tree_fd = -EBADF; - Context context = {}; - int r, signo; + _cleanup_(context_done) Context context = CONTEXT_NULL; + int r; /* When we're invoked by the kernel, stdout/stderr are closed which is dangerous because the fds * could get reallocated. To avoid hard to debug issues, let's instead bind stdout/stderr to @@ -1722,11 +1795,11 @@ static int process_kernel(int argc, char* argv[]) { /* Log minimal metadata now, so it is not lost if the system is about to shut down. */ log_info("Process %s (%s) of user %s terminated abnormally with signal %s/%s, processing...", - context.meta[META_ARGV_PID], context.meta[META_COMM], - context.meta[META_ARGV_UID], context.meta[META_ARGV_SIGNAL], - strna(safe_atoi(context.meta[META_ARGV_SIGNAL], &signo) >= 0 ? signal_to_string(signo) : NULL)); + context.meta[META_ARGV_PID], context.meta[META_COMM], + context.meta[META_ARGV_UID], context.meta[META_ARGV_SIGNAL], + signal_to_string(context.signo)); - r = in_same_namespace(getpid_cached(), context.pid, NAMESPACE_PID); + r = in_same_namespace(getpid_cached(), context.pidref.pid, NAMESPACE_PID); if (r < 0) log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m"); if (r == 0) { @@ -1736,11 +1809,9 @@ static int process_kernel(int argc, char* argv[]) { if (r >= 0) return 0; - r = gather_pid_mount_tree_fd(&context); - if (r < 0 && r != -EBADF) + r = acquire_pid_mount_tree_fd(&context, &context.mount_tree_fd); + if (r < 0) log_warning_errno(r, "Failed to access the mount tree of a container, ignoring: %m"); - else - mount_tree_fd = r; } /* If this is PID 1 disable coredump collection, we'll unlikely be able to process @@ -1758,18 +1829,20 @@ static int process_kernel(int argc, char* argv[]) { (void) iovw_put_string_field(iovw, "PRIORITY=", STRINGIFY(LOG_CRIT)); if (context.is_journald || context.is_pid1) - return submit_coredump(&context, iovw, STDIN_FILENO, mount_tree_fd); + return submit_coredump(&context, iovw, STDIN_FILENO); - return send_iovec(iovw, STDIN_FILENO, mount_tree_fd); + return send_iovec(iovw, STDIN_FILENO, &context.pidref, context.mount_tree_fd); } static int process_backtrace(int argc, char *argv[]) { _cleanup_(journal_importer_cleanup) JournalImporter importer = JOURNAL_IMPORTER_INIT(STDIN_FILENO); _cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL; - Context context = {}; + _cleanup_(context_done) Context context = CONTEXT_NULL; char *message; int r; + assert(argc >= 2); + log_debug("Processing backtrace on stdin..."); iovw = iovw_new(); diff --git a/src/coredump/coredump.conf b/src/coredump/coredump.conf index 2790bf1be6..181aede9da 100644 --- a/src/coredump/coredump.conf +++ b/src/coredump/coredump.conf @@ -25,4 +25,4 @@ #JournalSizeMax=767M #MaxUse= #KeepFree= -#AccessContainer=no +#EnterNamespace=no diff --git a/src/creds/creds.c b/src/creds/creds.c index bb59db37fc..b24a84eaa6 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -273,7 +273,7 @@ static int verb_list(int argc, char **argv, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)"); } - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) && table_isempty(t)) { + if (table_isempty(t) && !sd_json_format_enabled(arg_json_format_flags)) { log_info("No credentials"); return 0; } @@ -370,7 +370,7 @@ static int write_blob(FILE *f, const void *data, size_t size) { int r; if (arg_transcode == TRANSCODE_OFF && - arg_json_format_flags != SD_JSON_FORMAT_OFF) { + sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(erase_and_freep) char *suffixed = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; diff --git a/src/cryptenroll/cryptenroll-recovery.c b/src/cryptenroll/cryptenroll-recovery.c index 0a5e9cdcbe..33b58eeb37 100644 --- a/src/cryptenroll/cryptenroll-recovery.c +++ b/src/cryptenroll/cryptenroll-recovery.c @@ -67,7 +67,7 @@ int enroll_recovery( "whenever authentication is requested.\n", stderr); fflush(stderr); - (void) print_qrcode(stderr, "You may optionally scan the recovery key off screen", password); + (void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password); if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) { r = log_oom(); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 1cad45c8ff..78a830d574 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -877,7 +877,7 @@ static int action_dissect( if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) pager_open(arg_pager_flags); - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(arg_json_format_flags)) { printf(" File Name: %s%s%s\n", ansi_highlight(), bn, ansi_normal()); @@ -907,7 +907,7 @@ static int action_dissect( log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata."); else if (r < 0) return log_error_errno(r, "Failed to acquire image metadata: %m"); - else if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + else if (!sd_json_format_enabled(arg_json_format_flags)) { if (m->image_name && !streq(m->image_name, bn)) printf("Image Name: %s\n", m->image_name); @@ -1017,7 +1017,7 @@ static int action_dissect( /* Hide the device path if this is a loopback device that is not relinquished, since that means the * device node is not going to be useful the instant our command exits */ - if ((!d || d->created) && (arg_json_format_flags & SD_JSON_FORMAT_OFF)) + if ((!d || d->created) && !sd_json_format_enabled(arg_json_format_flags)) table_hide_column_from_display(t, 8); for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { @@ -1080,7 +1080,7 @@ static int action_dissect( return table_log_add_error(r); } - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(arg_json_format_flags)) { (void) table_set_header(t, arg_legend); r = table_print(t, NULL); @@ -1856,7 +1856,7 @@ static int action_discover(void) { return log_error_errno(r, "Failed to discover images: %m"); } - if ((arg_json_format_flags & SD_JSON_FORMAT_OFF) && hashmap_isempty(images)) { + if (hashmap_isempty(images) && !sd_json_format_enabled(arg_json_format_flags)) { log_info("No images found."); return 0; } diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 5bdeecc96c..9be62b8df3 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -104,8 +104,10 @@ static void print_welcome(int rfd) { if (!arg_welcome) return; - if (done) + if (done) { + putchar('\n'); /* Add some breathing room between multiple prompts */ return; + } r = parse_os_release_at(rfd, "PRETTY_NAME", &pretty_name, @@ -132,7 +134,7 @@ static void print_welcome(int rfd) { done = true; } -static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*is_valid)(const char *name), char **ret) { +static int prompt_loop(int rfd, const char *text, char **l, unsigned percentage, bool (*is_valid)(int rfd, const char *name), char **ret) { int r; assert(text); @@ -143,7 +145,8 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i _cleanup_free_ char *p = NULL; unsigned u; - r = ask_string(&p, "%s %s (empty to skip, \"list\" to list options): ", + r = ask_string(&p, strv_isempty(l) ? "%s %s (empty to skip): " + : "%s %s (empty to skip, \"list\" to list options): ", special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), text); if (r < 0) return log_error_errno(r, "Failed to query user: %m"); @@ -153,27 +156,29 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i return 0; } - if (streq(p, "list")) { - r = show_menu(l, 3, 20, percentage); - if (r < 0) - return r; + if (!strv_isempty(l)) { + if (streq(p, "list")) { + r = show_menu(l, 3, 20, percentage); + if (r < 0) + return r; - putchar('\n'); - continue; - }; - - r = safe_atou(p, &u); - if (r >= 0) { - if (u <= 0 || u > strv_length(l)) { - log_error("Specified entry number out of range."); + putchar('\n'); continue; } - log_info("Selected '%s'.", l[u-1]); - return free_and_strdup_warn(ret, l[u-1]); + r = safe_atou(p, &u); + if (r >= 0) { + if (u <= 0 || u > strv_length(l)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", l[u-1]); + return free_and_strdup_warn(ret, l[u-1]); + } } - if (is_valid(p)) + if (is_valid(rfd, p)) return free_and_replace(*ret, p); /* Be more helpful to the user, and give a hint what the user might have wanted to type. */ @@ -259,9 +264,15 @@ static bool locale_is_installed_bool(const char *name) { } static bool locale_is_ok(int rfd, const char *name) { + int r; + assert(rfd >= 0); - return dir_fd_is_root(rfd) ? locale_is_installed_bool(name) : locale_is_valid(name); + r = dir_fd_is_root(rfd); + if (r < 0) + log_debug_errno(r, "Unable to determine if operating on host root directory, assuming we are: %m"); + + return r != 0 ? locale_is_installed_bool(name) : locale_is_valid(name); } static int prompt_locale(int rfd) { @@ -316,21 +327,18 @@ static int prompt_locale(int rfd) { /* Not setting arg_locale_message here, since it defaults to LANG anyway */ } } else { - bool (*is_valid)(const char *name) = dir_fd_is_root(rfd) ? locale_is_installed_bool - : locale_is_valid; - print_welcome(rfd); - r = prompt_loop("Please enter system locale name or number", - locales, 60, is_valid, &arg_locale); + r = prompt_loop(rfd, "Please enter the new system locale name or number", + locales, 60, locale_is_ok, &arg_locale); if (r < 0) return r; if (isempty(arg_locale)) return 0; - r = prompt_loop("Please enter system message locale name or number", - locales, 60, is_valid, &arg_locale_messages); + r = prompt_loop(rfd, "Please enter the new system message locale name or number", + locales, 60, locale_is_ok, &arg_locale_messages); if (r < 0) return r; @@ -404,14 +412,16 @@ static bool keymap_exists_bool(const char *name) { return keymap_exists(name) > 0; } -static typeof(&keymap_is_valid) determine_keymap_validity_func(int rfd) { +static bool keymap_is_ok(int rfd, const char* name) { int r; + assert(rfd >= 0); + r = dir_fd_is_root(rfd); if (r < 0) log_debug_errno(r, "Unable to determine if operating on host root directory, assuming we are: %m"); - return r != 0 ? keymap_exists_bool : keymap_is_valid; + return r != 0 ? keymap_exists_bool(name) : keymap_is_valid(name); } static int prompt_keymap(int rfd) { @@ -444,8 +454,8 @@ static int prompt_keymap(int rfd) { print_welcome(rfd); - return prompt_loop("Please enter system keymap name or number", - kmaps, 60, determine_keymap_validity_func(rfd), &arg_keymap); + return prompt_loop(rfd, "Please enter the new keymap name or number", + kmaps, 60, keymap_is_ok, &arg_keymap); } static int process_keymap(int rfd) { @@ -502,7 +512,9 @@ static int process_keymap(int rfd) { return 1; } -static bool timezone_is_valid_log_debug(const char *name) { +static bool timezone_is_ok(int rfd, const char *name) { + assert(rfd >= 0); + return timezone_is_valid(name, LOG_DEBUG); } @@ -534,12 +546,8 @@ static int prompt_timezone(int rfd) { print_welcome(rfd); - r = prompt_loop("Please enter timezone name or number", - zones, 30, timezone_is_valid_log_debug, &arg_timezone); - if (r < 0) - return r; - - return 0; + return prompt_loop(rfd, "Please enter the new timezone name or number", + zones, 30, timezone_is_ok, &arg_timezone); } static int process_timezone(int rfd) { @@ -600,6 +608,12 @@ static int process_timezone(int rfd) { return 0; } +static bool hostname_is_ok(int rfd, const char *name) { + assert(rfd >= 0); + + return hostname_is_valid(name, VALID_HOSTNAME_TRAILING_DOT); +} + static int prompt_hostname(int rfd) { int r; @@ -614,31 +628,13 @@ static int prompt_hostname(int rfd) { } print_welcome(rfd); - putchar('\n'); - - for (;;) { - _cleanup_free_ char *h = NULL; - r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); - if (r < 0) - return log_error_errno(r, "Failed to query hostname: %m"); - - if (isempty(h)) { - log_info("No hostname entered, skipping."); - break; - } - - if (!hostname_is_valid(h, VALID_HOSTNAME_TRAILING_DOT)) { - log_error("Specified hostname invalid."); - continue; - } - - /* Get rid of the trailing dot that we allow, but don't want to see */ - arg_hostname = hostname_cleanup(h); - h = NULL; - break; - } + r = prompt_loop(rfd, "Please enter the new hostname", + NULL, 0, hostname_is_ok, &arg_hostname); + if (r < 0) + return r; + hostname_cleanup(arg_hostname); return 0; } @@ -728,10 +724,9 @@ static int prompt_root_password(int rfd) { } print_welcome(rfd); - putchar('\n'); - msg1 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip):"); - msg2 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter new root password again:"); + msg1 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter the new root password (empty to skip):"); + msg2 = strjoina(special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), " Please enter the new root password again:"); suggest_passwords(); @@ -799,6 +794,12 @@ static int find_shell(int rfd, const char *path) { return 0; } +static bool shell_is_ok(int rfd, const char *path) { + assert(rfd >= 0); + + return find_shell(rfd, path) >= 0; +} + static int prompt_root_shell(int rfd) { int r; @@ -821,29 +822,9 @@ static int prompt_root_shell(int rfd) { } print_welcome(rfd); - putchar('\n'); - - for (;;) { - _cleanup_free_ char *s = NULL; - - r = ask_string(&s, "%s Please enter root shell for new system (empty to skip): ", special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); - if (r < 0) - return log_error_errno(r, "Failed to query root shell: %m"); - if (isempty(s)) { - log_info("No shell entered, skipping."); - break; - } - - r = find_shell(rfd, s); - if (r < 0) - continue; - - arg_root_shell = TAKE_PTR(s); - break; - } - - return 0; + return prompt_loop(rfd, "Please enter the new root shell", + NULL, 0, shell_is_ok, &arg_root_shell); } static int write_root_passwd(int rfd, int etc_fd, const char *password, const char *shell) { @@ -1668,7 +1649,7 @@ static int run(int argc, char *argv[]) { /* We check these conditions here instead of in parse_argv() so that we can take the root directory * into account. */ - if (arg_keymap && !determine_keymap_validity_func(rfd)(arg_keymap)) + if (arg_keymap && !keymap_is_ok(rfd, arg_keymap)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Keymap %s is not installed.", arg_keymap); if (arg_locale && !locale_is_ok(rfd, arg_locale)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale); @@ -1705,15 +1686,15 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = process_machine_id(rfd); + r = process_root_account(rfd); if (r < 0) return r; - r = process_root_account(rfd); + r = process_kernel_cmdline(rfd); if (r < 0) return r; - r = process_kernel_cmdline(rfd); + r = process_machine_id(rfd); if (r < 0) return r; diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 7e604b8283..35758b5b18 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -542,7 +542,6 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { #define DECLARE_NOALLOC_SECTION(name, text) \ asm(".pushsection " name ",\"S\"\n\t" \ ".ascii " STRINGIFY(text) "\n\t" \ - ".zero 1\n\t" \ ".popsection\n") #ifdef SBAT_DISTRO diff --git a/src/home/homectl-recovery-key.c b/src/home/homectl-recovery-key.c index b5c57e14a8..2b76303edd 100644 --- a/src/home/homectl-recovery-key.c +++ b/src/home/homectl-recovery-key.c @@ -160,7 +160,7 @@ int identity_add_recovery_key(sd_json_variant **v) { "whenever authentication is requested.\n", stderr); fflush(stderr); - (void) print_qrcode(stderr, "You may optionally scan the recovery key off screen", password); + (void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password); return 0; } diff --git a/src/home/homectl.c b/src/home/homectl.c index 11270d7edb..b3aacbcbcf 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -203,7 +203,7 @@ static int list_homes(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - if (!table_isempty(table) || !FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { r = table_set_sort(table, (size_t) 0); if (r < 0) return table_log_sort_error(r); @@ -213,7 +213,7 @@ static int list_homes(int argc, char *argv[], void *userdata) { return r; } - if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { if (table_isempty(table)) printf("No home areas.\n"); else @@ -671,7 +671,7 @@ static void dump_home_record(UserRecord *hr) { log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", hr->user_name); } - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) + if (!sd_json_format_enabled(arg_json_format_flags)) user_record_show(hr, true); else { _cleanup_(user_record_unrefp) UserRecord *stripped = NULL; diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index 290e05ac6b..80e2773447 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -55,7 +55,7 @@ static int property_get_state( return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h))); } -int bus_home_client_is_trusted(Home *h, sd_bus_message *message) { +static int bus_home_client_is_trusted(Home *h, sd_bus_message *message, bool strict) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; uid_t euid; int r; @@ -73,7 +73,7 @@ int bus_home_client_is_trusted(Home *h, sd_bus_message *message) { if (r < 0) return r; - return euid == 0 || h->uid == euid; + return (!strict && euid == 0) || h->uid == euid; } static int home_verify_polkit_async( @@ -117,7 +117,7 @@ int bus_home_get_record_json( assert(h); assert(ret); - trusted = bus_home_client_is_trusted(h, message); + trusted = bus_home_client_is_trusted(h, message, /* strict= */ false); if (trusted < 0) { log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted."); trusted = false; @@ -423,7 +423,7 @@ int bus_home_update_record( Hashmap *blobs, uint64_t flags, sd_bus_error *error) { - int r; + int r, relax_access; assert(h); assert(message); @@ -436,10 +436,32 @@ int bus_home_update_record( if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided."); + if (blobs) { + const char *failed = NULL; + r = user_record_ensure_blob_manifest(hr, blobs, &failed); + if (r == -EINVAL) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest."); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed)); + } + + relax_access = user_record_self_changes_allowed(h->record, hr); + if (relax_access < 0) { + log_warning_errno(relax_access, "Failed to determine if changes to user record are permitted, assuming not: %m"); + relax_access = false; + } else if (relax_access) { + relax_access = bus_home_client_is_trusted(h, message, /* strict= */ true); + if (relax_access < 0) { + log_warning_errno(relax_access, "Failed to determine whether client is trusted, assuming not: %m"); + relax_access = false; + } + } + r = home_verify_polkit_async( h, message, - "org.freedesktop.home1.update-home", + relax_access ? "org.freedesktop.home1.update-home-by-owner" + : "org.freedesktop.home1.update-home", UID_INVALID, error); if (r < 0) @@ -561,7 +583,7 @@ int bus_home_method_change_password( h, message, "org.freedesktop.home1.passwd-home", - h->uid, + h->uid, /* Always let a user change their own password. Safe b/c homework will always re-check password */ error); if (r < 0) return r; diff --git a/src/home/homed-home-bus.h b/src/home/homed-home-bus.h index 1644bc8066..8d4ddf909f 100644 --- a/src/home/homed-home-bus.h +++ b/src/home/homed-home-bus.h @@ -6,7 +6,6 @@ #include "bus-object.h" #include "homed-home.h" -int bus_home_client_is_trusted(Home *h, sd_bus_message *message); int bus_home_get_record_json(Home *h, sd_bus_message *message, char **ret, bool *ret_incomplete); int bus_home_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 664397e55e..32691e4f81 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -1727,15 +1727,6 @@ static int home_update_internal( secret = saved_secret; } - if (blobs) { - const char *failed = NULL; - r = user_record_ensure_blob_manifest(hr, blobs, &failed); - if (r == -EINVAL) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest."); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed)); - } - r = manager_verify_user_record(h->manager, hr); switch (r) { diff --git a/src/home/org.freedesktop.home1.policy b/src/home/org.freedesktop.home1.policy index 3b19ed3ed5..d3317772ac 100644 --- a/src/home/org.freedesktop.home1.policy +++ b/src/home/org.freedesktop.home1.policy @@ -49,6 +49,16 @@ </defaults> </action> + <action id="org.freedesktop.home1.update-home-by-owner"> + <description gettext-domain="systemd">Update your home area</description> + <message gettext-domain="systemd">Authentication is required to update your home area.</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="org.freedesktop.home1.resize-home"> <description gettext-domain="systemd">Resize a home area</description> <message gettext-domain="systemd">Authentication is required to resize a user's home area.</message> diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 4bbfa670b7..a537312d25 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -491,7 +491,7 @@ static int show_status(int argc, char **argv, void *userdata) { sd_bus *bus = userdata; int r; - if (arg_json_format_flags != SD_JSON_FORMAT_OFF) { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; diff --git a/src/import/export-raw.c b/src/import/export-raw.c index f425396261..5e34fd372e 100644 --- a/src/import/export-raw.c +++ b/src/import/export-raw.c @@ -9,9 +9,11 @@ #include "copy.h" #include "export-raw.h" #include "fd-util.h" +#include "format-util.h" #include "fs-util.h" #include "import-common.h" #include "missing_fcntl.h" +#include "pretty-print.h" #include "ratelimit.h" #include "stat-util.h" #include "string-util.h" @@ -121,7 +123,16 @@ static void raw_export_report_progress(RawExport *e) { return; sd_notifyf(false, "X_IMPORT_PROGRESS=%u%%", percent); - log_info("Exported %u%%.", percent); + + if (isatty_safe(STDERR_FILENO)) + (void) draw_progress_barf( + percent, + "%s %s/%s", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + FORMAT_BYTES(e->written_uncompressed), + FORMAT_BYTES(e->st.st_size)); + else + log_info("Exported %u%%.", percent); e->last_percent = percent; } @@ -215,6 +226,9 @@ static int raw_export_process(RawExport *e) { finish: if (r >= 0) { + if (isatty_safe(STDERR_FILENO)) + clear_progress_bar(/* prefix= */ NULL); + (void) copy_times(e->input_fd, e->output_fd, COPY_CRTIME); (void) copy_xattr(e->input_fd, NULL, e->output_fd, NULL, 0); } diff --git a/src/import/export-tar.c b/src/import/export-tar.c index 9e92badfef..e025efe411 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -7,6 +7,7 @@ #include "export-tar.h" #include "fd-util.h" #include "import-common.h" +#include "pretty-print.h" #include "process-util.h" #include "ratelimit.h" #include "string-util.h" @@ -132,7 +133,16 @@ static void tar_export_report_progress(TarExport *e) { return; sd_notifyf(false, "X_IMPORT_PROGRESS=%u%%", percent); - log_info("Exported %u%%.", percent); + + if (isatty_safe(STDERR_FILENO)) + (void) draw_progress_barf( + percent, + "%s %s/%s", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + FORMAT_BYTES(e->written_uncompressed), + FORMAT_BYTES(e->quota_referenced)); + else + log_info("Exported %u%%.", percent); e->last_percent = percent; } @@ -229,6 +239,9 @@ static int tar_export_process(TarExport *e) { return 0; finish: + if (r >= 0 && isatty_safe(STDERR_FILENO)) + clear_progress_bar(/* prefix= */ NULL); + if (e->on_finished) e->on_finished(e, r, e->userdata); else diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 78775b96d6..602d1f1ac3 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -20,6 +20,7 @@ #include "machine-pool.h" #include "mkdir-label.h" #include "path-util.h" +#include "pretty-print.h" #include "qcow2-util.h" #include "ratelimit.h" #include "rm-rf.h" @@ -149,7 +150,16 @@ static void raw_import_report_progress(RawImport *i) { return; sd_notifyf(false, "X_IMPORT_PROGRESS=%u%%", percent); - log_info("Imported %u%%.", percent); + + if (isatty_safe(STDERR_FILENO)) + (void) draw_progress_barf( + percent, + "%s %s/%s", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + FORMAT_BYTES(i->written_compressed), + FORMAT_BYTES(i->input_stat.st_size)); + else + log_info("Imported %u%%.", percent); i->last_percent = percent; } @@ -459,6 +469,9 @@ static int raw_import_process(RawImport *i) { return 0; complete: + if (isatty_safe(STDERR_FILENO)) + clear_progress_bar(/* prefix= */ NULL); + r = raw_import_finish(i); finish: diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 976c918246..e82159cb52 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -20,6 +20,7 @@ #include "machine-pool.h" #include "mkdir-label.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "qcow2-util.h" #include "ratelimit.h" @@ -150,7 +151,16 @@ static void tar_import_report_progress(TarImport *i) { return; sd_notifyf(false, "X_IMPORT_PROGRESS=%u%%", percent); - log_info("Imported %u%%.", percent); + + if (isatty_safe(STDERR_FILENO)) + (void) draw_progress_barf( + percent, + "%s %s/%s", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + FORMAT_BYTES(i->written_compressed), + FORMAT_BYTES(i->input_stat.st_size)); + else + log_info("Imported %u%%.", percent); i->last_percent = percent; } @@ -322,6 +332,9 @@ static int tar_import_process(TarImport *i) { return 0; finish: + if (r >= 0 && isatty_safe(STDERR_FILENO)) + clear_progress_bar(/* prefix= */ NULL); + if (i->on_finished) i->on_finished(i, r, i->userdata); else diff --git a/src/import/importctl.c b/src/import/importctl.c index 0ddfa988a6..1ddba76b09 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -936,7 +936,7 @@ static int list_images(int argc, char *argv[], void *userdata) { if (r < 0) return table_log_add_error(r); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (!sd_json_format_enabled(arg_json_format_flags)) r = table_add_many( t, TABLE_STRING, read_only ? "ro" : "rw", diff --git a/src/journal/bsod.c b/src/journal/bsod.c index cdf9e4874c..2f06808cd4 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -143,7 +143,7 @@ static int display_emergency_message_fullscreen(const char *message) { unsigned qr_code_start_row = 1, qr_code_start_column = 1; char ttybuf[STRLEN("/dev/tty") + DECIMAL_STR_MAX(int) + 1]; _cleanup_close_ int fd = -EBADF; - _cleanup_fclose_ FILE *stream = NULL; + _cleanup_fclose_ FILE *f = NULL; char read_character_buffer = '\0'; struct winsize w = { .ws_col = 80, @@ -206,13 +206,15 @@ static int display_emergency_message_fullscreen(const char *message) { goto cleanup; } - r = fdopen_independent(fd, "r+", &stream); + r = fdopen_independent(fd, "r+", &f); if (r < 0) { r = log_error_errno(errno, "Failed to open output file: %m"); goto cleanup; } - r = print_qrcode_full(stream, "Scan the QR code", message, qr_code_start_row, qr_code_start_column, w.ws_col, w.ws_row); + r = print_qrcode_full(f, "Scan the error message", + message, qr_code_start_row, qr_code_start_column, w.ws_col, w.ws_row, + /* check_tty= */ false); if (r < 0) log_warning_errno(r, "QR code could not be printed, ignoring: %m"); @@ -226,7 +228,7 @@ static int display_emergency_message_fullscreen(const char *message) { goto cleanup; } - r = read_one_char(stream, &read_character_buffer, USEC_INFINITY, NULL); + r = read_one_char(f, &read_character_buffer, USEC_INFINITY, NULL); if (r < 0 && r != -EINTR) log_error_errno(r, "Failed to read character: %m"); diff --git a/src/journal/journalctl-authenticate.c b/src/journal/journalctl-authenticate.c index 8167aef7f5..865814cd03 100644 --- a/src/journal/journalctl-authenticate.c +++ b/src/journal/journalctl-authenticate.c @@ -199,9 +199,7 @@ int action_setup_keys(void) { if (!url) return log_oom(); - (void) print_qrcode(stderr, - "To transfer the verification key to your phone scan the QR code below", - url); + (void) print_qrcode(stderr, "Scan the verification key to transfer it to another device", url); #endif return 0; diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index 5a1f311221..3dea69d9fb 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -333,7 +333,7 @@ int action_list_namespaces(void) { } } - if (table_isempty(table) && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(table) && !sd_json_format_enabled(arg_json_format_flags)) { if (!arg_quiet) log_notice("No namespaces found."); } else { diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index f4f30df07d..f059f5dc76 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1370,7 +1370,7 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { if (r < 0) return table_log_add_error(r); - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(arg_json_format_flags)) { r = table_add_many(t, TABLE_FIELD, "Plugin Arguments", TABLE_STRV, strv_skip(c->argv, 1)); diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index 1071d98b19..d784ffb3ff 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -1358,6 +1358,11 @@ static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t r = ndisc_get_dns_name(opt + off, ilen, &res.auth_name); if (r < 0) return r; + r = dns_name_is_valid_ldh(res.auth_name); + if (r < 0) + return r; + if (!r) + return -EBADMSG; if (dns_name_is_root(res.auth_name)) return -EBADMSG; off += ilen; diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 1c0cd6829b..fc891a0b04 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -628,6 +628,11 @@ static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver ** r = lease_parse_dns_name(option + offset, ilen, &res.auth_name); if (r < 0) return r; + r = dns_name_is_valid_ldh(res.auth_name); + if (r < 0) + return r; + if (!r) + return -EBADMSG; if (dns_name_is_root(res.auth_name)) return -EBADMSG; offset += ilen; @@ -1551,7 +1556,8 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { r = deserialize_dnr(&lease->dnr, dnr); if (r < 0) log_debug_errno(r, "Failed to deserialize DNR servers %s, ignoring: %m", dnr); - lease->n_dnr = r; + else + lease->n_dnr = r; } if (ntp) { diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index bc054c42b2..2ff1e87a2e 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" +#include "dns-domain.h" #include "network-common.h" #include "sort-util.h" #include "strv.h" @@ -465,6 +466,11 @@ static int dhcp6_lease_add_dnr(sd_dhcp6_lease *lease, const uint8_t *optval, siz r = dhcp6_option_parse_domainname(optval + offset, ilen, &res.auth_name); if (r < 0) return r; + r = dns_name_is_valid_ldh(res.auth_name); + if (r < 0) + return r; + if (!r) + return -EBADMSG; offset += ilen; /* RFC9463 § 3.1.6: adn only mode */ diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index f765238ba7..03423bdcd3 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -60,8 +60,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NOT_YOUR_DEVICE, EPERM), - /* needs to be EOPNOTSUPP for proper handling in callers of logind_schedule_shutdown() */ - SD_BUS_ERROR_MAP(BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, EBADSLT), SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_NTP_SUPPORT, EOPNOTSUPP), diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 2aeb076823..81c0fd02a6 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -157,6 +157,7 @@ enum { _JSON_BUILD_PAIR_FINITE_USEC, _JSON_BUILD_PAIR_STRING_NON_EMPTY, _JSON_BUILD_PAIR_STRV_NON_EMPTY, + _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, _JSON_BUILD_PAIR_VARIANT_NON_NULL, /* _SD_JSON_BUILD_PAIR_VARIANT_ARRAY_NON_EMPTY, */ _JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY, @@ -204,6 +205,7 @@ enum { #define JSON_BUILD_PAIR_FINITE_USEC(name, u) _JSON_BUILD_PAIR_FINITE_USEC, (const char*) { name }, (usec_t) { u } #define JSON_BUILD_PAIR_STRING_NON_EMPTY(name, s) _JSON_BUILD_PAIR_STRING_NON_EMPTY, (const char*) { name }, (const char*) { s } #define JSON_BUILD_PAIR_STRV_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_NON_EMPTY, (const char*) { name }, (char**) { l } +#define JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY(name, l) _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY, (const char*) { name }, (char**) { l } #define JSON_BUILD_PAIR_VARIANT_NON_NULL(name, v) _JSON_BUILD_PAIR_VARIANT_NON_NULL, (const char*) { name }, (sd_json_variant*) { v } #define JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY(name, v, n) _JSON_BUILD_PAIR_BYTE_ARRAY_NON_EMPTY, (const char*) { name }, (const void*) { v }, (size_t) { n } #define JSON_BUILD_PAIR_IN4_ADDR_NON_NULL(name, v) _JSON_BUILD_PAIR_IN4_ADDR_NON_NULL, (const char*) { name }, (const struct in_addr*) { v } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 297052bdb4..a8a5e47761 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "ansi-color.h" +#include "env-util.h" #include "errno-util.h" #include "escape.h" #include "ether-addr-util.h" @@ -1877,7 +1878,7 @@ _public_ int sd_json_variant_format(sd_json_variant *v, sd_json_format_flags_t f assert_return(v, -EINVAL); assert_return(ret, -EINVAL); - if (flags & SD_JSON_FORMAT_OFF) + if (!sd_json_format_enabled(flags)) return -ENOEXEC; f = memstream_init(&m); @@ -3867,22 +3868,13 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { l = va_arg(ap, char **); - _cleanup_strv_free_ char **el = NULL; - STRV_FOREACH_PAIR(x, y, l) { - char *n = NULL; - - n = strjoin(*x, "=", *y); - if (!n) { - r = -ENOMEM; - goto finish; - } + if (current->n_suppress == 0) { + _cleanup_strv_free_ char **el = NULL; - r = strv_consume(&el, n); + r = strv_env_get_merged(l, &el); if (r < 0) goto finish; - } - if (current->n_suppress == 0) { r = sd_json_variant_new_array_strv(&add, el); if (r < 0) goto finish; @@ -4541,7 +4533,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } - case _JSON_BUILD_PAIR_STRV_NON_EMPTY: { + case _JSON_BUILD_PAIR_STRV_NON_EMPTY: + case _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY: { const char *n; char **l; @@ -4554,11 +4547,19 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { l = va_arg(ap, char **); if (!strv_isempty(l) && current->n_suppress == 0) { + _cleanup_strv_free_ char **el = NULL; + + if (command == _JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY) { + r = strv_env_get_merged(l, &el); + if (r < 0) + goto finish; + } + r = sd_json_variant_new_string(&add, n); if (r < 0) goto finish; - r = sd_json_variant_new_array_strv(&add_more, l); + r = sd_json_variant_new_array_strv(&add_more, el ?: l); if (r < 0) goto finish; } @@ -5614,9 +5615,7 @@ _public_ int sd_json_dispatch_id128(const char *name, sd_json_variant *variant, } _public_ int sd_json_dispatch_signal(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - int *signo = userdata; - uint32_t k; - int r; + int *signo = ASSERT_PTR(userdata), r; assert_return(variant, -EINVAL); @@ -5625,7 +5624,8 @@ _public_ int sd_json_dispatch_signal(const char *name, sd_json_variant *variant, return 0; } - r = sd_json_dispatch_uint32(name, variant, flags, &k); + int k; + r = sd_json_dispatch_int(name, variant, flags, &k); if (r < 0) return r; diff --git a/src/libsystemd/sd-netlink/netlink-message-rtnl.c b/src/libsystemd/sd-netlink/netlink-message-rtnl.c index 29c3af1fec..986e10c623 100644 --- a/src/libsystemd/sd-netlink/netlink-message-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-rtnl.c @@ -342,7 +342,7 @@ int sd_rtnl_message_new_link(sd_netlink *rtnl, sd_netlink_message **ret, uint16_ if (r < 0) return r; - if (nlmsg_type == RTM_NEWLINK) + if (nlmsg_type == RTM_NEWLINK && ifindex == 0) (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; else if (nlmsg_type == RTM_NEWLINKPROP) (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL | NLM_F_APPEND; diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index fb42e3205d..9c9160fd4e 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1356,23 +1356,28 @@ static int varlink_dispatch_method(sd_varlink *v) { r = varlink_idl_validate_method_call(v->current_method, parameters, flags, &bad_field); if (r == -EBADE) { - varlink_log_errno(v, r, "Method %s() called without 'more' flag, but flag needs to be set: %m", + varlink_log_errno(v, r, "Method %s() called without 'more' flag, but flag needs to be set.", method); if (v->state == VARLINK_PROCESSING_METHOD) { r = sd_varlink_error(v, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); - if (r < 0) - return r; + /* If we didn't manage to enqueue an error response, then fail the + * connection completely. Otherwise ignore the error from + * sd_varlink_error() here, as it is synthesized from the function's + * parameters. */ + if (r < 0 && VARLINK_STATE_WANTS_REPLY(v->state)) + goto fail; } } else if (r < 0) { /* Please adjust test/units/end.sh when updating the log message. */ varlink_log_errno(v, r, "Parameters for method %s() didn't pass validation on field '%s': %m", method, strna(bad_field)); - if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { + if (VARLINK_STATE_WANTS_REPLY(v->state)) { r = sd_varlink_error_invalid_parameter_name(v, bad_field); - if (r < 0) - return r; + /* If we didn't manage to enqueue an error response, then fail the connection completely. */ + if (r < 0 && VARLINK_STATE_WANTS_REPLY(v->state)) + goto fail; } } @@ -1381,21 +1386,22 @@ static int varlink_dispatch_method(sd_varlink *v) { if (!invalid) { r = callback(v, parameters, flags, v->userdata); - if (r < 0) { + if (r < 0 && VARLINK_STATE_WANTS_REPLY(v->state)) { varlink_log_errno(v, r, "Callback for %s returned error: %m", method); - /* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */ - if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { - r = sd_varlink_error_errno(v, r); - if (r < 0) - return r; - } + /* We got an error back from the callback. Propagate it to the client if the + * method call remains unanswered. */ + r = sd_varlink_error_errno(v, r); + /* If we didn't manage to enqueue an error response, then fail the connection completely. */ + if (r < 0 && VARLINK_STATE_WANTS_REPLY(v->state)) + goto fail; } } - } else if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { + } else if (VARLINK_STATE_WANTS_REPLY(v->state)) { r = sd_varlink_errorbo(v, SD_VARLINK_ERROR_METHOD_NOT_FOUND, SD_JSON_BUILD_PAIR("method", SD_JSON_BUILD_STRING(method))); - if (r < 0) - return r; + /* If we didn't manage to enqueue an error response, then fail the connection completely. */ + if (r < 0 && VARLINK_STATE_WANTS_REPLY(v->state)) + goto fail; } switch (v->state) { @@ -2558,7 +2564,10 @@ _public_ int sd_varlink_error(sd_varlink *v, const char *error_id, sd_json_varia } else varlink_set_state(v, VARLINK_PROCESSED_METHOD); - return 1; + /* Everything worked. Let's now return the error we got passed as input as negative errno, so that + * programs can just do "return sd_varlink_error();" and get both: a friendly error reply to clients, + * and an error return from the current stack frame. */ + return sd_varlink_error_to_errno(error_id, parameters); } _public_ int sd_varlink_errorb(sd_varlink *v, const char *error_id, ...) { diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 5469d9e345..b184ac7754 100644 --- a/src/libsystemd/sd-varlink/varlink-internal.h +++ b/src/libsystemd/sd-varlink/varlink-internal.h @@ -63,6 +63,13 @@ typedef enum VarlinkState { VARLINK_PENDING_METHOD, \ VARLINK_PENDING_METHOD_MORE) +/* Tests whether we are expected to generate a method call reply, i.e. are processing a method call, except + * one with the ONEWAY flag set. */ +#define VARLINK_STATE_WANTS_REPLY(state) \ + IN_SET(state, \ + VARLINK_PROCESSING_METHOD, \ + VARLINK_PROCESSING_METHOD_MORE) + typedef struct VarlinkJsonQueueItem VarlinkJsonQueueItem; /* A queued message we shall write into the socket, along with the file descriptors to send at the same diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 70950438d8..bb99ae9c6d 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -157,7 +157,7 @@ static int help(void) { " handle-lid-switch\n" " --who=STRING A descriptive string who is inhibiting\n" " --why=STRING A descriptive string why is being inhibited\n" - " --mode=MODE One of block, block-weak, delay, or delay-weak\n" + " --mode=MODE One of block, block-weak, or delay\n" " --list List active inhibitors\n" "\nSee the %s for details.\n", program_invocation_short_name, diff --git a/src/login/loginctl.c b/src/login/loginctl.c index cdb017a931..bda69de076 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -1628,7 +1628,7 @@ static int parse_argv(int argc, char *argv[]) { if (r <= 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) arg_legend = false; break; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 5de882e42b..7557ab2d93 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -310,7 +310,14 @@ static int property_get_inhibited( assert(bus); assert(reply); - w = manager_inhibit_what(m, /* block= */ streq(property, "BlockInhibited")); + if (streq(property, "BlockInhibited")) + w = manager_inhibit_what(m, INHIBIT_BLOCK); + else if (streq(property, "BlockWeakInhibited")) + w = manager_inhibit_what(m, INHIBIT_BLOCK_WEAK); + else if (streq(property, "DelayInhibited")) + w = manager_inhibit_what(m, INHIBIT_DELAY); + else + assert_not_reached(); return sd_bus_message_append(reply, "s", inhibit_what_to_string(w)); } @@ -2656,11 +2663,11 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ if (r < 0) { if (r == -ENOENT) return sd_bus_error_set(error, - BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, - "No upcoming maintenance window scheduled"); - return sd_bus_error_setf(error, - BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, - "Failed to determine next maintenance window"); + BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, + "No upcoming maintenance window scheduled"); + + return sd_bus_error_set_errnof(error, r, + "Failed to determine next maintenance window: %m"); } log_info("Scheduled %s at maintenance window %s", type, FORMAT_TIMESTAMP(elapse)); @@ -3640,7 +3647,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error "Invalid mode specification %s", mode); /* Delay is only supported for shutdown/sleep */ - if (IN_SET(mm, INHIBIT_DELAY, INHIBIT_DELAY_WEAK) && (w & ~(INHIBIT_SHUTDOWN|INHIBIT_SLEEP))) + if (mm == INHIBIT_DELAY && (w & ~(INHIBIT_SHUTDOWN|INHIBIT_SLEEP))) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Delay inhibitors only supported for shutdown and sleep"); @@ -3745,6 +3752,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("BlockInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("BlockWeakInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("DelayInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("InhibitDelayMaxUSec", "t", NULL, offsetof(Manager, inhibit_delay_max), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UserStopDelayUSec", "t", NULL, offsetof(Manager, user_stop_delay), SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index c1798f927c..27ad8f1652 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -363,16 +363,14 @@ bool inhibitor_is_orphan(Inhibitor *i) { return false; } -InhibitWhat manager_inhibit_what(Manager *m, bool block) { +InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mode) { Inhibitor *i; InhibitWhat what = 0; assert(m); HASHMAP_FOREACH(i, m->inhibitors) - if (i->started && - ((!block && IN_SET(i->mode, INHIBIT_DELAY, INHIBIT_DELAY_WEAK)) || - (block && IN_SET(i->mode, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK)))) + if (i->started && i->mode == mode) what |= i->what; return what; @@ -424,13 +422,13 @@ bool manager_is_inhibited( continue; if ((block && !IN_SET(i->mode, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK)) || - (!block && !IN_SET(i->mode, INHIBIT_DELAY, INHIBIT_DELAY_WEAK))) + (!block && i->mode != INHIBIT_DELAY)) continue; if (ignore_inactive && pidref_is_active_session(m, &i->pid) <= 0) continue; - if (IN_SET(i->mode, INHIBIT_BLOCK_WEAK, INHIBIT_DELAY_WEAK) && ignore_uid && i->uid == uid) + if (i->mode == INHIBIT_BLOCK_WEAK && ignore_uid && i->uid == uid) continue; if (!inhibited || @@ -531,7 +529,6 @@ static const char* const inhibit_mode_table[_INHIBIT_MODE_MAX] = { [INHIBIT_BLOCK] = "block", [INHIBIT_BLOCK_WEAK] = "block-weak", [INHIBIT_DELAY] = "delay", - [INHIBIT_DELAY_WEAK] = "delay-weak" }; DEFINE_STRING_TABLE_LOOKUP(inhibit_mode, InhibitMode); diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h index b5167974af..16abd6958c 100644 --- a/src/login/logind-inhibit.h +++ b/src/login/logind-inhibit.h @@ -22,7 +22,6 @@ typedef enum InhibitMode { INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK, INHIBIT_DELAY, - INHIBIT_DELAY_WEAK, _INHIBIT_MODE_MAX, _INHIBIT_MODE_INVALID = -EINVAL, } InhibitMode; @@ -67,7 +66,7 @@ int inhibitor_create_fifo(Inhibitor *i); bool inhibitor_is_orphan(Inhibitor *i); -InhibitWhat manager_inhibit_what(Manager *m, bool block); +InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mode); bool manager_is_inhibited(Manager *m, InhibitWhat w, bool block, dual_timestamp *since, bool ignore_inactive, bool ignore_uid, uid_t uid, Inhibitor **offending); static inline bool inhibit_what_is_valid(InhibitWhat w) { diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 30abc659f8..de70c0a188 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -407,7 +407,7 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu _cleanup_strv_free_ char **env = NULL, **args_wire = NULL, **args = NULL; _cleanup_free_ char *command_line = NULL; Machine *m = ASSERT_PTR(userdata); - const char *p, *unit, *user, *path, *description, *utmp_id; + const char *unit, *user, *path, *description, *utmp_id; int r; assert(message); @@ -484,10 +484,11 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu if (master < 0) return master; - p = path_startswith(pty_name, "/dev/pts/"); - assert(p); - - slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); + /* First try to get an fd for the PTY peer via the new racefree ioctl(), directly. Otherwise go via + * joining the namespace, because it goes by path */ + slave = pty_open_peer_racefree(master, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (ERRNO_IS_NEG_NOT_SUPPORTED(slave)) + slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); if (slave < 0) return slave; @@ -505,6 +506,8 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu return r; /* Name and mode */ + const char *p = ASSERT_PTR(path_startswith(pty_name, "/dev/pts/")); + unit = strjoina("container-shell@", p, ".service"); r = sd_bus_message_append(tm, "ss", unit, "fail"); if (r < 0) diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index c9f44f20ab..bfa4095a3b 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -236,8 +236,6 @@ int lookup_machine_by_name_or_pidref(sd_varlink *link, Manager *manager, const c assert(manager); assert(ret_machine); - /* This returns 0 on success, 1 on error and it is replied, and a negative errno otherwise. */ - if (machine_name) { r = lookup_machine_by_name(link, manager, machine_name, &machine); if (r == -EINVAL) @@ -350,7 +348,7 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met r = lookup_machine_by_name_or_pidref(link, manager, p.name, &p.pidref, &machine); if (r == -ESRCH) return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); - if (r != 0) + if (r < 0) return r; if (isempty(p.swhom)) diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 2c2eb2d918..151d06e5f4 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -392,56 +392,49 @@ static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *paramete } static int json_build_local_addresses(const struct local_address *addresses, size_t n_addresses, sd_json_variant **ret) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; int r; - if (n_addresses == 0) - return 0; - - assert(addresses); + assert(addresses || n_addresses == 0); assert(ret); FOREACH_ARRAY(a, addresses, n_addresses) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; - r = sd_json_buildo( - &entry, + r = sd_json_variant_append_arraybo( + &array, JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("ifindex", a->ifindex), SD_JSON_BUILD_PAIR_INTEGER("family", a->family), SD_JSON_BUILD_PAIR_BYTE_ARRAY("address", &a->address.bytes, FAMILY_ADDRESS_SIZE(a->family))); if (r < 0) return r; - - r = sd_json_variant_append_array(ret, entry); - if (r < 0) - return r; } + *ret = TAKE_PTR(array); return 0; } static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m, bool more, AcquireMetadata am) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *addr_array = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *addr_array = NULL; _cleanup_strv_free_ char **os_release = NULL; uid_t shift = UID_INVALID; - int r, n = 0; + int r; assert(link); assert(m); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - if (should_acquire_metadata(am)) { _cleanup_free_ struct local_address *addresses = NULL; - n = machine_get_addresses(m, &addresses); - if (n < 0 && am == ACQUIRE_METADATA_GRACEFUL) - log_debug_errno(n, "Failed to get address (graceful mode), ignoring: %m"); - else if (n == -ENONET) + + r = machine_get_addresses(m, &addresses); + if (r < 0 && am == ACQUIRE_METADATA_GRACEFUL) + log_debug_errno(r, "Failed to get address (graceful mode), ignoring: %m"); + else if (r == -ENONET) return sd_varlink_error(link, "io.systemd.Machine.NoPrivateNetworking", NULL); - else if (ERRNO_IS_NEG_NOT_SUPPORTED(n)) + else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return sd_varlink_error(link, "io.systemd.Machine.NotAvailable", NULL); - else if (n < 0) - return log_debug_errno(n, "Failed to get addresses: %m"); + else if (r < 0) + return log_debug_errno(r, "Failed to get addresses: %m"); else { - r = json_build_local_addresses(addresses, n, &addr_array); + r = json_build_local_addresses(addresses, r, &addr_array); if (r < 0) return r; } @@ -477,11 +470,11 @@ static int list_machine_one_and_maybe_read_metadata(sd_varlink *link, Machine *m JSON_BUILD_PAIR_STRING_NON_EMPTY("unit", m->unit), SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&m->leader), "leader", JSON_BUILD_PIDREF(&m->leader)), SD_JSON_BUILD_PAIR_CONDITION(dual_timestamp_is_set(&m->timestamp), "timestamp", JSON_BUILD_DUAL_TIMESTAMP(&m->timestamp)), - SD_JSON_BUILD_PAIR_CONDITION(m->vsock_cid != VMADDR_CID_ANY, "vSockCid", SD_JSON_BUILD_UNSIGNED(m->vsock_cid)), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("vSockCid", m->vsock_cid, VMADDR_CID_ANY), JSON_BUILD_PAIR_STRING_NON_EMPTY("sshAddress", m->ssh_address), JSON_BUILD_PAIR_STRING_NON_EMPTY("sshPrivateKeyPath", m->ssh_private_key_path), - SD_JSON_BUILD_PAIR_CONDITION(n > 0, "addresses", SD_JSON_BUILD_VARIANT(addr_array)), - SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(os_release), "OSRelease", JSON_BUILD_STRV_ENV_PAIR(os_release)), + JSON_BUILD_PAIR_VARIANT_NON_NULL("addresses", addr_array), + JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY("OSRelease", os_release), JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("UIDShift", shift, UID_INVALID)); if (r < 0) return r; @@ -517,7 +510,6 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl Manager *m = ASSERT_PTR(userdata); _cleanup_(machine_lookup_parameters_done) MachineLookupParameters p = { .pidref = PIDREF_NULL, - .acquire_metadata = ACQUIRE_METADATA_NO, }; Machine *machine; @@ -534,7 +526,7 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl r = lookup_machine_by_name_or_pidref(link, m, p.name, &p.pidref, &machine); if (r == -ESRCH) return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); - if (r != 0) + if (r < 0) return r; return list_machine_one_and_maybe_read_metadata(link, machine, /* more = */ false, p.acquire_metadata); @@ -584,7 +576,7 @@ static int lookup_machine_and_call_method(sd_varlink *link, sd_json_variant *par r = lookup_machine_by_name_or_pidref(link, manager, p.name, &p.pidref, &machine); if (r == -ESRCH) return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); - if (r != 0) + if (r < 0) return r; return method(link, parameters, flags, machine); @@ -635,8 +627,8 @@ static int list_image_one_and_maybe_read_metadata(sd_varlink *link, Image *image &v, JSON_BUILD_PAIR_STRING_NON_EMPTY("hostname", image->hostname), SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(image->machine_id), "machineId", SD_JSON_BUILD_ID128(image->machine_id)), - SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(image->machine_info), "machineInfo", JSON_BUILD_STRV_ENV_PAIR(image->machine_info)), - SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(image->os_release), "OSRelease", JSON_BUILD_STRV_ENV_PAIR(image->os_release))); + JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY("machineInfo", image->machine_info), + JSON_BUILD_PAIR_STRV_ENV_PAIR_NON_EMPTY("OSRelease", image->os_release)); if (r < 0) return r; } @@ -651,7 +643,7 @@ static int vl_method_list_images(sd_varlink *link, sd_json_variant *parameters, struct params { const char *image_name; AcquireMetadata acquire_metadata; - } p = { .acquire_metadata = ACQUIRE_METADATA_NO }; + } p = {}; int r; static const sd_json_dispatch_field dispatch_table[] = { diff --git a/src/network/fuzz-netdev-parser.c b/src/network/fuzz-netdev-parser.c index f0988bd4cc..7e29ba9b8e 100644 --- a/src/network/fuzz-netdev-parser.c +++ b/src/network/fuzz-netdev-parser.c @@ -10,6 +10,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_(manager_freep) Manager *manager = NULL; _cleanup_fclose_ FILE *f = NULL; _cleanup_(unlink_tempfilep) char netdev_config[] = "/tmp/fuzz-networkd.XXXXXX"; + _cleanup_(netdev_unrefp) NetDev *netdev = NULL; if (outside_size_range(size, 0, 65536)) return 0; @@ -22,6 +23,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { fflush(f); assert_se(manager_new(&manager, /* test_mode = */ true) >= 0); - (void) netdev_load_one(manager, netdev_config); + (void) netdev_load_one(manager, netdev_config, &netdev); return 0; } diff --git a/src/network/netdev/batadv.c b/src/network/netdev/batadv.c index e600727c20..9806d8eb7c 100644 --- a/src/network/netdev/batadv.c +++ b/src/network/netdev/batadv.c @@ -163,6 +163,9 @@ static int netdev_batadv_post_create(NetDev *netdev, Link *link) { assert(netdev); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = sd_genl_message_new(netdev->manager->genl, BATADV_NL_NAME, BATADV_CMD_SET_MESH, &message); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not allocate netlink message: %m"); diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c index 25b0f81aa0..da5b332277 100644 --- a/src/network/netdev/bridge.c +++ b/src/network/netdev/bridge.c @@ -159,14 +159,13 @@ static int netdev_bridge_post_create(NetDev *netdev, Link *link) { assert(netdev); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &req, RTM_NEWLINK, netdev->ifindex); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not allocate netlink message: %m"); - r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK); - if (r < 0) - return log_link_error_errno(link, r, "Could not set netlink message flags: %m"); - r = netdev_bridge_post_create_message(netdev, req); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not create netlink message: %m"); diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c index e962c6c64b..a85aff2e6d 100644 --- a/src/network/netdev/fou-tunnel.c +++ b/src/network/netdev/fou-tunnel.c @@ -89,6 +89,7 @@ static int netdev_create_fou_tunnel_message(NetDev *netdev, sd_netlink_message * int r; assert(netdev); + assert(netdev->manager); r = sd_genl_message_new(netdev->manager->genl, FOU_GENL_NAME, FOU_CMD_ADD, &m); if (r < 0) @@ -128,6 +129,9 @@ static int netdev_fou_tunnel_create(NetDev *netdev) { assert(FOU(netdev)); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = netdev_create_fou_tunnel_message(netdev, &m); if (r < 0) return r; diff --git a/src/network/netdev/ipoib.c b/src/network/netdev/ipoib.c index 0065a5452e..6932c62e2a 100644 --- a/src/network/netdev/ipoib.c +++ b/src/network/netdev/ipoib.c @@ -53,10 +53,6 @@ int ipoib_set_netlink_message(Link *link, sd_netlink_message *m) { assert(link->network); assert(m); - r = sd_netlink_message_set_flags(m, NLM_F_REQUEST | NLM_F_ACK); - if (r < 0) - return r; - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); if (r < 0) return r; diff --git a/src/network/netdev/ipvlan.c b/src/network/netdev/ipvlan.c index 892678b4ad..5cfae1eb64 100644 --- a/src/network/netdev/ipvlan.c +++ b/src/network/netdev/ipvlan.c @@ -37,6 +37,13 @@ static int netdev_ipvlan_fill_message_create(NetDev *netdev, Link *link, sd_netl return 0; } +static bool ipvlan_can_set_mac(NetDev *netdev, const struct hw_addr_data *hw_addr) { + assert(netdev); + + /* MAC address cannot be updated. Even unchanged, IFLA_ADDRESS attribute cannot be set in the message. */ + return netdev->ifindex <= 0; +} + static void ipvlan_init(NetDev *netdev) { IPVlan *m = ASSERT_PTR(netdev)->kind == NETDEV_KIND_IPVLAN ? IPVLAN(netdev) : IPVTAP(netdev); @@ -50,6 +57,7 @@ const NetDevVTable ipvlan_vtable = { .sections = NETDEV_COMMON_SECTIONS "IPVLAN\0", .fill_message_create = netdev_ipvlan_fill_message_create, .create_type = NETDEV_CREATE_STACKED, + .can_set_mac = ipvlan_can_set_mac, .iftype = ARPHRD_ETHER, .generate_mac = true, }; @@ -60,6 +68,7 @@ const NetDevVTable ipvtap_vtable = { .sections = NETDEV_COMMON_SECTIONS "IPVTAP\0", .fill_message_create = netdev_ipvlan_fill_message_create, .create_type = NETDEV_CREATE_STACKED, + .can_set_mac = ipvlan_can_set_mac, .iftype = ARPHRD_ETHER, .generate_mac = true, }; diff --git a/src/network/netdev/l2tp-tunnel.c b/src/network/netdev/l2tp-tunnel.c index c1372bb1e0..c87e44797b 100644 --- a/src/network/netdev/l2tp-tunnel.c +++ b/src/network/netdev/l2tp-tunnel.c @@ -94,6 +94,8 @@ static int l2tp_session_new_static(L2tpTunnel *t, const char *filename, unsigned static int netdev_l2tp_create_message_tunnel(NetDev *netdev, union in_addr_union *local_address, sd_netlink_message **ret) { assert(local_address); + assert(netdev); + assert(netdev->manager); _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; uint16_t encap_type; @@ -188,6 +190,7 @@ static int netdev_l2tp_create_message_session(NetDev *netdev, L2tpSession *sessi int r; assert(netdev); + assert(netdev->manager); assert(session); assert(session->tunnel); @@ -385,6 +388,11 @@ static int l2tp_create_session(NetDev *netdev, L2tpSession *session) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *n = NULL; int r; + assert(netdev); + + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = netdev_l2tp_create_message_session(netdev, session, &n); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); @@ -429,6 +437,9 @@ static int l2tp_create_tunnel(NetDev *netdev) { L2tpTunnel *t = L2TP(netdev); int r; + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = l2tp_get_local_address(netdev, &local_address); if (r < 0) return log_netdev_error_errno(netdev, r, "Could not find local address."); diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 187da41344..6dd434f803 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -224,6 +224,7 @@ static int netdev_macsec_create_message(NetDev *netdev, int command, sd_netlink_ assert(netdev); assert(netdev->ifindex > 0); + assert(netdev->manager); r = sd_genl_message_new(netdev->manager->genl, MACSEC_GENL_NAME, command, &m); if (r < 0) @@ -334,6 +335,9 @@ static int netdev_macsec_configure_receive_association(NetDev *netdev, ReceiveAs assert(netdev); assert(a); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = netdev_macsec_create_message(netdev, MACSEC_CMD_ADD_RXSA, &m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); @@ -406,6 +410,9 @@ static int netdev_macsec_configure_receive_channel(NetDev *netdev, ReceiveChanne assert(netdev); assert(c); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = netdev_macsec_create_message(netdev, MACSEC_CMD_ADD_RXSC, &m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); @@ -454,6 +461,9 @@ static int netdev_macsec_configure_transmit_association(NetDev *netdev, Transmit assert(netdev); assert(a); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + r = netdev_macsec_create_message(netdev, MACSEC_CMD_ADD_TXSA, &m); if (r < 0) return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); @@ -499,12 +509,6 @@ static int netdev_macsec_fill_message_create(NetDev *netdev, Link *link, sd_netl MACsec *v = MACSEC(netdev); int r; - if (v->port > 0) { - r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port); - if (r < 0) - return r; - } - if (v->encrypt >= 0) { r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCRYPT, v->encrypt); if (r < 0) @@ -515,6 +519,20 @@ static int netdev_macsec_fill_message_create(NetDev *netdev, Link *link, sd_netl if (r < 0) return r; + /* The properties below cannot be updated, and the kernel refuses the whole request if one of the + * following attributes is set for an existing interface. */ + if (netdev->ifindex > 0) + return 0; + + if (v->port > 0) { + r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port); + if (r < 0) + return r; + } + + /* Currently not supported by networkd, but IFLA_MACSEC_CIPHER_SUITE, IFLA_MACSEC_ICV_LEN, and + * IFLA_MACSEC_SCI can neither set for an existing interface. */ + return 0; } diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 8e58a1ae12..85760c741d 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -34,6 +34,7 @@ #include "networkd-queue.h" #include "networkd-setlink.h" #include "networkd-sriov.h" +#include "networkd-state-file.h" #include "nlmon.h" #include "path-lookup.h" #include "siphash24.h" @@ -241,6 +242,7 @@ static NetDev* netdev_free(NetDev *netdev) { condition_free_list(netdev->conditions); free(netdev->filename); strv_free(netdev->dropins); + hashmap_free(netdev->stats_by_path); free(netdev->description); free(netdev->ifname); @@ -273,18 +275,17 @@ void netdev_drop(NetDev *netdev) { netdev_detach(netdev); } -int netdev_attach_name(NetDev *netdev, const char *name) { +static int netdev_attach_name_full(NetDev *netdev, const char *name, Hashmap **netdevs) { int r; assert(netdev); - assert(netdev->manager); assert(name); - r = hashmap_ensure_put(&netdev->manager->netdevs, &string_hash_ops, name, netdev); + r = hashmap_ensure_put(netdevs, &string_hash_ops, name, netdev); if (r == -ENOMEM) return log_oom(); if (r == -EEXIST) { - NetDev *n = hashmap_get(netdev->manager->netdevs, name); + NetDev *n = hashmap_get(*netdevs, name); assert(n); if (!streq(netdev->filename, n->filename)) @@ -299,6 +300,13 @@ int netdev_attach_name(NetDev *netdev, const char *name) { return 0; } +int netdev_attach_name(NetDev *netdev, const char *name) { + assert(netdev); + assert(netdev->manager); + + return netdev_attach_name_full(netdev, name, &netdev->manager->netdevs); +} + static int netdev_attach(NetDev *netdev) { int r; @@ -345,16 +353,16 @@ void link_assign_netdev(Link *link) { old = TAKE_PTR(link->netdev); if (netdev_get(link->manager, link->ifname, &netdev) < 0) - return; + goto not_found; int ifindex = NETDEV_VTABLE(netdev)->get_ifindex ? NETDEV_VTABLE(netdev)->get_ifindex(netdev, link->ifname) : netdev->ifindex; if (ifindex != link->ifindex) - return; + goto not_found; if (NETDEV_VTABLE(netdev)->iftype != link->iftype) - return; + goto not_found; if (!NETDEV_VTABLE(netdev)->skip_netdev_kind_check) { const char *kind; @@ -365,13 +373,23 @@ void link_assign_netdev(Link *link) { kind = netdev_kind_to_string(netdev->kind); if (!streq_ptr(kind, link->kind)) - return; + goto not_found; } link->netdev = netdev_ref(netdev); - if (netdev != old) - log_link_debug(link, "Found matching .netdev file: %s", netdev->filename); + if (netdev == old) + return; /* The same NetDev found. */ + + log_link_debug(link, "Found matching .netdev file: %s", netdev->filename); + link_dirty(link); + return; + +not_found: + + if (old) + /* Previously assigned NetDev is detached from Manager? Update the state file. */ + link_dirty(link); } void netdev_enter_failed(NetDev *netdev) { @@ -395,6 +413,17 @@ int netdev_enter_ready(NetDev *netdev) { return 0; } +bool netdev_needs_reconfigure(NetDev *netdev, NetDevLocalAddressType type) { + assert(netdev); + assert(type < _NETDEV_LOCAL_ADDRESS_TYPE_MAX); + + if (type < 0) + return true; + + return NETDEV_VTABLE(netdev)->needs_reconfigure && + NETDEV_VTABLE(netdev)->needs_reconfigure(netdev, type); +} + /* callback for netdev's created without a backing Link */ static int netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) { int r; @@ -605,6 +634,31 @@ finalize: return 0; } +static bool netdev_can_set_mac(NetDev *netdev, const struct hw_addr_data *hw_addr) { + assert(netdev); + assert(hw_addr); + + if (hw_addr->length <= 0) + return false; + + if (!NETDEV_VTABLE(netdev)->can_set_mac) + return true; + + return NETDEV_VTABLE(netdev)->can_set_mac(netdev, hw_addr); +} + +static bool netdev_can_set_mtu(NetDev *netdev, uint32_t mtu) { + assert(netdev); + + if (mtu <= 0) + return false; + + if (!NETDEV_VTABLE(netdev)->can_set_mtu) + return true; + + return NETDEV_VTABLE(netdev)->can_set_mtu(netdev, mtu); +} + static int netdev_create_message(NetDev *netdev, Link *link, sd_netlink_message *m) { int r; @@ -617,14 +671,14 @@ static int netdev_create_message(NetDev *netdev, Link *link, sd_netlink_message if (r < 0) return r; - if (hw_addr.length > 0) { + if (netdev_can_set_mac(netdev, &hw_addr)) { log_netdev_debug(netdev, "Using MAC address: %s", HW_ADDR_TO_STR(&hw_addr)); r = netlink_message_append_hw_addr(m, IFLA_ADDRESS, &hw_addr); if (r < 0) return r; } - if (netdev->mtu != 0) { + if (netdev_can_set_mtu(netdev, netdev->mtu)) { r = sd_netlink_message_append_u32(m, IFLA_MTU, netdev->mtu); if (r < 0) return r; @@ -670,6 +724,7 @@ static int independent_netdev_create(NetDev *netdev) { int r; assert(netdev); + assert(netdev->manager); /* create netdev */ if (NETDEV_VTABLE(netdev)->create) { @@ -681,7 +736,7 @@ static int independent_netdev_create(NetDev *netdev) { return 0; } - r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, netdev->ifindex); if (r < 0) return r; @@ -710,7 +765,7 @@ static int stacked_netdev_create(NetDev *netdev, Link *link, Request *req) { assert(link); assert(req); - r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0); + r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, netdev->ifindex); if (r < 0) return r; @@ -755,9 +810,6 @@ static bool link_is_ready_to_create_stacked_netdev(Link *link) { static int netdev_is_ready_to_create(NetDev *netdev, Link *link) { assert(netdev); - if (netdev->state != NETDEV_STATE_LOADING) - return false; - if (link && !link_is_ready_to_create_stacked_netdev(link)) return false; @@ -774,6 +826,9 @@ static int stacked_netdev_process_request(Request *req, Link *link, void *userda assert(req); assert(link); + if (!netdev_is_managed(netdev)) + return 1; /* Already detached, due to e.g. reloading .netdev files, cancelling the request. */ + r = netdev_is_ready_to_create(netdev, link); if (r <= 0) return r; @@ -816,8 +871,8 @@ int link_request_stacked_netdev(Link *link, NetDev *netdev) { if (!netdev_is_stacked(netdev)) return -EINVAL; - if (!IN_SET(netdev->state, NETDEV_STATE_LOADING, NETDEV_STATE_FAILED) || netdev->ifindex > 0) - return 0; /* Already created. */ + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ link->stacked_netdevs_created = false; r = link_queue_request_full(link, REQUEST_TYPE_NETDEV_STACKED, @@ -843,6 +898,9 @@ static int independent_netdev_process_request(Request *req, Link *link, void *us assert(!link); + if (!netdev_is_managed(netdev)) + return 1; /* Already detached, due to e.g. reloading .netdev files, cancelling the request. */ + r = netdev_is_ready_to_create(netdev, NULL); if (r <= 0) return r; @@ -866,6 +924,12 @@ static int netdev_request_to_create(NetDev *netdev) { if (netdev_is_stacked(netdev)) return 0; + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + + if (netdev->state != NETDEV_STATE_LOADING) + return 0; /* Already configured (at least tried previously). Not necessary to reconfigure. */ + r = netdev_is_ready_to_create(netdev, NULL); if (r < 0) return r; @@ -885,21 +949,20 @@ static int netdev_request_to_create(NetDev *netdev) { return 0; } -int netdev_load_one(Manager *manager, const char *filename) { +int netdev_load_one(Manager *manager, const char *filename, NetDev **ret) { _cleanup_(netdev_unrefp) NetDev *netdev_raw = NULL, *netdev = NULL; const char *dropin_dirname; int r; assert(manager); assert(filename); + assert(ret); r = null_or_empty_path(filename); if (r < 0) return log_warning_errno(r, "Failed to check if \"%s\" is empty: %m", filename); - if (r > 0) { - log_debug("Skipping empty file: %s", filename); - return 0; - } + if (r > 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Skipping empty file: %s", filename); netdev_raw = new(NetDev, 1); if (!netdev_raw) @@ -924,10 +987,8 @@ int netdev_load_one(Manager *manager, const char *filename) { return r; /* config_parse_many() logs internally. */ /* skip out early if configuration does not match the environment */ - if (!condition_test_list(netdev_raw->conditions, environ, NULL, NULL, NULL)) { - log_debug("%s: Conditions in the file do not match the system environment, skipping.", filename); - return 0; - } + if (!condition_test_list(netdev_raw->conditions, environ, NULL, NULL, NULL)) + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "%s: Conditions in the file do not match the system environment, skipping.", filename); if (netdev_raw->kind == _NETDEV_KIND_INVALID) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "NetDev has no Kind= configured in \"%s\", ignoring.", filename); @@ -954,7 +1015,7 @@ int netdev_load_one(Manager *manager, const char *filename) { config_item_perf_lookup, network_netdev_gperf_lookup, CONFIG_PARSE_WARN, netdev, - NULL, + &netdev->stats_by_path, &netdev->dropins); if (r < 0) return r; /* config_parse_many() logs internally. */ @@ -970,35 +1031,111 @@ int netdev_load_one(Manager *manager, const char *filename) { if (!netdev->filename) return log_oom(); - r = netdev_attach(netdev); - if (r < 0) - return r; - log_syntax(/* unit = */ NULL, LOG_DEBUG, filename, /* config_line = */ 0, /* error = */ 0, "Successfully loaded."); - r = netdev_request_to_create(netdev); + *ret = TAKE_PTR(netdev); + return 0; +} + +int netdev_load(Manager *manager) { + _cleanup_strv_free_ char **files = NULL; + int r; + + assert(manager); + + r = conf_files_list_strv(&files, ".netdev", NULL, 0, NETWORK_DIRS); if (r < 0) - return r; /* netdev_request_to_create() logs internally. */ + return log_error_errno(r, "Failed to enumerate netdev files: %m"); + + STRV_FOREACH(f, files) { + _cleanup_(netdev_unrefp) NetDev *netdev = NULL; + + if (netdev_load_one(manager, *f, &netdev) < 0) + continue; + + if (netdev_attach(netdev) < 0) + continue; + + if (netdev_request_to_create(netdev) < 0) + continue; + + TAKE_PTR(netdev); + } - TAKE_PTR(netdev); return 0; } -int netdev_load(Manager *manager, bool reload) { +int netdev_reload(Manager *manager) { + _cleanup_hashmap_free_ Hashmap *new_netdevs = NULL; _cleanup_strv_free_ char **files = NULL; int r; assert(manager); - if (!reload) - hashmap_clear_with_destructor(manager->netdevs, netdev_unref); - r = conf_files_list_strv(&files, ".netdev", NULL, 0, NETWORK_DIRS); if (r < 0) return log_error_errno(r, "Failed to enumerate netdev files: %m"); - STRV_FOREACH(f, files) - (void) netdev_load_one(manager, *f); + STRV_FOREACH(f, files) { + _cleanup_(netdev_unrefp) NetDev *netdev = NULL; + NetDev *old; + + if (netdev_load_one(manager, *f, &netdev) < 0) + continue; + + if (netdev_get(manager, netdev->ifname, &old) < 0) { + log_netdev_debug(netdev, "Found new .netdev file: %s", netdev->filename); + + if (netdev_attach_name_full(netdev, netdev->ifname, &new_netdevs) >= 0) + TAKE_PTR(netdev); + + continue; + } + + if (!stats_by_path_equal(netdev->stats_by_path, old->stats_by_path)) { + log_netdev_debug(netdev, "Found updated .netdev file: %s", netdev->filename); + + /* Copy ifindex. */ + netdev->ifindex = old->ifindex; + + if (netdev_attach_name_full(netdev, netdev->ifname, &new_netdevs) >= 0) + TAKE_PTR(netdev); + + continue; + } + + /* Keep the original object, and drop the new one. */ + if (netdev_attach_name_full(old, old->ifname, &new_netdevs) >= 0) + netdev_ref(old); + } + + /* Detach old NetDev objects from Manager. + * Note, the same object may be registered with multiple names, and netdev_detach() may drop multiple + * entries. Hence, hashmap_free_with_destructor() cannot be used. */ + for (NetDev *n; (n = hashmap_first(manager->netdevs)); ) + netdev_detach(n); + + /* Attach new NetDev objects to Manager. */ + for (;;) { + _cleanup_(netdev_unrefp) NetDev *netdev = hashmap_steal_first(new_netdevs); + if (!netdev) + break; + + netdev->manager = manager; + if (netdev_attach(netdev) < 0) + continue; + + /* Create a new netdev or update existing netdev, */ + if (netdev_request_to_create(netdev) < 0) + continue; + + TAKE_PTR(netdev); + } + + /* Reassign NetDev objects to Link object. */ + Link *link; + HASHMAP_FOREACH(link, manager->links_by_index) + link_assign_netdev(link); return 0; } diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h index 10b9d0dd77..765496d044 100644 --- a/src/network/netdev/netdev.h +++ b/src/network/netdev/netdev.h @@ -8,6 +8,7 @@ #include "hash-funcs.h" #include "list.h" #include "log-link.h" +#include "netdev-util.h" #include "networkd-link.h" #include "time-util.h" @@ -118,6 +119,7 @@ typedef struct NetDev { char *filename; char **dropins; + Hashmap *stats_by_path; LIST_HEAD(Condition, conditions); @@ -179,6 +181,16 @@ typedef struct NetDevVTable { /* get ifindex of the netdev. */ int (*get_ifindex)(NetDev *netdev, const char *name); + /* provides if MAC address can be set. If this is not set, assumed to be yes. */ + bool (*can_set_mac)(NetDev *netdev, const struct hw_addr_data *hw_addr); + + /* provides if MTU can be set. If this is not set, assumed to be yes. */ + bool (*can_set_mtu)(NetDev *netdev, uint32_t mtu); + + /* provides if the netdev needs to be reconfigured when a specified type of address on the underlying + * interface is updated. */ + bool (*needs_reconfigure)(NetDev *netdev, NetDevLocalAddressType type); + /* expected iftype, e.g. ARPHRD_ETHER. */ uint16_t iftype; @@ -211,14 +223,15 @@ NetDev* netdev_detach_name(NetDev *netdev, const char *name); void netdev_detach(NetDev *netdev); int netdev_set_ifindex_internal(NetDev *netdev, int ifindex); -int netdev_load(Manager *manager, bool reload); -int netdev_load_one(Manager *manager, const char *filename); +int netdev_load(Manager *manager); +int netdev_reload(Manager *manager); +int netdev_load_one(Manager *manager, const char *filename, NetDev **ret); void netdev_drop(NetDev *netdev); void netdev_enter_failed(NetDev *netdev); int netdev_enter_ready(NetDev *netdev); -NetDev *netdev_unref(NetDev *netdev); -NetDev *netdev_ref(NetDev *netdev); +NetDev* netdev_unref(NetDev *netdev); +NetDev* netdev_ref(NetDev *netdev); DEFINE_TRIVIAL_DESTRUCTOR(netdev_destroy_callback, NetDev, netdev_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(NetDev*, netdev_unref); @@ -229,6 +242,7 @@ int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *newlink); int netdev_generate_hw_addr(NetDev *netdev, Link *link, const char *name, const struct hw_addr_data *hw_addr, struct hw_addr_data *ret); +bool netdev_needs_reconfigure(NetDev *netdev, NetDevLocalAddressType type); int link_request_stacked_netdev(Link *link, NetDev *netdev); const char* netdev_kind_to_string(NetDevKind d) _const_; diff --git a/src/network/netdev/tunnel.c b/src/network/netdev/tunnel.c index 2bf58086b2..0339c49c8f 100644 --- a/src/network/netdev/tunnel.c +++ b/src/network/netdev/tunnel.c @@ -34,7 +34,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_ip6tnl_mode, ip6tnl_mode, Ip6TnlMode); #define HASH_KEY SD_ID128_MAKE(74,c4,de,12,f3,d9,41,34,bb,3d,c1,a4,42,93,50,87) -int dhcp4_pd_create_6rd_tunnel_name(Link *link, char **ret) { +static int dhcp4_pd_create_6rd_tunnel_name(Link *link) { _cleanup_free_ char *ifname_alloc = NULL; uint8_t ipv4masklen, sixrd_prefixlen, *buf, *p; struct in_addr ipv4address; @@ -47,13 +47,16 @@ int dhcp4_pd_create_6rd_tunnel_name(Link *link, char **ret) { assert(link); assert(link->dhcp_lease); + if (link->dhcp4_6rd_tunnel_name) + return 0; /* Already set. Do not change even if the 6rd option is changed. */ + r = sd_dhcp_lease_get_address(link->dhcp_lease, &ipv4address); if (r < 0) - return log_link_debug_errno(link, r, "Failed to get DHCPv4 address: %m"); + return r; r = sd_dhcp_lease_get_6rd(link->dhcp_lease, &ipv4masklen, &sixrd_prefixlen, &sixrd_prefix, NULL, NULL); if (r < 0) - return log_link_debug_errno(link, r, "Failed to get 6rd option: %m"); + return r; sz = sizeof(uint8_t) * 2 + sizeof(struct in6_addr) + sizeof(struct in_addr); buf = newa(uint8_t, sz); @@ -80,21 +83,44 @@ int dhcp4_pd_create_6rd_tunnel_name(Link *link, char **ret) { ifname_alloc = strdup(ifname); if (!ifname_alloc) - return log_oom_debug(); + return -ENOMEM; - *ret = TAKE_PTR(ifname_alloc); + link->dhcp4_6rd_tunnel_name = TAKE_PTR(ifname_alloc); return 0; } -static int dhcp4_pd_create_6rd_tunnel_message( - Link *link, - sd_netlink_message *m, - const struct in_addr *ipv4address, - uint8_t ipv4masklen, - const struct in6_addr *sixrd_prefix, - uint8_t sixrd_prefixlen) { +int dhcp4_pd_create_6rd_tunnel(Link *link, link_netlink_message_handler_t callback) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint8_t ipv4masklen, sixrd_prefixlen; + struct in_addr ipv4address; + struct in6_addr sixrd_prefix; + Link *sit = NULL; int r; + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->dhcp_lease); + assert(callback); + + r = sd_dhcp_lease_get_address(link->dhcp_lease, &ipv4address); + if (r < 0) + return r; + + r = sd_dhcp_lease_get_6rd(link->dhcp_lease, &ipv4masklen, &sixrd_prefixlen, &sixrd_prefix, NULL, NULL); + if (r < 0) + return r; + + r = dhcp4_pd_create_6rd_tunnel_name(link); + if (r < 0) + return r; + + (void) link_get_by_name(link->manager, link->dhcp4_6rd_tunnel_name, &sit); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, sit ? sit->ifindex : 0); + if (r < 0) + return r; + r = sd_netlink_message_append_string(m, IFLA_IFNAME, link->dhcp4_6rd_tunnel_name); if (r < 0) return r; @@ -107,7 +133,7 @@ static int dhcp4_pd_create_6rd_tunnel_message( if (r < 0) return r; - r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_LOCAL, ipv4address); + r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_LOCAL, &ipv4address); if (r < 0) return r; @@ -115,7 +141,7 @@ static int dhcp4_pd_create_6rd_tunnel_message( if (r < 0) return r; - r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_6RD_PREFIX, sixrd_prefix); + r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_6RD_PREFIX, &sixrd_prefix); if (r < 0) return r; @@ -123,7 +149,7 @@ static int dhcp4_pd_create_6rd_tunnel_message( if (r < 0) return r; - struct in_addr relay_prefix = *ipv4address; + struct in_addr relay_prefix = ipv4address; (void) in4_addr_mask(&relay_prefix, ipv4masklen); r = sd_netlink_message_append_u32(m, IFLA_IPTUN_6RD_RELAY_PREFIX, relay_prefix.s_addr); if (r < 0) @@ -141,48 +167,12 @@ static int dhcp4_pd_create_6rd_tunnel_message( if (r < 0) return r; - return 0; -} - -int dhcp4_pd_create_6rd_tunnel(Link *link, link_netlink_message_handler_t callback) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - uint8_t ipv4masklen, sixrd_prefixlen; - struct in_addr ipv4address; - struct in6_addr sixrd_prefix; - int r; - - assert(link); - assert(link->ifindex > 0); - assert(link->manager); - assert(link->dhcp_lease); - assert(link->dhcp4_6rd_tunnel_name); - assert(callback); - - r = sd_dhcp_lease_get_address(link->dhcp_lease, &ipv4address); - if (r < 0) - return log_link_debug_errno(link, r, "Failed to get DHCPv4 address: %m"); - - r = sd_dhcp_lease_get_6rd(link->dhcp_lease, &ipv4masklen, &sixrd_prefixlen, &sixrd_prefix, NULL, NULL); - if (r < 0) - return log_link_debug_errno(link, r, "Failed to get 6rd option: %m"); - - r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_link_debug_errno(link, r, "Failed to create netlink message: %m"); - - r = dhcp4_pd_create_6rd_tunnel_message(link, m, - &ipv4address, ipv4masklen, - &sixrd_prefix, sixrd_prefixlen); - if (r < 0) - return log_link_debug_errno(link, r, "Failed to fill netlink message: %m"); - r = netlink_call_async(link->manager->rtnl, NULL, m, callback, link_netlink_destroy_callback, link); if (r < 0) - return log_link_debug_errno(link, r, "Could not send netlink message: %m"); + return r; link_ref(link); - return 0; } @@ -681,34 +671,27 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) { } } - if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE) && - !IN_SET(t->family, AF_UNSPEC, AF_INET)) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "vti/ipip/sit/gre tunnel without a local/remote IPv4 address configured in %s. Ignoring", filename); + if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE, NETDEV_KIND_GRETAP, NETDEV_KIND_ERSPAN)) { + if (!IN_SET(t->family, AF_UNSPEC, AF_INET)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s tunnel without a local/remote IPv4 address configured in %s, ignoring.", + netdev_kind_to_string(netdev->kind), filename); - if (IN_SET(netdev->kind, NETDEV_KIND_GRETAP, NETDEV_KIND_ERSPAN) && - (t->family != AF_INET || !in_addr_is_set(t->family, &t->remote))) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "gretap/erspan tunnel without a remote IPv4 address configured in %s. Ignoring", filename); - - if ((IN_SET(netdev->kind, NETDEV_KIND_VTI6, NETDEV_KIND_IP6TNL) && t->family != AF_INET6) || - (netdev->kind == NETDEV_KIND_IP6GRE && !IN_SET(t->family, AF_UNSPEC, AF_INET6))) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "vti6/ip6tnl/ip6gre tunnel without a local/remote IPv6 address configured in %s. Ignoring", filename); + t->family = AF_INET; /* For netlink_message_append_in_addr_union(). */ + } - if (netdev->kind == NETDEV_KIND_IP6GRETAP && - (t->family != AF_INET6 || !in_addr_is_set(t->family, &t->remote))) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "ip6gretap tunnel without a remote IPv6 address configured in %s. Ignoring", filename); + if (IN_SET(netdev->kind, NETDEV_KIND_VTI6, NETDEV_KIND_IP6TNL, NETDEV_KIND_IP6GRE, NETDEV_KIND_IP6GRETAP)) { + if (!IN_SET(t->family, AF_UNSPEC, AF_INET6)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s tunnel without a local/remote IPv6 address configured in %s, ignoring,", + netdev_kind_to_string(netdev->kind), filename); + t->family = AF_INET6; /* For netlink_message_append_in_addr_union(). */ + } if (t->fou_tunnel && t->fou_destination_port <= 0) return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), "FooOverUDP missing port configured in %s. Ignoring", filename); - /* netlink_message_append_in_addr_union() is used for vti/vti6. So, t->family cannot be AF_UNSPEC. */ - if (netdev->kind == NETDEV_KIND_VTI) - t->family = AF_INET; - if (t->assign_to_loopback) t->independent = true; @@ -725,6 +708,14 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) { return 0; } +static bool tunnel_needs_reconfigure(NetDev *netdev, NetDevLocalAddressType type) { + assert(type >= 0 && type < _NETDEV_LOCAL_ADDRESS_TYPE_MAX); + + Tunnel *t = ASSERT_PTR(TUNNEL(netdev)); + + return t->local_type == type; +} + static int unset_local(Tunnel *t) { assert(t); @@ -1136,6 +1127,7 @@ const NetDevVTable ipip_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_TUNNEL, }; @@ -1147,6 +1139,7 @@ const NetDevVTable sit_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_SIT, }; @@ -1158,6 +1151,7 @@ const NetDevVTable vti_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_TUNNEL, }; @@ -1169,6 +1163,7 @@ const NetDevVTable vti6_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_TUNNEL6, }; @@ -1180,6 +1175,7 @@ const NetDevVTable gre_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_IPGRE, }; @@ -1191,6 +1187,7 @@ const NetDevVTable gretap_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_ETHER, .generate_mac = true, }; @@ -1203,6 +1200,7 @@ const NetDevVTable ip6gre_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_IP6GRE, }; @@ -1214,6 +1212,7 @@ const NetDevVTable ip6gretap_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_ETHER, .generate_mac = true, }; @@ -1226,6 +1225,7 @@ const NetDevVTable ip6tnl_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_TUNNEL6, }; @@ -1237,6 +1237,7 @@ const NetDevVTable erspan_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_tunnel_is_ready_to_create, .config_verify = netdev_tunnel_verify, + .needs_reconfigure = tunnel_needs_reconfigure, .iftype = ARPHRD_ETHER, .generate_mac = true, }; diff --git a/src/network/netdev/tunnel.h b/src/network/netdev/tunnel.h index cf26cfad98..6cd03b032d 100644 --- a/src/network/netdev/tunnel.h +++ b/src/network/netdev/tunnel.h @@ -69,7 +69,6 @@ typedef struct Tunnel { uint8_t sixrd_prefixlen; } Tunnel; -int dhcp4_pd_create_6rd_tunnel_name(Link *link, char **ret); int dhcp4_pd_create_6rd_tunnel(Link *link, link_netlink_message_handler_t callback); DEFINE_NETDEV_CAST(IPIP, Tunnel); diff --git a/src/network/netdev/tuntap.c b/src/network/netdev/tuntap.c index f5be31ed94..c012bc1591 100644 --- a/src/network/netdev/tuntap.c +++ b/src/network/netdev/tuntap.c @@ -103,6 +103,7 @@ static int netdev_create_tuntap(NetDev *netdev) { int r; assert(netdev); + assert(netdev->manager); t = TUNTAP(netdev); assert(t); diff --git a/src/network/netdev/vxlan.c b/src/network/netdev/vxlan.c index e928b20d83..9f22794d34 100644 --- a/src/network/netdev/vxlan.c +++ b/src/network/netdev/vxlan.c @@ -44,12 +44,6 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli int local_family, r; VxLan *v = VXLAN(netdev); - if (v->vni <= VXLAN_VID_MAX) { - r = sd_netlink_message_append_u32(m, IFLA_VXLAN_ID, v->vni); - if (r < 0) - return r; - } - if (in_addr_is_set(v->group_family, &v->group)) { if (v->group_family == AF_INET) r = sd_netlink_message_append_in_addr(m, IFLA_VXLAN_GROUP, &v->group.in); @@ -83,12 +77,12 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli if (r < 0) return r; - if (v->inherit) { - r = sd_netlink_message_append_flag(m, IFLA_VXLAN_TTL_INHERIT); - if (r < 0) - return r; - } else { - r = sd_netlink_message_append_u8(m, IFLA_VXLAN_TTL, v->ttl); + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_TTL, v->ttl); + if (r < 0) + return r; + + if (v->fdb_ageing != 0) { + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_AGEING, v->fdb_ageing / USEC_PER_SEC); if (r < 0) return r; } @@ -99,6 +93,34 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli return r; } + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LABEL, htobe32(v->flow_label)); + if (r < 0) + return r; + + if (v->df != _NETDEV_VXLAN_DF_INVALID) { + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_DF, v->df); + if (r < 0) + return r; + } + + if (netdev->ifindex > 0) + return 0; + + /* The properties below cannot be updated, and the kernel refuses the whole request if one of the + * following attributes is set for an existing interface. */ + + if (v->vni <= VXLAN_VID_MAX) { + r = sd_netlink_message_append_u32(m, IFLA_VXLAN_ID, v->vni); + if (r < 0) + return r; + } + + if (v->inherit) { + r = sd_netlink_message_append_flag(m, IFLA_VXLAN_TTL_INHERIT); + if (r < 0) + return r; + } + r = sd_netlink_message_append_u8(m, IFLA_VXLAN_LEARNING, v->learning); if (r < 0) return r; @@ -119,12 +141,6 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli if (r < 0) return r; - if (v->fdb_ageing != 0) { - r = sd_netlink_message_append_u32(m, IFLA_VXLAN_AGEING, v->fdb_ageing / USEC_PER_SEC); - if (r < 0) - return r; - } - if (v->max_fdb != 0) { r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LIMIT, v->max_fdb); if (r < 0) @@ -166,10 +182,6 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli return r; } - r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LABEL, htobe32(v->flow_label)); - if (r < 0) - return r; - if (v->group_policy) { r = sd_netlink_message_append_flag(m, IFLA_VXLAN_GBP); if (r < 0) @@ -182,15 +194,16 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli return r; } - if (v->df != _NETDEV_VXLAN_DF_INVALID) { - r = sd_netlink_message_append_u8(m, IFLA_VXLAN_DF, v->df); - if (r < 0) - return r; - } - return 0; } +static bool vxlan_can_set_mtu(NetDev *netdev, uint32_t mtu) { + assert(netdev); + + /* MTU cannot be updated. Even unchanged, IFLA_MTU attribute cannot be set in the message. */ + return netdev->ifindex <= 0; +} + int config_parse_vxlan_address( const char *unit, const char *filename, @@ -402,6 +415,14 @@ static int netdev_vxlan_verify(NetDev *netdev, const char *filename) { return 0; } +static bool vxlan_needs_reconfigure(NetDev *netdev, NetDevLocalAddressType type) { + assert(type >= 0 && type < _NETDEV_LOCAL_ADDRESS_TYPE_MAX); + + VxLan *v = VXLAN(netdev); + + return v->local_type == type; +} + static int netdev_vxlan_is_ready_to_create(NetDev *netdev, Link *link) { VxLan *v = VXLAN(netdev); @@ -431,6 +452,8 @@ const NetDevVTable vxlan_vtable = { .create_type = NETDEV_CREATE_STACKED, .is_ready_to_create = netdev_vxlan_is_ready_to_create, .config_verify = netdev_vxlan_verify, + .can_set_mtu = vxlan_can_set_mtu, + .needs_reconfigure = vxlan_needs_reconfigure, .iftype = ARPHRD_ETHER, .generate_mac = true, }; diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c index 9715cf4034..9e8dfb259a 100644 --- a/src/network/netdev/wireguard.c +++ b/src/network/netdev/wireguard.c @@ -234,6 +234,9 @@ static int wireguard_set_interface(NetDev *netdev) { Wireguard *w = WIREGUARD(netdev); int r; + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + for (WireguardPeer *peer_start = w->peers; peer_start || !sent_once; ) { uint16_t i = 0; @@ -399,6 +402,9 @@ static int peer_resolve_endpoint(WireguardPeer *peer) { netdev = NETDEV(peer->wireguard); + if (!netdev_is_managed(netdev)) + return 0; /* Already detached, due to e.g. reloading .netdev files. */ + if (!peer->endpoint_host || !peer->endpoint_port) /* Not necessary to resolve the endpoint. */ return 0; diff --git a/src/network/netdev/wlan.c b/src/network/netdev/wlan.c index 904e40fe81..5b9db8b219 100644 --- a/src/network/netdev/wlan.c +++ b/src/network/netdev/wlan.c @@ -27,6 +27,9 @@ static void wlan_init(NetDev *netdev) { static int wlan_get_wiphy(NetDev *netdev, Wiphy **ret) { WLan *w = WLAN(netdev); + if (!netdev_is_managed(netdev)) + return -ENOENT; /* Already detached, due to e.g. reloading .netdev files. */ + if (w->wiphy_name) return wiphy_get_by_name(netdev->manager, w->wiphy_name, ret); diff --git a/src/network/networkctl-description.c b/src/network/networkctl-description.c index 14ce55bfae..410751f9e7 100644 --- a/src/network/networkctl-description.c +++ b/src/network/networkctl-description.c @@ -122,7 +122,7 @@ int dump_description(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - if (arg_json_format_flags == SD_JSON_FORMAT_OFF) + if (!sd_json_format_enabled(arg_json_format_flags)) return 0; r = acquire_bus(&bus); diff --git a/src/network/networkctl-lldp.c b/src/network/networkctl-lldp.c index 7836cb5ccf..8a10562b0d 100644 --- a/src/network/networkctl-lldp.c +++ b/src/network/networkctl-lldp.c @@ -231,7 +231,7 @@ int link_lldp_status(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (arg_json_format_flags != SD_JSON_FORMAT_OFF) + if (sd_json_format_enabled(arg_json_format_flags)) return dump_lldp_neighbors_json(reply, strv_skip(argv, 1)); pager_open(arg_pager_flags); diff --git a/src/network/networkd-can.c b/src/network/networkd-can.c index a5b003ad22..9457104a0e 100644 --- a/src/network/networkd-can.c +++ b/src/network/networkd-can.c @@ -20,10 +20,6 @@ int can_set_netlink_message(Link *link, sd_netlink_message *m) { assert(link->network); assert(m); - r = sd_netlink_message_set_flags(m, NLM_F_REQUEST | NLM_F_ACK); - if (r < 0) - return r; - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); if (r < 0) return r; diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 56555df62c..1f80513007 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -961,7 +961,6 @@ static int dhcp4_pd_6rd_tunnel_create_handler(sd_netlink *rtnl, sd_netlink_messa } int dhcp4_pd_prefix_acquired(Link *uplink) { - _cleanup_free_ char *tunnel_name = NULL; uint8_t ipv4masklen, sixrd_prefixlen, pd_prefixlen; struct in6_addr sixrd_prefix, pd_prefix; struct in_addr ipv4address; @@ -1010,28 +1009,10 @@ int dhcp4_pd_prefix_acquired(Link *uplink) { if (r < 0) return r; - /* Generate 6rd SIT tunnel device name. */ - r = dhcp4_pd_create_6rd_tunnel_name(uplink, &tunnel_name); + /* Create or update 6rd SIT tunnel device. */ + r = dhcp4_pd_create_6rd_tunnel(uplink, dhcp4_pd_6rd_tunnel_create_handler); if (r < 0) - return r; - - /* Remove old tunnel device if exists. */ - if (!streq_ptr(uplink->dhcp4_6rd_tunnel_name, tunnel_name)) { - Link *old_tunnel; - - if (uplink->dhcp4_6rd_tunnel_name && - link_get_by_name(uplink->manager, uplink->dhcp4_6rd_tunnel_name, &old_tunnel) >= 0) - (void) link_remove(old_tunnel); - - free_and_replace(uplink->dhcp4_6rd_tunnel_name, tunnel_name); - } - - /* Create 6rd SIT tunnel device if it does not exist yet. */ - if (link_get_by_name(uplink->manager, uplink->dhcp4_6rd_tunnel_name, NULL) < 0) { - r = dhcp4_pd_create_6rd_tunnel(uplink, dhcp4_pd_6rd_tunnel_create_handler); - if (r < 0) - return r; - } + return log_link_warning_errno(uplink, r, "Failed to create or update 6rd SIT tunnel: %m"); /* Then, assign subnet prefixes to downstream interfaces. */ HASHMAP_FOREACH(link, uplink->manager->links_by_index) { diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 2dd29bca94..bda3a561d9 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -326,6 +326,10 @@ int dhcp4_check_ready(Link *link) { if (r < 0) return r; + r = link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_DHCP4); + if (r < 0) + return r; + r = sd_ipv4ll_stop(link->ipv4ll); if (r < 0) return log_link_warning_errno(link, r, "Failed to drop IPv4 link-local address: %m"); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index b49f51f684..1eb5138d5e 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -122,6 +122,10 @@ int dhcp6_check_ready(Link *link) { if (r < 0) return r; + r = link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_DHCP6); + if (r < 0) + return r; + link_check_ready(link); return 0; } diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index 299aaedd07..ea960593bb 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -109,6 +109,10 @@ static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { log_link_debug(link, "IPv4 link-local claim "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + r = link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_IPV4LL); + if (r < 0) + return r; + return link_request_address(link, address, NULL, ipv4ll_address_handler, NULL); } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 59240bbc36..be8826b49c 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -222,7 +222,7 @@ void link_dns_settings_clear(Link *link) { link->dnssec_negative_trust_anchors = set_free_free(link->dnssec_negative_trust_anchors); } -static void link_free_engines(Link *link) { +void link_free_engines(Link *link) { if (!link) return; @@ -379,7 +379,7 @@ int link_stop_engines(Link *link, bool may_keep_dhcp) { bool keep_dhcp = may_keep_dhcp && link->network && !link->network->dhcp_send_decline && /* IPv4 ACD for the DHCPv4 address is running. */ - (link->manager->restarting || + (link->manager->state == MANAGER_RESTARTING || FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP)); if (!keep_dhcp) { @@ -640,15 +640,23 @@ static int link_request_static_configs(Link *link) { return 0; } -static int link_request_stacked_netdevs(Link *link) { +int link_request_stacked_netdevs(Link *link, NetDevLocalAddressType type) { NetDev *netdev; int r; assert(link); + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + assert(link->network); + link->stacked_netdevs_created = false; HASHMAP_FOREACH(netdev, link->network->stacked_netdevs) { + if (!netdev_needs_reconfigure(netdev, type)) + continue; + r = link_request_stacked_netdev(link, netdev); if (r < 0) return r; @@ -776,6 +784,10 @@ int link_ipv6ll_gained(Link *link) { if (r < 0) return r; + r = link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_IPV6LL); + if (r < 0) + return r; + link_check_ready(link); return 0; } @@ -1188,7 +1200,7 @@ static int link_configure(Link *link) { if (r < 0) return r; - r = link_request_stacked_netdevs(link); + r = link_request_stacked_netdevs(link, _NETDEV_LOCAL_ADDRESS_TYPE_INVALID); if (r < 0) return r; @@ -1313,9 +1325,13 @@ int link_reconfigure_impl(Link *link, bool force) { int r; assert(link); + assert(link->manager); link_assign_netdev(link); + if (link->manager->state != MANAGER_RUNNING) + return 0; + if (IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_LINGER)) return 0; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 89278f3a43..86aba9556b 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -19,6 +19,7 @@ #include "ether-addr-util.h" #include "log-link.h" +#include "netdev.h" #include "netif-util.h" #include "network-util.h" #include "networkd-bridge-vlan.h" @@ -222,8 +223,8 @@ bool link_is_ready_to_configure(Link *link, bool allow_unmanaged); void link_ntp_settings_clear(Link *link); void link_dns_settings_clear(Link *link); -Link *link_unref(Link *link); -Link *link_ref(Link *link); +Link* link_unref(Link *link); +Link* link_ref(Link *link); DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_unref); DEFINE_TRIVIAL_DESTRUCTOR(link_netlink_destroy_callback, Link, link_unref); @@ -252,10 +253,13 @@ int link_ipv6ll_gained(Link *link); bool link_has_ipv6_connectivity(Link *link); int link_stop_engines(Link *link, bool may_keep_dhcp); +void link_free_engines(Link *link); const char* link_state_to_string(LinkState s) _const_; LinkState link_state_from_string(const char *s) _pure_; +int link_request_stacked_netdevs(Link *link, NetDevLocalAddressType type); + int link_reconfigure_impl(Link *link, bool force); int link_reconfigure(Link *link, bool force); int link_reconfigure_on_bus_method_reload(Link *link, sd_bus_message *message); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 2813fa1f28..6639d59b32 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -422,30 +422,73 @@ static int manager_connect_rtnl(Manager *m, int fd) { static int manager_post_handler(sd_event_source *s, void *userdata) { Manager *manager = ASSERT_PTR(userdata); + /* To release dynamic leases, we need to process queued remove requests before stopping networkd. + * This is especially important when KeepConfiguration=no. See issue #34837. */ (void) manager_process_remove_requests(manager); - (void) manager_process_requests(manager); - (void) manager_clean_all(manager); + + switch (manager->state) { + case MANAGER_RUNNING: + (void) manager_process_requests(manager); + (void) manager_clean_all(manager); + return 0; + + case MANAGER_TERMINATING: + case MANAGER_RESTARTING: + if (!ordered_set_isempty(manager->remove_request_queue)) + return 0; /* There are some unissued remove requests. */ + + if (netlink_get_reply_callback_count(manager->rtnl) > 0 || + netlink_get_reply_callback_count(manager->genl) > 0 || + fw_ctx_get_reply_callback_count(manager->fw_ctx) > 0) + return 0; /* There are some message calls waiting for their replies. */ + + manager->state = MANAGER_STOPPED; + return sd_event_exit(sd_event_source_get_event(s), 0); + + default: + assert_not_reached(); + } + return 0; } -static int signal_terminate_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - Manager *m = ASSERT_PTR(userdata); +static int manager_stop(Manager *manager, ManagerState state) { + assert(manager); + assert(IN_SET(state, MANAGER_TERMINATING, MANAGER_RESTARTING)); - m->restarting = false; + if (manager->state != MANAGER_RUNNING) { + log_debug("Already terminating or restarting systemd-networkd, refusing further operation request."); + return 0; + } - log_debug("Terminate operation initiated."); + switch (state) { + case MANAGER_TERMINATING: + log_debug("Terminate operation initiated."); + break; + case MANAGER_RESTARTING: + log_debug("Restart operation initiated."); + break; + default: + assert_not_reached(); + } - return sd_event_exit(sd_event_source_get_event(s), 0); -} + manager->state = state; -static int signal_restart_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - Manager *m = ASSERT_PTR(userdata); + Link *link; + HASHMAP_FOREACH(link, manager->links_by_index) { + (void) link_stop_engines(link, /* may_keep_dhcp = */ true); + link_free_engines(link); + } - m->restarting = true; + return 0; +} - log_debug("Restart operation initiated."); +static int signal_terminate_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + return manager_stop(userdata, MANAGER_TERMINATING); +} - return sd_event_exit(sd_event_source_get_event(s), 0); +static int signal_restart_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + return manager_stop(userdata, MANAGER_RESTARTING); } static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { @@ -614,8 +657,6 @@ int manager_new(Manager **ret, bool test_mode) { } Manager* manager_free(Manager *m) { - Link *link; - if (!m) return NULL; @@ -623,9 +664,6 @@ Manager* manager_free(Manager *m) { free(m->state_file); - HASHMAP_FOREACH(link, m->links_by_index) - (void) link_stop_engines(link, true); - m->request_queue = ordered_set_free(m->request_queue); m->remove_request_queue = ordered_set_free(m->remove_request_queue); @@ -739,7 +777,7 @@ int manager_start(Manager *m) { int manager_load_config(Manager *m) { int r; - r = netdev_load(m, false); + r = netdev_load(m); if (r < 0) return r; @@ -1150,7 +1188,7 @@ int manager_reload(Manager *m, sd_bus_message *message) { (void) notify_reloading(); - r = netdev_load(m, /* reload= */ true); + r = netdev_reload(m); if (r < 0) goto finish; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 05a86b6b58..5f4508ea6f 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -19,6 +19,15 @@ #include "set.h" #include "time-util.h" +typedef enum ManagerState { + MANAGER_RUNNING, + MANAGER_TERMINATING, + MANAGER_RESTARTING, + MANAGER_STOPPED, + _MANAGER_STATE_MAX, + _MANAGER_STATE_INVALID = -EINVAL, +} ManagerState; + struct Manager { sd_netlink *rtnl; /* lazy initialized */ @@ -35,10 +44,10 @@ struct Manager { KeepConfiguration keep_configuration; IPv6PrivacyExtensions ipv6_privacy_extensions; + ManagerState state; bool test_mode; bool enumerating; bool dirty; - bool restarting; bool manage_foreign_routes; bool manage_foreign_rules; bool manage_foreign_nexthops; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 46f3954725..0773e9e8ca 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -2141,6 +2141,8 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t updated = true; } + RET_GATHER(ret, link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_SLAAC)); + if (updated) link_dirty(link); diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3d399fe876..9cd683e9bc 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -598,24 +598,47 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi return 0; } -int network_load(Manager *manager, OrderedHashmap **networks) { +int network_load(Manager *manager, OrderedHashmap **ret) { _cleanup_strv_free_ char **files = NULL; + OrderedHashmap *networks = NULL; int r; assert(manager); - - ordered_hashmap_clear_with_destructor(*networks, network_unref); + assert(ret); r = conf_files_list_strv(&files, ".network", NULL, 0, NETWORK_DIRS); if (r < 0) return log_error_errno(r, "Failed to enumerate network files: %m"); STRV_FOREACH(f, files) - (void) network_load_one(manager, networks, *f); + (void) network_load_one(manager, &networks, *f); + *ret = TAKE_PTR(networks); return 0; } +static bool network_netdev_equal(Network *a, Network *b) { + assert(a); + assert(b); + + if (a->batadv != b->batadv || + a->bridge != b->bridge || + a->bond != b->bond || + a->vrf != b->vrf || + a->xfrm != b->xfrm) + return false; + + if (hashmap_size(a->stacked_netdevs) != hashmap_size(b->stacked_netdevs)) + return false; + + NetDev *n; + HASHMAP_FOREACH(n, a->stacked_netdevs) + if (hashmap_get(b->stacked_netdevs, n->ifname) != n) + return false; + + return true; +} + int network_reload(Manager *manager) { OrderedHashmap *new_networks = NULL; Network *n, *old; @@ -630,15 +653,21 @@ int network_reload(Manager *manager) { ORDERED_HASHMAP_FOREACH(n, new_networks) { r = network_get_by_name(manager, n->name, &old); if (r < 0) { - log_debug("Found new .network file: %s", n->filename); + log_debug("%s: Found new .network file.", n->filename); continue; } if (!stats_by_path_equal(n->stats_by_path, old->stats_by_path)) { - log_debug("Found updated .network file: %s", n->filename); + log_debug("%s: Found updated .network file.", n->filename); + continue; + } + + if (!network_netdev_equal(n, old)) { + log_debug("%s: Detected update of referenced .netdev file(s).", n->filename); continue; } + /* Nothing updated, use the existing Network object, and drop the new one. */ r = ordered_hashmap_replace(new_networks, old->name, old); if (r < 0) goto failure; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index b4ab117928..30ea9ac352 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -419,7 +419,7 @@ Network *network_ref(Network *network); Network *network_unref(Network *network); DEFINE_TRIVIAL_CLEANUP_FUNC(Network*, network_unref); -int network_load(Manager *manager, OrderedHashmap **networks); +int network_load(Manager *manager, OrderedHashmap **ret); int network_reload(Manager *manager); int network_load_one(Manager *manager, OrderedHashmap **networks, const char *filename); int network_verify(Network *network); diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index dcb9bd0549..e898ea6e85 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -193,6 +193,7 @@ int netdev_queue_request( int r; assert(netdev); + assert(netdev->manager); r = request_new(netdev->manager, NULL, REQUEST_TYPE_NETDEV_INDEPENDENT, netdev, (mfree_func_t) netdev_unref, diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index b1a2623dcd..8519e6e7a0 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -191,10 +191,6 @@ static int link_configure_fill_message( return r; break; case REQUEST_TYPE_SET_LINK_BOND: - r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK); - if (r < 0) - return r; - r = sd_netlink_message_open_container(req, IFLA_LINKINFO); if (r < 0) return r; diff --git a/src/notify/notify.c b/src/notify/notify.c index 32bd6e6a7a..8937457ec9 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -28,7 +28,7 @@ static bool arg_ready = false; static bool arg_reloading = false; static bool arg_stopping = false; -static pid_t arg_pid = 0; +static PidRef arg_pid = PIDREF_NULL; static const char *arg_status = NULL; static bool arg_booted = false; static uid_t arg_uid = UID_INVALID; @@ -39,6 +39,7 @@ static char **arg_exec = NULL; static FDSet *arg_fds = NULL; static char *arg_fdname = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_pid, pidref_done); STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep); @@ -99,16 +100,24 @@ static pid_t manager_pid(void) { return pid; } -static pid_t pid_parent_if_possible(void) { - pid_t parent_pid = getppid(); +static int pidref_parent_if_applicable(PidRef *ret) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert(ret); + + r = pidref_set_parent(&pidref); + if (r < 0) + return log_debug_errno(r, "Failed to create reference to our parent process: %m"); /* 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 (parent_pid <= 1 || - parent_pid == manager_pid()) - return getpid_cached(); + if (pidref.pid <= 1 || + pidref.pid == manager_pid()) + return pidref_set_self(ret); - return parent_pid; + *ret = TAKE_PIDREF(pidref); + return 0; } static int parse_argv(int argc, char *argv[]) { @@ -175,17 +184,18 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PID: + pidref_done(&arg_pid); + if (isempty(optarg) || streq(optarg, "auto")) - arg_pid = pid_parent_if_possible(); + r = pidref_parent_if_applicable(&arg_pid); else if (streq(optarg, "parent")) - arg_pid = getppid(); + r = pidref_set_parent(&arg_pid); else if (streq(optarg, "self")) - arg_pid = getpid_cached(); - else { - r = parse_pid(optarg, &arg_pid); - if (r < 0) - return log_error_errno(r, "Failed to parse PID %s.", optarg); - } + r = pidref_set_self(&arg_pid); + else + r = pidref_set_pidstr(&arg_pid, optarg); + if (r < 0) + return log_error_errno(r, "Failed to refer to --pid='%s': %m", optarg); break; @@ -276,7 +286,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_fdname && fdset_isempty(arg_fds)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing."); - bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || arg_pid > 0 || !fdset_isempty(arg_fds); + bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds); size_t n_arg_env; if (do_exec) { @@ -326,9 +336,10 @@ static int parse_argv(int argc, char *argv[]) { } static int run(int argc, char* argv[]) { - _cleanup_free_ char *status = NULL, *cpid = NULL, *msg = NULL, *monotonic_usec = NULL, *fdn = NULL; + _cleanup_free_ char *status = NULL, *main_pid = NULL, *main_pidfd_id = NULL, *msg = NULL, + *monotonic_usec = NULL, *fdn = NULL; _cleanup_strv_free_ char **final_env = NULL; - const char *our_env[9]; + const char *our_env[10]; size_t i = 0; int r; @@ -371,11 +382,22 @@ static int run(int argc, char* argv[]) { our_env[i++] = status; } - if (arg_pid > 0) { - if (asprintf(&cpid, "MAINPID="PID_FMT, arg_pid) < 0) + if (pidref_is_set(&arg_pid)) { + if (asprintf(&main_pid, "MAINPID="PID_FMT, arg_pid.pid) < 0) return log_oom(); - our_env[i++] = cpid; + our_env[i++] = main_pid; + + r = pidref_acquire_pidfd_id(&arg_pid); + if (r < 0) + log_debug_errno(r, "Unable to acquire pidfd id of new main pid " PID_FMT ", ignoring: %m", + arg_pid.pid); + else { + if (asprintf(&main_pidfd_id, "MAINPIDFDID=%" PRIu64, arg_pid.fd_id) < 0) + return log_oom(); + + our_env[i++] = main_pidfd_id; + } } if (!fdset_isempty(arg_fds)) { @@ -415,11 +437,11 @@ static int run(int argc, char* argv[]) { /* If --pid= is explicitly specified, use it as source pid. Otherwise, pretend the message originates * from our parent, i.e. --pid=auto */ - if (arg_pid <= 0) - arg_pid = pid_parent_if_possible(); + if (!pidref_is_set(&arg_pid)) + (void) pidref_parent_if_applicable(&arg_pid); if (fdset_isempty(arg_fds)) - r = sd_pid_notify(arg_pid, /* unset_environment= */ false, msg); + r = sd_pid_notify(arg_pid.pid, /* unset_environment= */ false, msg); else { _cleanup_free_ int *a = NULL; int k; @@ -428,7 +450,7 @@ static int run(int argc, char* argv[]) { if (k < 0) return log_error_errno(k, "Failed to convert file descriptor set to array: %m"); - r = sd_pid_notify_with_fds(arg_pid, /* unset_environment= */ false, msg, a, k); + r = sd_pid_notify_with_fds(arg_pid.pid, /* unset_environment= */ false, msg, a, k); } if (r < 0) @@ -440,7 +462,7 @@ static int run(int argc, char* argv[]) { arg_fds = fdset_free(arg_fds); /* Close before we execute anything */ if (!arg_no_block) { - r = sd_pid_notify_barrier(arg_pid, /* unset_environment= */ false, 5 * USEC_PER_SEC); + r = sd_pid_notify_barrier(arg_pid.pid, /* unset_environment= */ false, 5 * USEC_PER_SEC); if (r < 0) return log_error_errno(r, "Failed to invoke barrier: %m"); if (r == 0) diff --git a/src/partition/repart.c b/src/partition/repart.c index 65b50e346f..5427e0a69f 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3511,7 +3511,7 @@ static int context_dump_partitions(Context *context) { const size_t roothash_col = 14, dropin_files_col = 15, split_path_col = 16; bool has_roothash = false, has_dropin_files = false, has_split_path = false; - if ((arg_json_format_flags & SD_JSON_FORMAT_OFF) && context->n_partitions == 0) { + if (context->n_partitions == 0 && !sd_json_format_enabled(arg_json_format_flags)) { log_info("Empty partition table."); return 0; } @@ -3541,7 +3541,7 @@ static int context_dump_partitions(Context *context) { table_set_json_field_name(t, 15, "drop-in_files"); if (!DEBUG_LOGGING) { - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) + if (!sd_json_format_enabled(arg_json_format_flags)) (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, (size_t) 8, (size_t) 9, (size_t) 12, roothash_col, dropin_files_col, split_path_col); @@ -3624,7 +3624,7 @@ static int context_dump_partitions(Context *context) { has_split_path = has_split_path || !isempty(p->split_path); } - if ((arg_json_format_flags & SD_JSON_FORMAT_OFF) && (sum_padding > 0 || sum_size > 0)) { + if (!sd_json_format_enabled(arg_json_format_flags) && (sum_padding > 0 || sum_size > 0)) { const char *a, *b; a = strjoina(special_glyph(SPECIAL_GLYPH_SIGMA), " = ", FORMAT_BYTES(sum_size)); @@ -3891,17 +3891,17 @@ static int context_dump(Context *context, bool late) { assert(context); - if (arg_pretty == 0 && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (arg_pretty == 0 && !sd_json_format_enabled(arg_json_format_flags)) return 0; /* If we're outputting JSON, only dump after doing all operations so we can include the roothashes * in the output. */ - if (!late && !FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (!late && sd_json_format_enabled(arg_json_format_flags)) return 0; /* If we're not outputting JSON, only dump again after doing all operations if there are any * roothashes that we need to communicate to the user. */ - if (late && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) && !context_has_roothash(context)) + if (late && !sd_json_format_enabled(arg_json_format_flags) && !context_has_roothash(context)) return 0; r = context_dump_partitions(context); @@ -3910,7 +3910,7 @@ static int context_dump(Context *context, bool late) { /* Only write the partition bar once, even if we're writing the partition table twice to communicate * roothashes. */ - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) && !late) { + if (!sd_json_format_enabled(arg_json_format_flags) && !late) { putc('\n', stdout); r = context_dump_partition_bar(context); @@ -5085,21 +5085,19 @@ static int partition_format_verity_sig(Context *context, Partition *p) { static int progress_bytes(uint64_t n_bytes, void *userdata) { Partition *p = ASSERT_PTR(userdata); - _cleanup_free_ char *s = NULL; p->copy_blocks_done += n_bytes; - if (asprintf(&s, "%s %s %s %s/%s", - strna(p->copy_blocks_path), - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), - strna(p->definition_path), - FORMAT_BYTES(p->copy_blocks_done), - FORMAT_BYTES(p->copy_blocks_size)) < 0) - return log_oom(); + (void) draw_progress_barf( + p->copy_blocks_done >= p->copy_blocks_size ? 100.0 : /* catch division be zero */ + 100.0 * (double) p->copy_blocks_done / (double) p->copy_blocks_size, + "%s %s %s %s/%s", + strna(p->copy_blocks_path), + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + strna(p->definition_path), + FORMAT_BYTES(p->copy_blocks_done), + FORMAT_BYTES(p->copy_blocks_size)); - draw_progress_bar(s, - p->copy_blocks_done >= p->copy_blocks_size ? 100.0 : /* catch division be zero */ - 100.0 * (double) p->copy_blocks_done / (double) p->copy_blocks_size); return 0; } diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 46f285d54a..c1915761ee 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2045,7 +2045,7 @@ static int add_algorithm_columns( if (r < 0) return table_log_add_error(r); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) && + if (!sd_json_format_enabled(arg_json_format_flags) && el->primary_algorithm != UINT16_MAX && *alg != el->primary_algorithm) (void) table_hide_column_from_display(table, c); @@ -2106,7 +2106,7 @@ static int show_log_table(EventLog *el, sd_json_variant **ret_variant) { (void) table_hide_column_from_display(table, table_get_columns(table) - 3); /* hide source */ - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) (void) table_hide_column_from_display(table, (size_t) 1); /* hide color block column */ (void) table_set_json_field_name(table, phase_column, "phase"); @@ -2242,7 +2242,7 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) (void) table_hide_column_from_display(table, (size_t) 1, (size_t) 2); /* hide color block and emoji column */ else if (!emoji_enabled()) (void) table_hide_column_from_display(table, (size_t) 2); @@ -2347,7 +2347,7 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { if (r < 0) return log_error_errno(r, "Failed to output table: %m"); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (!sd_json_format_enabled(arg_json_format_flags)) printf("\n" "%sLegend: H → PCR hash value matches event log%s\n" "%s R → All event log records for this PCR have a matching component%s\n" @@ -2431,7 +2431,7 @@ static int event_log_load_and_process(EventLog **ret) { static int verb_show_log(int argc, char *argv[], void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *log_table = NULL, *pcr_table = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; - bool want_json = !FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF); + bool want_json = sd_json_format_enabled(arg_json_format_flags); int r; r = event_log_load_and_process(&el); @@ -2607,7 +2607,7 @@ static int verb_list_components(int argc, char *argv[], void *userdata) { FOREACH_ARRAY(c, el->components, el->n_components) { - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { _cleanup_free_ char *marker = NULL; switch (loc) { @@ -2653,13 +2653,13 @@ static int verb_list_components(int argc, char *argv[], void *userdata) { } } - if (!table_isempty(table) || !FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ true); if (r < 0) return log_error_errno(r, "Failed to output table: %m"); } - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { if (table_isempty(table)) printf("No components defined.\n"); else @@ -4154,7 +4154,7 @@ static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) pager_open(arg_pager_flags); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 0204fdf16f..62354b4784 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -204,7 +204,7 @@ static void print_source(uint64_t flags, usec_t rtt) { if (!arg_legend) return; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return; if (flags == 0) @@ -273,7 +273,7 @@ static int resolve_host(sd_bus *bus, const char *name) { assert(name); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type=A or --type=AAAA to acquire address record information in JSON format."); log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); @@ -374,7 +374,7 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); if (ifindex <= 0) @@ -468,7 +468,7 @@ static int output_rr_packet(const void *d, size_t l, int ifindex) { if (r < 0) return log_error_errno(r, "Failed to parse RR: %m"); - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; r = dns_resource_record_to_json(rr, &j); if (r < 0) @@ -994,7 +994,7 @@ static int verb_service(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); if (argc == 2) @@ -1060,7 +1060,7 @@ static int verb_openpgp(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); STRV_FOREACH(p, strv_skip(argv, 1)) @@ -1117,7 +1117,7 @@ static int verb_tlsa(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); if (service_family_is_valid(argv[1])) { @@ -1152,7 +1152,7 @@ static int show_statistics(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); struct statistics { @@ -1316,7 +1316,7 @@ static int reset_statistics(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (sd_json_format_enabled(arg_json_format_flags)) return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); return 0; @@ -2978,7 +2978,7 @@ static int monitor_reply( return 0; } - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) { + if (!sd_json_format_enabled(arg_json_format_flags)) { monitor_query_dump(parameters); printf("\n"); } else @@ -3180,7 +3180,7 @@ static int verb_show_cache(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "DumpCache() response 'dump' field not an array"); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { sd_json_variant *i; JSON_VARIANT_ARRAY_FOREACH(i, d) { @@ -3360,7 +3360,7 @@ static int verb_show_server_state(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "DumpCache() response 'dump' field not an array"); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { sd_json_variant *i; JSON_VARIANT_ARRAY_FOREACH(i, d) { diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index f9991d86ab..c414ca800c 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -557,12 +557,19 @@ int dns_packet_append_name( bool canonical_candidate, size_t *start) { - size_t saved_size; + _cleanup_free_ char **added_entries = NULL; /* doesn't own the strings! this is just regular pointer array, not a NULL-terminated strv! */ + size_t n_added_entries = 0, saved_size; int r; assert(p); assert(name); + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (p->refuse_compression) allow_compression = false; @@ -598,6 +605,11 @@ int dns_packet_append_name( if (allow_compression) { _cleanup_free_ char *s = NULL; + if (!GREEDY_REALLOC(added_entries, n_added_entries + 1)) { + r = -ENOMEM; + goto fail; + } + s = strdup(z); if (!s) { r = -ENOMEM; @@ -608,7 +620,8 @@ int dns_packet_append_name( if (r < 0) goto fail; - TAKE_PTR(s); + /* Keep track of the entries we just added (note that the string is owned by the hashtable, not this array!) */ + added_entries[n_added_entries++] = TAKE_PTR(s); } } @@ -623,6 +636,12 @@ done: return 0; fail: + /* Remove all label compression names we added again */ + FOREACH_ARRAY(s, added_entries, n_added_entries) { + hashmap_remove(p->names, *s); + free(*s); + } + dns_packet_truncate(p, saved_size); return r; } @@ -1506,7 +1525,7 @@ int dns_packet_read_name( size_t after_rindex = 0, jump_barrier = p->rindex; _cleanup_free_ char *name = NULL; bool first = true; - size_t n = 0; + size_t n = 0, m = 0; int r; if (p->refuse_compression) @@ -1535,14 +1554,21 @@ int dns_packet_read_name( if (first) first = false; - else + else { name[n++] = '.'; + m++; + } r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); if (r < 0) return r; n += r; + m += c; + + if (m > DNS_HOSTNAME_MAX) + return -EBADMSG; + continue; } else if (allow_compression && FLAGS_SET(c, 0xc0)) { uint16_t ptr; diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 6a8e80128c..168923e6b5 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -241,16 +241,12 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) { assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } + if (q->state != DNS_TRANSACTION_SUCCESS) + return (void) reply_query_state(q); r = dns_query_process_cname_many(q); - if (r == -ELOOP) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } + if (r == -ELOOP) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); if (r < 0) goto finish; if (r == DNS_QUERY_CNAME) { @@ -265,10 +261,8 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) { if (r < 0) goto finish; - if (sd_json_variant_is_blank_object(array)) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); - goto finish; - } + if (sd_json_variant_is_blank_object(array)) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); assert(canonical); r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized); @@ -443,16 +437,12 @@ static void vl_method_resolve_address_complete(DnsQuery *query) { assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } + if (q->state != DNS_TRANSACTION_SUCCESS) + return (void) reply_query_state(q); r = dns_query_process_cname_many(q); - if (r == -ELOOP) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } + if (r == -ELOOP) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); if (r < 0) goto finish; if (r == DNS_QUERY_CNAME) { @@ -484,10 +474,8 @@ static void vl_method_resolve_address_complete(DnsQuery *query) { goto finish; } - if (sd_json_variant_is_blank_object(array)) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); - goto finish; - } + if (sd_json_variant_is_blank_object(array)) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); r = sd_varlink_replybo( q->varlink_request, @@ -711,14 +699,14 @@ static int append_srv( return 1; /* added */ } -static sd_varlink *get_vl_link_aux_query(DnsQuery *aux) { +static sd_varlink* take_vl_link_aux_query(DnsQuery *aux) { assert(aux); /* Find the main query */ while (aux->auxiliary_for) aux = aux->auxiliary_for; - return aux->varlink_request; + return TAKE_PTR(aux->varlink_request); } static void resolve_service_all_complete(DnsQuery *query) { @@ -768,20 +756,16 @@ static void resolve_service_all_complete(DnsQuery *query) { if (bad->state == DNS_TRANSACTION_SUCCESS) { assert(bad->auxiliary_result != 0); - if (bad->auxiliary_result == -ELOOP) { - r = sd_varlink_error(query->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } + if (bad->auxiliary_result == -ELOOP) + return (void) sd_varlink_error(query->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); assert(bad->auxiliary_result < 0); r = bad->auxiliary_result; goto finish; } - bad->varlink_request = get_vl_link_aux_query(bad); - r = reply_query_state(bad); - bad->varlink_request = NULL; - goto finish; + bad->varlink_request = take_vl_link_aux_query(bad); + return (void) reply_query_state(bad); } } @@ -804,10 +788,8 @@ static void resolve_service_all_complete(DnsQuery *query) { canonical = dns_resource_record_ref(rr); } - if (sd_json_variant_is_blank_object(srv)) { - r = sd_varlink_error(query->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); - goto finish; - } + if (sd_json_variant_is_blank_object(srv)) + return (void) sd_varlink_error(query->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); DNS_ANSWER_FOREACH(rr, q->answer) { r = dns_question_matches_rr(question, rr, NULL); @@ -829,10 +811,8 @@ static void resolve_service_all_complete(DnsQuery *query) { if (r < 0) goto finish; - if (isempty(type)) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.InconsistentServiceRecords", NULL); - goto finish; - } + if (isempty(type)) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.InconsistentServiceRecords", NULL); r = sd_varlink_replybo( query->varlink_request, @@ -925,18 +905,14 @@ static void vl_method_resolve_service_complete(DnsQuery *query) { assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } + if (q->state != DNS_TRANSACTION_SUCCESS) + return (void) reply_query_state(q); r = dns_query_process_cname_many(q); - if (r == -ELOOP) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } + if (r == -ELOOP) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); if (r < 0) - goto finish; + goto fail; if (r == DNS_QUERY_CNAME) { /* This was a cname, and the query was restarted. */ TAKE_PTR(q); @@ -948,7 +924,7 @@ static void vl_method_resolve_service_complete(DnsQuery *query) { DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { r = dns_question_matches_rr(question, rr, NULL); if (r < 0) - goto finish; + goto fail; if (r == 0) continue; @@ -966,34 +942,28 @@ static void vl_method_resolve_service_complete(DnsQuery *query) { q->block_all_complete--; if (r < 0) - goto finish; + goto fail; } found++; } - if (has_root_domain && found <= 0) { + if (has_root_domain && found <= 0) /* If there's exactly one SRV RR and it uses the root domain as hostname, then the service is * explicitly not offered on the domain. Report this as a recognizable error. See RFC 2782, * Section "Usage Rules". */ - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.ServiceNotProvided", NULL); - goto finish; - } + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.ServiceNotProvided", NULL); - if (found <= 0) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); - goto finish; - } + if (found <= 0) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); /* Maybe we are already finished? check now... */ resolve_service_all_complete(TAKE_PTR(q)); return; -finish: - if (r < 0) { - log_error_errno(r, "Failed to send address reply: %m"); - (void) sd_varlink_error_errno(q->varlink_request, r); - } +fail: + log_error_errno(r, "Failed to send address reply: %m"); + (void) sd_varlink_error_errno(q->varlink_request, r); } static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* parameters, sd_varlink_method_flags_t flags, void* userdata) { @@ -1090,16 +1060,12 @@ static void vl_method_resolve_record_complete(DnsQuery *query) { assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } + if (q->state != DNS_TRANSACTION_SUCCESS) + return (void) reply_query_state(q); r = dns_query_process_cname_many(q); - if (r == -ELOOP) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } + if (r == -ELOOP) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); if (r < 0) goto finish; if (r == DNS_QUERY_CNAME) { @@ -1141,10 +1107,8 @@ static void vl_method_resolve_record_complete(DnsQuery *query) { added++; } - if (added <= 0) { - r = sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); - goto finish; - } + if (added <= 0) + return (void) sd_varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); r = sd_varlink_replybo( q->varlink_request, diff --git a/src/resolve/test-dns-packet-append.c b/src/resolve/test-dns-packet-append.c index 9ef77b3b98..8942050927 100644 --- a/src/resolve/test-dns-packet-append.c +++ b/src/resolve/test-dns-packet-append.c @@ -1267,4 +1267,34 @@ TEST(packet_append_answer_single_svcb) { ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0); } +static void dump_packet_data(DnsPacket *packet) { + assert(packet); + fprintf(stderr, "packet bytes:"); + for (size_t i = 0; i < packet->size; i++) + fprintf(stderr, " %x", DNS_PACKET_DATA(packet)[i]); + fprintf(stderr, "\n"); +} + +TEST(packet_append_key_name_too_long) { + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + int r; + + ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX)); + + DNS_PACKET_ID(packet) = htobe16(42); + DNS_PACKET_HEADER(packet)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0, 0, 0, 0, 1, 0, 0, 0, 0)); + DNS_PACKET_HEADER(packet)->qdcount = htobe16(1); + + key = ASSERT_PTR(dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com")); + r = dns_packet_append_key(packet, key, 0, NULL); + + log_debug("r = %d, size = %zu", r, packet->size); + log_debug("key name = <%s>", dns_resource_key_name(key)); + dump_packet_data(packet); + + ASSERT_EQ(r, -EINVAL); + ASSERT_EQ(packet->size, 12U); +} + DEFINE_TEST_MAIN(LOG_DEBUG) diff --git a/src/resolve/test-dns-packet-extract.c b/src/resolve/test-dns-packet-extract.c index 5cd5a83d49..0bded09077 100644 --- a/src/resolve/test-dns-packet-extract.c +++ b/src/resolve/test-dns-packet-extract.c @@ -806,12 +806,7 @@ TEST(packet_query_single_long_domain) { 0x10, 'n', 'i', 't', 'r', 'o', 's', 'y', 'l', 's', 'u', 'l', 'f', 'u', 'r', 'i', 'c', 0x10, 'o', 'b', 'j', 'e', 'c', 't', 'l', 'e', 's', 's', 'n', 'e', 's', 's', 'e', 's', 0x10, 'p', 'a', 'r', 't', 'r', 'i', 'd', 'g', 'e', 'b', 'e', 'r', 'r', 'i', 'e', 's', - 0x10, 'r', 'e', 'a', 's', 'o', 'n', 'l', 'e', 's', 's', 'n', 'e', 's', 's', 'e', 's', - 0x10, 's', 'e', 'm', 'i', 'p', 'a', 't', 'h', 'o', 'l', 'o', 'g', 'i', 'c', 'a', 'l', - 0x10, 't', 'o', 'm', 'f', 'o', 'o', 'l', 'i', 's', 'h', 'n', 'e', 's', 's', 'e', 's', - 0x10, 'u', 'n', 'd', 'e', 'r', 'c', 'a', 'p', 'i', 't', 'a', 'l', 'i', 'z', 'e', 'd', - 0x10, 'v', 'e', 'c', 't', 'o', 'r', 'c', 'a', 'r', 'd', 'i', 'o', 'g', 'r', 'a', 'm', - 0x10, 'w', 'e', 'a', 't', 'h', 'e', 'r', 'p', 'r', 'o', 'o', 'f', 'n', 'e', 's', 's', + 0x0F, 'r', 'e', 'a', 's', 'o', 'n', 'l', 'e', 's', 's', 'n', 'e', 's', 's', 'e', 0x00, /* A */ 0x00, 0x01, /* IN */ 0x00, 0x01 @@ -823,12 +818,12 @@ TEST(packet_query_single_long_domain) { ASSERT_EQ(dns_question_size(packet->question), 1u); ASSERT_EQ(dns_answer_size(packet->answer), 0u); - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, - "absorptivenesses.calligraphically.deacidifications.ecophysiological." - "falsifiabilities.heterochromatism.icositetrahedron.journalistically." - "kinaesthetically.lactovegetarians.misinterpretable.nitrosylsulfuric." - "objectlessnesses.partridgeberries.reasonlessnesses.semipathological." - "tomfoolishnesses.undercapitalized.vectorcardiogram.weatherproofness"); + key = dns_resource_key_new( + DNS_CLASS_IN, DNS_TYPE_A, + "absorptivenesses.calligraphically.deacidifications.ecophysiological." + "falsifiabilities.heterochromatism.icositetrahedron.journalistically." + "kinaesthetically.lactovegetarians.misinterpretable.nitrosylsulfuric." + "objectlessnesses.partridgeberries.reasonlessnesse"); ASSERT_NOT_NULL(key); ASSERT_TRUE(dns_question_contains_key(packet->question, key)); @@ -4777,4 +4772,78 @@ TEST(format_dns_svc_param_key) { ASSERT_STREQ(str, "ohttp"); } +TEST(overlong_domain) { + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + + ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX)); + ASSERT_NOT_NULL(packet); + dns_packet_truncate(packet, 0); + + const uint8_t data[] = { + 0x00, 0x42, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 0x00, + 0x00, DNS_TYPE_A, + 0x00, DNS_CLASS_IN, + }; + + ASSERT_OK(dns_packet_append_blob(packet, data, sizeof(data), NULL)); + ASSERT_OK(dns_packet_validate_query(packet)); + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + ASSERT_ERROR(dns_packet_read_key(packet, &key, NULL, NULL), EBADMSG); + + const uint8_t data2[] = { + 0x00, 0x42, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 62, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', + 0x00, + 0x00, DNS_TYPE_A, + 0x00, DNS_CLASS_IN, + }; + + packet = dns_packet_unref(packet); + ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX)); + ASSERT_NOT_NULL(packet); + dns_packet_truncate(packet, 0); + ASSERT_OK(dns_packet_append_blob(packet, data2, sizeof(data2), NULL)); + ASSERT_OK(dns_packet_validate_query(packet)); + ASSERT_ERROR(dns_packet_read_key(packet, &key, NULL, NULL), EBADMSG); + + const uint8_t data3[] = { + 0x00, 0x42, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 63, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', + 61, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + 0x00, + 0x00, DNS_TYPE_A, + 0x00, DNS_CLASS_IN, + }; + + packet = dns_packet_unref(packet); + ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX)); + ASSERT_NOT_NULL(packet); + dns_packet_truncate(packet, 0); + ASSERT_OK(dns_packet_append_blob(packet, data3, sizeof(data3), NULL)); + ASSERT_OK(dns_packet_validate_query(packet)); + ASSERT_OK(dns_packet_read_key(packet, &key, NULL, NULL)); + + ASSERT_STREQ(dns_resource_key_name(key), + "012345678901234567890123456789012345678901234567890123456789012." + "012345678901234567890123456789012345678901234567890123456789012." + "012345678901234567890123456789012345678901234567890123456789012." + "0123456789012345678901234567890123456789012345678901234567890"); +} + DEFINE_TEST_MAIN(LOG_DEBUG) diff --git a/src/run/run.c b/src/run/run.c index b73e292514..e4bcc5d599 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1858,11 +1858,11 @@ static void set_window_title(PTYForward *f) { (void) pty_forward_set_title_prefix(f, dot); } -static int chown_to_capsule(const char *path, const char *capsule) { +static int fchown_to_capsule(int fd, const char *capsule) { _cleanup_free_ char *p = NULL; int r; - assert(path); + assert(fd >= 0); assert(capsule); p = path_join("/run/capsules/", capsule); @@ -1877,7 +1877,7 @@ static int chown_to_capsule(const char *path, const char *capsule) { if (uid_is_system(st.st_uid) || gid_is_system(st.st_gid)) /* paranoid safety check */ return -EPERM; - return chmod_and_chown(path, 0600, st.st_uid, st.st_gid); + return fchmod_and_chown(fd, 0600, st.st_uid, st.st_gid); } static int print_unit_invocation(const char *unit, sd_id128_t invocation_id) { @@ -1886,7 +1886,7 @@ static int print_unit_invocation(const char *unit, sd_id128_t invocation_id) { assert(unit); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { if (sd_id128_is_null(invocation_id)) log_info("Running as unit: %s", unit); else @@ -1912,7 +1912,7 @@ static int start_transient_service(sd_bus *bus) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; _cleanup_free_ char *service = NULL, *pty_path = NULL; - _cleanup_close_ int master = -EBADF, slave = -EBADF; + _cleanup_close_ int pty_fd = -EBADF, peer_fd = -EBADF; int r; assert(bus); @@ -1922,29 +1922,22 @@ static int start_transient_service(sd_bus *bus) { if (arg_stdio == ARG_STDIO_PTY) { if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)) { - master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (master < 0) - return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); + pty_fd = openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, &pty_path); + if (pty_fd < 0) + return log_error_errno(pty_fd, "Failed to acquire pseudo tty: %m"); - r = ptsname_malloc(master, &pty_path); - if (r < 0) - return log_error_errno(r, "Failed to determine tty name: %m"); + peer_fd = pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (peer_fd < 0) + return log_error_errno(peer_fd, "Failed to open pty peer: %m"); if (arg_transport == BUS_TRANSPORT_CAPSULE) { /* If we are in capsule mode, we must give the capsule UID/GID access to the PTY we just allocated first. */ - r = chown_to_capsule(pty_path, arg_host); + r = fchown_to_capsule(peer_fd, arg_host); if (r < 0) return log_error_errno(r, "Failed to chown tty to capsule UID/GID: %m"); } - if (unlockpt(master) < 0) - return log_error_errno(errno, "Failed to unlock tty: %m"); - - slave = open_terminal(pty_path, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (slave < 0) - return log_error_errno(slave, "Failed to open pty slave: %m"); - } else if (arg_transport == BUS_TRANSPORT_MACHINE) { _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = NULL; @@ -1965,18 +1958,25 @@ static int start_transient_service(sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to get machine PTY: %s", bus_error_message(&error, r)); - r = sd_bus_message_read(pty_reply, "hs", &master, &s); + r = sd_bus_message_read(pty_reply, "hs", &pty_fd, &s); if (r < 0) return bus_log_parse_error(r); - master = fcntl(master, F_DUPFD_CLOEXEC, 3); - if (master < 0) + pty_fd = fcntl(pty_fd, F_DUPFD_CLOEXEC, 3); + if (pty_fd < 0) return log_error_errno(errno, "Failed to duplicate master fd: %m"); pty_path = strdup(s); if (!pty_path) return log_oom(); + peer_fd = pty_open_peer_racefree(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (ERRNO_IS_NEG_NOT_SUPPORTED(peer_fd)) + log_debug_errno(r, "TIOCGPTPEER ioctl not available, falling back to race-ful PTY peer opening: %m"); + /* We do not open the peer_fd in this case, we let systemd on the remote side open it instead */ + else if (peer_fd < 0) + return log_debug_errno(peer_fd, "Failed to open PTY peer: %m"); + // FIXME: Introduce OpenMachinePTYEx() that accepts ownership/permission as param // and additionally returns the pty fd, for #33216 and #32999 } else @@ -2005,10 +2005,10 @@ static int start_transient_service(sd_bus *bus) { return r; } - r = make_transient_service_unit(bus, &m, service, pty_path, slave); + r = make_transient_service_unit(bus, &m, service, pty_path, peer_fd); if (r < 0) return r; - slave = safe_close(slave); + peer_fd = safe_close(peer_fd); r = bus_call_with_hint(bus, m, "service", &reply); if (r < 0) @@ -2066,13 +2066,13 @@ static int start_transient_service(sd_bus *bus) { if (!c.bus_path) return log_oom(); - if (master >= 0) { + if (pty_fd >= 0) { (void) sd_event_set_signal_exit(c.event, true); if (!arg_quiet) log_info("Press ^] three times within 1s to disconnect TTY."); - r = pty_forward_new(c.event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &c.forward); + r = pty_forward_new(c.event, pty_fd, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &c.forward); if (r < 0) return log_error_errno(r, "Failed to create PTY forwarder: %m"); diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 7b9cdadc54..0c3156cd27 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -22,6 +22,7 @@ #include "ansi-color.h" #include "ask-password-api.h" #include "creds-util.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" @@ -113,6 +114,28 @@ static int touch_ask_password_directory(AskPasswordFlags flags) { return 1; /* did something */ } +static usec_t keyring_cache_timeout(void) { + static usec_t saved_timeout = USEC_INFINITY; + static bool saved_timeout_set = false; + int r; + + if (saved_timeout_set) + return saved_timeout; + + const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC"); + if (streq_ptr(e, "default")) + saved_timeout = KEYRING_TIMEOUT_USEC; + else if (e) { + r = parse_sec(e, &saved_timeout); + if (r < 0) + log_debug_errno(r, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC, ignoring: %s", e); + } + + saved_timeout_set = true; + + return saved_timeout; +} + static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) { _cleanup_strv_free_erase_ char **l = NULL; _cleanup_(erase_and_freep) char *p = NULL; @@ -122,7 +145,7 @@ static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **pa assert(keyname); - if (!FLAGS_SET(flags, ASK_PASSWORD_PUSH_CACHE)) + if (!FLAGS_SET(flags, ASK_PASSWORD_PUSH_CACHE) || keyring_cache_timeout() == 0) return 0; if (strv_isempty(passwords)) return 0; @@ -151,9 +174,10 @@ static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **pa if (serial == -1) return -errno; - if (keyctl(KEYCTL_SET_TIMEOUT, - (unsigned long) serial, - (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0) + if (keyring_cache_timeout() != USEC_INFINITY && + keyctl(KEYCTL_SET_TIMEOUT, + (unsigned long) serial, + (unsigned long) DIV_ROUND_UP(keyring_cache_timeout(), USEC_PER_SEC), 0, 0) < 0) log_debug_errno(errno, "Failed to adjust kernel keyring key timeout: %m"); /* Tell everyone to check the keyring */ diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 87aa11ccdf..4c0195a41e 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1936,7 +1936,7 @@ int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_form assert(config); - if (!FLAGS_SET(json_format, SD_JSON_FORMAT_OFF)) { + if (sd_json_format_enabled(json_format)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; for (size_t i = 0; i < config->n_entries; i++) { diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 59e4901878..90b6f233e2 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1047,6 +1047,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con "ProtectHome", "PrivateTmpEx", "PrivateUsersEx", + "ProtectControlGroupsEx", "SELinuxContext", "RootImage", "RootVerity", @@ -2126,7 +2127,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con } if (STR_IN_SET(field, "StateDirectory", "RuntimeDirectory", "CacheDirectory", "LogsDirectory")) { - _cleanup_strv_free_ char **symlinks = NULL, **sources = NULL; + _cleanup_strv_free_ char **symlinks = NULL, **symlinks_ro = NULL, **sources = NULL, **sources_ro = NULL; const char *p = eq; /* Adding new directories is supported from both *DirectorySymlink methods and the @@ -2134,7 +2135,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con * tuple use the new method, else use the old one. */ for (;;) { - _cleanup_free_ char *tuple = NULL, *source = NULL, *destination = NULL; + _cleanup_free_ char *tuple = NULL, *source = NULL, *dest = NULL, *flags = NULL; r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE); if (r < 0) @@ -2143,20 +2144,31 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con break; const char *t = tuple; - r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination); + r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &dest, &flags); if (r <= 0) return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m"); path_simplify(source); - if (isempty(destination)) { + if (isempty(dest) && isempty(flags)) { r = strv_consume(&sources, TAKE_PTR(source)); if (r < 0) return bus_log_create_error(r); + } else if (isempty(flags)) { + path_simplify(dest); + r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(dest)); + if (r < 0) + return log_oom(); } else { - path_simplify(destination); - - r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(destination)); + ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags); + if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0) + return log_error_errno(r, "Failed to parse flags: %s", flags); + + if (!isempty(dest)) { + path_simplify(dest); + r = strv_consume_pair(&symlinks_ro, TAKE_PTR(source), TAKE_PTR(dest)); + } else + r = strv_consume(&sources_ro, TAKE_PTR(source)); if (r < 0) return log_oom(); } @@ -2191,7 +2203,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con /* For State and Runtime directories we support an optional destination parameter, which * will be used to create a symlink to the source. But it is new so we cannot change the * old DBUS signatures, so append a new message type. */ - if (!strv_isempty(symlinks)) { + if (!strv_isempty(symlinks) || !strv_isempty(symlinks_ro) || !strv_isempty(sources_ro)) { const char *symlink_field; r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); @@ -2227,6 +2239,18 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_log_create_error(r); } + STRV_FOREACH_PAIR(source, destination, symlinks_ro) { + r = sd_bus_message_append(m, "(sst)", *source, *destination, (uint64_t) EXEC_DIRECTORY_READ_ONLY); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(source, sources_ro) { + r = sd_bus_message_append(m, "(sst)", *source, "", (uint64_t) EXEC_DIRECTORY_READ_ONLY); + if (r < 0) + return bus_log_create_error(r); + } + r = sd_bus_message_close_container(m); if (r < 0) return bus_log_create_error(r); @@ -3077,3 +3101,13 @@ int unit_freezer_freeze(UnitFreezer *f) { int unit_freezer_thaw(UnitFreezer *f) { return unit_freezer_action(f, false); } + +ExecDirectoryFlags exec_directory_flags_from_string(const char *s) { + if (isempty(s)) + return 0; + + if (streq(s, "ro")) + return EXEC_DIRECTORY_READ_ONLY; + + return _EXEC_DIRECTORY_FLAGS_INVALID; +} diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h index 6f0d55971c..8090b36e3d 100644 --- a/src/shared/bus-unit-util.h +++ b/src/shared/bus-unit-util.h @@ -7,6 +7,16 @@ #include "pidref.h" #include "unit-def.h" +typedef enum ExecDirectoryFlags { + EXEC_DIRECTORY_READ_ONLY = 1 << 0, /* Public API via DBUS, do not change */ + EXEC_DIRECTORY_ONLY_CREATE = 1 << 1, /* Only the private directory will be created, not the symlink to it */ + _EXEC_DIRECTORY_FLAGS_MAX, + _EXEC_DIRECTORY_FLAGS_PUBLIC = EXEC_DIRECTORY_READ_ONLY, + _EXEC_DIRECTORY_FLAGS_INVALID = -EINVAL, +} ExecDirectoryFlags; + +ExecDirectoryFlags exec_directory_flags_from_string(const char *s) _pure_; + typedef struct UnitInfo { const char *machine; const char *id; diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 31f6db5137..e91284177c 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -480,17 +480,17 @@ finish: return 0; } -void dns_name_hash_func(const char *p, struct siphash *state) { +void dns_name_hash_func(const char *name, struct siphash *state) { int r; - assert(p); + assert(name); - for (;;) { + for (const char *p = name;;) { char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, 0); if (r < 0) - break; + return string_hash_func(p, state); /* fallback for invalid DNS names */ if (r == 0) break; @@ -516,13 +516,13 @@ int dns_name_compare_func(const char *a, const char *b) { for (;;) { char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; - if (x == NULL && y == NULL) + if (!x && !y) return 0; r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); if (r < 0 || q < 0) - return CMP(r, q); + return strcmp(a, b); /* if not valid DNS labels, then let's compare the whole strings as is */ r = ascii_strcasecmp_nn(la, r, lb, q); if (r != 0) diff --git a/src/shared/fdset.c b/src/shared/fdset.c index b3d3cfa784..42092ada25 100644 --- a/src/shared/fdset.c +++ b/src/shared/fdset.c @@ -22,7 +22,7 @@ #define MAKE_SET(s) ((Set*) s) #define MAKE_FDSET(s) ((FDSet*) s) -FDSet *fdset_new(void) { +FDSet* fdset_new(void) { return MAKE_FDSET(set_new(NULL)); } @@ -52,12 +52,20 @@ int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) { return 0; } -void fdset_close(FDSet *s, bool async) { +int fdset_steal_first(FDSet *fds) { void *p; - while ((p = set_steal_first(MAKE_SET(s)))) { - int fd = PTR_TO_FD(p); + p = set_steal_first(MAKE_SET(fds)); + if (!p) + return -ENOENT; + + return PTR_TO_FD(p); +} + +void fdset_close(FDSet *fds, bool async) { + int fd; + while ((fd = fdset_steal_first(fds)) >= 0) { /* Valgrind's fd might have ended up in this set here, due to fdset_new_fill(). We'll ignore * all failures here, so that the EBADFD that valgrind will return us on close() doesn't * influence us */ @@ -144,7 +152,7 @@ bool fdset_contains(FDSet *s, int fd) { return false; } - return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); + return set_contains(MAKE_SET(s), FD_TO_PTR(fd)); } int fdset_remove(FDSet *s, int fd) { @@ -230,13 +238,13 @@ int fdset_new_fill( } int fdset_cloexec(FDSet *fds, bool b) { - void *p; int r; assert(fds); - SET_FOREACH(p, MAKE_SET(fds)) { - r = fd_cloexec(PTR_TO_FD(p), b); + int fd; + FDSET_FOREACH(fd, fds) { + r = fd_cloexec(fd, b); if (r < 0) return r; } @@ -269,7 +277,6 @@ int fdset_new_listen_fds(FDSet **ret, bool unset) { int fdset_to_array(FDSet *fds, int **ret) { unsigned j = 0, m; - void *e; int *a; assert(ret); @@ -286,8 +293,9 @@ int fdset_to_array(FDSet *fds, int **ret) { if (!a) return -ENOMEM; - SET_FOREACH(e, MAKE_SET(fds)) - a[j++] = PTR_TO_FD(e); + int fd; + FDSET_FOREACH(fd, fds) + a[j++] = fd; assert(j == m); @@ -322,13 +330,3 @@ int fdset_iterate(FDSet *s, Iterator *i) { return PTR_TO_FD(p); } - -int fdset_steal_first(FDSet *fds) { - void *p; - - p = set_steal_first(MAKE_SET(fds)); - if (!p) - return -ENOENT; - - return PTR_TO_FD(p); -} diff --git a/src/shared/format-table.c b/src/shared/format-table.c index f5ef7175a3..5f05247438 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -3124,7 +3124,7 @@ int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) { assert(t); - if (flags & SD_JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */ + if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */ return table_print(t, f); if (!f) diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index 4dc0b8f662..9264638303 100644 --- a/src/shared/journal-importer.c +++ b/src/shared/journal-importer.c @@ -33,7 +33,7 @@ void journal_importer_cleanup(JournalImporter *imp) { free(imp->name); free(imp->buf); - iovw_free_contents(&imp->iovw, false); + iovw_done(&imp->iovw); } static char* realloc_buffer(JournalImporter *imp, size_t size) { @@ -452,7 +452,7 @@ void journal_importer_drop_iovw(JournalImporter *imp) { /* This function drops processed data that along with the iovw that points at it */ - iovw_free_contents(&imp->iovw, false); + iovw_done(&imp->iovw); /* possibly reset buffer position */ remain = imp->filled - imp->offset; diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 0360fa9d72..4e38e60775 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include <sys/utsname.h> #include <errno.h> +#include <math.h> #include <stdio.h> +#include <sys/utsname.h> #include "alloc-util.h" #include "color-util.h" @@ -87,7 +88,9 @@ int terminal_urlify(const char *url, const char *text, char **ret) { text = url; if (urlify_enabled()) - n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a"); + n = strjoin(ANSI_OSC "8;;", url, ANSI_ST, + text, + ANSI_OSC "8;;" ANSI_ST); else n = strdup(text); if (!n) @@ -460,7 +463,7 @@ bool shall_tint_background(void) { return cache != 0; } -void draw_progress_bar_impl(const char *prefix, double percentage) { +void draw_progress_bar_unbuffered(const char *prefix, double percentage) { fputc('\r', stderr); if (prefix) { fputs(prefix, stderr); @@ -468,6 +471,14 @@ void draw_progress_bar_impl(const char *prefix, double percentage) { } if (!terminal_is_dumb()) { + /* Generate the Windows Terminal progress indication OSC sequence here. Most Linux terminals currently + * ignore this. But let's hope this changes one day. For details about this OSC sequence, see: + * + * https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC + * https://github.com/microsoft/terminal/pull/8055 + */ + fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, (unsigned) ceil(percentage)); + size_t cols = columns(); size_t prefix_width = utf8_console_width(prefix) + 1 /* space */; size_t length = cols > prefix_width + 6 ? cols - prefix_width - 6 : 0; @@ -513,10 +524,9 @@ void draw_progress_bar_impl(const char *prefix, double percentage) { fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); fputc('\r', stderr); - } -void clear_progress_bar_impl(const char *prefix) { +void clear_progress_bar_unbuffered(const char *prefix) { fputc('\r', stderr); if (terminal_is_dumb()) @@ -525,7 +535,9 @@ void clear_progress_bar_impl(const char *prefix) { LESS_BY(columns(), 1U)), stderr); else - fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + /* Undo Windows Terminal progress indication again. */ + fputs(ANSI_OSC "9;4;0;;" ANSI_ST + ANSI_ERASE_TO_END_OF_LINE, stderr); fputc('\r', stderr); } @@ -535,10 +547,26 @@ void draw_progress_bar(const char *prefix, double percentage) { * unbuffered by default. Let's temporarily turn on full buffering, so that this is passed to the tty * as a single buffer, to make things more efficient. */ WITH_BUFFERED_STDERR; - draw_progress_bar_impl(prefix, percentage); + draw_progress_bar_unbuffered(prefix, percentage); +} + +int draw_progress_barf(double percentage, const char *prefixf, ...) { + _cleanup_free_ char *s = NULL; + va_list ap; + int r; + + va_start(ap, prefixf); + r = vasprintf(&s, prefixf, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + draw_progress_bar(s, percentage); + return 0; } void clear_progress_bar(const char *prefix) { WITH_BUFFERED_STDERR; - clear_progress_bar_impl(prefix); + clear_progress_bar_unbuffered(prefix); } diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 446f305b73..8ea1e964a3 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -54,9 +54,10 @@ int terminal_tint_color(double hue, char **ret); bool shall_tint_background(void); void draw_progress_bar(const char *prefix, double percentage); +int draw_progress_barf(double percentage, const char *prefixf, ...) _printf_(2, 3); void clear_progress_bar(const char *prefix); -void draw_progress_bar_impl(const char *prefix, double percentage); -void clear_progress_bar_impl(const char *prefix); +void draw_progress_bar_unbuffered(const char *prefix, double percentage); +void clear_progress_bar_unbuffered(const char *prefix); static inline FILE* enable_buffering(FILE *f, char *buffer, size_t size) { assert(f); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index c06c279d87..c1e3b7180b 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -397,7 +397,7 @@ static int insert_window_title_fix(PTYForward *f, size_t offset) { if (!t) return 0; - _cleanup_free_ char *joined = strjoin("\x1b]0;", f->title_prefix, t, "\a"); + _cleanup_free_ char *joined = strjoin(ANSI_OSC "0;", f->title_prefix, t, ANSI_ST); if (!joined) return -ENOMEM; @@ -507,11 +507,16 @@ static int pty_forward_ansi_process(PTYForward *f, size_t offset) { } else { /* Otherwise, the OSC sequence is over * - * There are two allowed ways to end an OSC sequence: - * BEL '\x07' - * String Terminator (ST): <Esc>\ - "\x1b\x5c" - * since we cannot lookahead to see if the Esc is followed by a \ - * we cut a corner here and assume it will be \. */ + * There are three documented ways to end an OSC sequence: + * 1. BEL aka ^G aka \x07 + * 2. \x9c + * 3. \x1b\x5c + * since we cannot look ahead to see if the Esc is followed by a "\" + * we cut a corner here and assume it will be "\"e. + * + * Note that we do not support \x9c here, because that's also a valid UTF8 + * codepoint, and that would create ambiguity. Various terminal emulators + * similar do not support it. */ if (IN_SET(c, '\x07', '\x1b')) { r = insert_window_title_fix(f, i+1); @@ -567,7 +572,7 @@ static int do_shovel(PTYForward *f) { if (f->title) { if (!strextend(&f->out_buffer, ANSI_WINDOW_TITLE_PUSH - "\x1b]2;", f->title, "\a")) + ANSI_OSC "2;", f->title, ANSI_ST)) return log_oom(); } diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index 36cd5dcd7c..da88d80d03 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -173,36 +173,49 @@ static void write_qrcode(FILE *output, QRcode *qr, unsigned int row, unsigned in fflush(output); } -int print_qrcode_full(FILE *out, const char *header, const char *string, unsigned row, unsigned column, unsigned tty_width, unsigned tty_height) { - QRcode* qr; +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(QRcode*, sym_QRcode_free, NULL); + +int print_qrcode_full( + FILE *out, + const char *header, + const char *string, + unsigned row, + unsigned column, + unsigned tty_width, + unsigned tty_height, + bool check_tty) { + int r; /* If this is not a UTF-8 system or ANSI colors aren't supported/disabled don't print any QR * codes */ - if (!is_locale_utf8() || !colors_enabled()) - return -EOPNOTSUPP; + if (!is_locale_utf8()) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not an UTF-8 system, cannot print qrcode"); + if (check_tty && !colors_enabled()) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Colors are disabled, cannot print qrcode"); r = dlopen_qrencode(); if (r < 0) return r; - qr = sym_QRcode_encodeString(string, 0, QR_ECLEVEL_L, QR_MODE_8, 1); + _cleanup_(sym_QRcode_freep) QRcode *qr = + sym_QRcode_encodeString(string, 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (!qr) - return -ENOMEM; + return log_oom_debug(); if (row != UINT_MAX && column != UINT_MAX) { - int fd; unsigned qr_code_width, qr_code_height; - fd = fileno(out); + + int fd = fileno(out); if (fd < 0) return log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); - qr_code_width = qr_code_height = qr->width + 8; + qr_code_width = qr_code_height = qr->width + 8; if (column + qr_code_width > tty_width) column = tty_width - qr_code_width; /* Terminal characters are twice as high as they are wide so it's qr_code_height / 2, - * our QR code prints an extra new line, so we have -1 as well */ + * our QR code prints an extra new line, so we have -1 as well */ if (row + qr_code_height > tty_height) row = tty_height - (qr_code_height / 2 ) - 1; @@ -218,10 +231,8 @@ int print_qrcode_full(FILE *out, const char *header, const char *string, unsigne fprintf(out, "\n%s:\n\n", header); write_qrcode(out, qr, row, column); - fputc('\n', out); - sym_QRcode_free(qr); return 0; } #endif diff --git a/src/shared/qrcode-util.h b/src/shared/qrcode-util.h index ee58294436..89a15bb3f5 100644 --- a/src/shared/qrcode-util.h +++ b/src/shared/qrcode-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ - #pragma once + +#include <stdbool.h> #include <stdio.h> #include <errno.h> #include <limits.h> @@ -8,15 +9,29 @@ #if HAVE_QRENCODE int dlopen_qrencode(void); -int print_qrcode_full(FILE *out, const char *header, const char *string, unsigned row, unsigned column, unsigned tty_width, unsigned tty_height); -static inline int print_qrcode(FILE *out, const char *header, const char *string) { - return print_qrcode_full(out, header, string, UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX); -} +int print_qrcode_full( + FILE *out, + const char *header, + const char *string, + unsigned row, + unsigned column, + unsigned tty_width, + unsigned tty_height, + bool check_tty); #else -static inline int print_qrcode_full(FILE *out, const char *header, const char *string, unsigned row, unsigned column, unsigned tty_width, unsigned tty_height) { +static inline int print_qrcode_full( + FILE *out, + const char *header, + const char *string, + unsigned row, + unsigned column, + unsigned tty_width, + unsigned tty_height, + bool check_tty) { return -EOPNOTSUPP; } +#endif + static inline int print_qrcode(FILE *out, const char *header, const char *string) { - return -EOPNOTSUPP; + return print_qrcode_full(out, header, string, UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX, true); } -#endif diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index 45ef48bab6..e6de0cd002 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -28,6 +28,25 @@ const char* user_record_state_color(const char *state) { return NULL; } +static void dump_self_modifiable(const char *heading, char **field, const char **value) { + assert(heading); + + /* Helper function for printing the various self_modifiable_* fields from the user record */ + + if (strv_isempty((char**) value)) + /* Case 1: the array is explicitly set to be empty by the administrator */ + printf("%13s %sDisabled by Administrator%s\n", heading, ansi_highlight_red(), ansi_normal()); + else if (!field) + /* Case 2: we have values, but the field is NULL. This means that we're using the defaults. + * We list them anyways, because they're security-sensitive to the administrator */ + STRV_FOREACH(i, value) + printf("%13s %s%s%s\n", i == value ? heading : "", ansi_grey(), *i, ansi_normal()); + else + /* Case 3: we have a list provided by the administrator */ + STRV_FOREACH(i, value) + printf("%13s %s\n", i == value ? heading : "", *i); +} + void user_record_show(UserRecord *hr, bool show_full_group_info) { _cleanup_strv_free_ char **langs = NULL; const char *hd, *ip, *shell; @@ -585,6 +604,16 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->service) printf(" Service: %s\n", hr->service); + + dump_self_modifiable("Self Modify:", + hr->self_modifiable_fields, + user_record_self_modifiable_fields(hr)); + dump_self_modifiable("(Blobs)", + hr->self_modifiable_blobs, + user_record_self_modifiable_blobs(hr)); + dump_self_modifiable("(Privileged)", + hr->self_modifiable_privileged, + user_record_self_modifiable_privileged(hr)); } void group_record_show(GroupRecord *gr, bool show_full_user_info) { diff --git a/src/shared/user-record.c b/src/shared/user-record.c index b03a38eb18..47fd5cc311 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -207,6 +207,10 @@ static UserRecord* user_record_free(UserRecord *h) { for (size_t i = 0; i < h->n_recovery_key; i++) recovery_key_done(h->recovery_key + i); + strv_free(h->self_modifiable_fields); + strv_free(h->self_modifiable_blobs); + strv_free(h->self_modifiable_privileged); + sd_json_variant_unref(h->json); return mfree(h); @@ -1300,6 +1304,9 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j { "passwordChangeNow", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 }, { "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 }, { "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 }, + { "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 }, {}, }; @@ -1468,7 +1475,6 @@ int user_group_record_mangle( assert(v); assert(ret_variant); - assert(ret_mask); /* Note that this function is shared with the group record parser, hence we try to be generic in our * log message wording here, to cover both cases. */ @@ -1556,7 +1562,8 @@ int user_group_record_mangle( else *ret_variant = sd_json_variant_ref(v); - *ret_mask = m; + if (ret_mask) + *ret_mask = m; return 0; } @@ -1646,6 +1653,9 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load { "pkcs11TokenUri", SD_JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 }, { "fido2HmacCredential", SD_JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 }, { "recoveryKeyType", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(UserRecord, recovery_key_type), 0 }, + { "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 }, { "secret", SD_JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 }, { "privileged", SD_JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 }, @@ -2156,6 +2166,244 @@ int user_record_languages(UserRecord *h, char ***ret) { return 0; } +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 + * a given system. */ + static const char *const default_fields[] = { + /* For display purposes */ + "realName", + "emailAddress", /* Just the $EMAIL env var */ + "iconName", + "location", + + /* Basic account settings */ + "shell", + "umask", + "environment", + "timeZone", + "preferredLanguage", + "additionalLanguages", + "preferredSessionLauncher", + "preferredSessionType", + + /* Authentication methods */ + "pkcs11TokenUri", + "fido2HmacCredential", + "recoveryKeyType", + + "lastChangeUSec", /* Necessary to be able to change record at all */ + "lastPasswordChangeUSec", /* Ditto, but for authentication methods */ + NULL + }; + + assert(h); + + /* Note that we intentionally distinguish between NULL and an empty array here */ + return (const char**) h->self_modifiable_fields ?: (const char**) default_fields; +} + +const char** user_record_self_modifiable_blobs(UserRecord *h) { + static const char *const default_blobs[] = { + /* For display purposes */ + "avatar", + "login-background", + NULL + }; + + assert(h); + + /* Note that we intentionally distinguish between NULL and an empty array here */ + return (const char**) h->self_modifiable_blobs ?: (const char**) default_blobs; +} + +const char** user_record_self_modifiable_privileged(UserRecord *h) { + static const char *const default_fields[] = { + /* For display purposes */ + "passwordHint", + + /* Authentication methods */ + "hashedPassword" + "pkcs11EncryptedKey", + "fido2HmacSalt", + "recoveryKey", + + "sshAuthorizedKeys", /* Basically just ~/.ssh/authorized_keys */ + NULL + }; + + assert(h); + + /* Note that we intentionally distinguish between NULL and an empty array here */ + return (const char**) h->self_modifiable_privileged ?: (const char**) default_fields; +} + +static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_json_variant **target) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *blobs = NULL; + char **allowed; + int r; + + assert(current); + assert(target); + + if (!sd_json_variant_is_object(*target)) + return -EINVAL; + + v = sd_json_variant_ref(*target); + + /* Handle basic fields */ + allowed = (char**) user_record_self_modifiable_fields(current); + r = sd_json_variant_filter(&v, allowed); + if (r < 0) + return r; + + /* Handle blobs */ + blobs = sd_json_variant_ref(sd_json_variant_by_key(v, "blobManifest")); + if (blobs) { + /* The blobManifest contains the sha256 hashes of the blobs, + * which are enforced by the service managing the user. So, by + * comparing the blob manifests like this, we're actually comparing + * the contents of the blob directories & files */ + + allowed = (char**) user_record_self_modifiable_blobs(current); + r = sd_json_variant_filter(&blobs, allowed); + if (r < 0) + return r; + + if (sd_json_variant_is_blank_object(blobs)) + r = sd_json_variant_filter(&v, STRV_MAKE("blobManifest")); + else + r = sd_json_variant_set_field(&v, "blobManifest", blobs); + if (r < 0) + return r; + } + + JSON_VARIANT_REPLACE(*target, TAKE_PTR(v)); + return 0; +} + +static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *privileged = NULL; + sd_json_variant *per_machine; + char **allowed; + int r; + + assert(current); + assert(h); + assert(ret); + + r = user_group_record_mangle(h->json, USER_RECORD_EXTRACT_SIGNABLE|USER_RECORD_PERMISSIVE, &v, NULL); + if (r < 0) + return r; + + /* Handle the regular section */ + r = remove_self_modifiable_json_fields_common(current, &v); + if (r < 0) + return r; + + /* Handle the perMachine section */ + per_machine = sd_json_variant_by_key(v, "perMachine"); + if (per_machine) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_per_machine = NULL; + sd_json_variant *e; + + if (!sd_json_variant_is_array(per_machine)) + return -EINVAL; + + JSON_VARIANT_ARRAY_FOREACH(e, per_machine) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *z = NULL; + + if (!sd_json_variant_is_object(e)) + return -EINVAL; + + r = per_machine_match(e, 0); + if (r < 0) + return r; + if (r == 0) { + /* It's only permissible to change anything inside of matching perMachine sections */ + r = sd_json_variant_append_array(&new_per_machine, e); + if (r < 0) + return r; + continue; + } + + z = sd_json_variant_ref(e); + + r = remove_self_modifiable_json_fields_common(current, &z); + if (r < 0) + return r; + + if (!sd_json_variant_is_blank_object(z)) { + r = sd_json_variant_append_array(&new_per_machine, z); + if (r < 0) + return r; + } + } + + if (sd_json_variant_is_blank_array(new_per_machine)) + r = sd_json_variant_filter(&v, STRV_MAKE("perMachine")); + else + r = sd_json_variant_set_field(&v, "perMachine", new_per_machine); + if (r < 0) + return r; + } + + /* Handle the privileged section */ + privileged = sd_json_variant_ref(sd_json_variant_by_key(v, "privileged")); + if (privileged) { + allowed = (char**) user_record_self_modifiable_privileged(current); + r = sd_json_variant_filter(&privileged, allowed); + if (r < 0) + return r; + + if (sd_json_variant_is_blank_object(privileged)) + r = sd_json_variant_filter(&v, STRV_MAKE("privileged")); + else + r = sd_json_variant_set_field(&v, "privileged", privileged); + if (r < 0) + return r; + } + + JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v)); + return 0; +} + +int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *vc = NULL, *vi = NULL; + int r; + + assert(current); + assert(incoming); + + /* We remove the fields that the user is allowed to change and then + * compare the resulting JSON records. If they are not equal, that + * means a disallowed field has been changed and thus we should + * require administrator permission to apply the changes. */ + + r = remove_self_modifiable_json_fields(current, current, &vc); + if (r < 0) + return r; + + /* Note that we use `current` as the source of the allowlist, and not + * `incoming`. This prevents the user from adding fields. Consider a + * scenario that would've been possible if we had messed up this check: + * + * 1) A user starts out with no group memberships and no custom allowlist. + * Thus, this user is not an administrator, and the `memberOf` and + * `selfModifiableFields` fields are unset in their record. + * 2) This user crafts a request to add the following to their record: + * { "memberOf": ["wheel"], "selfModifiableFields": ["memberOf", "selfModifiableFields"] } + * 3) We remove the `mebmerOf` and `selfModifiabileFields` fields from `incoming` + * 4) `current` and `incoming` compare as equal, so we let the change happen + * 5) the user has granted themselves administrator privileges + */ + r = remove_self_modifiable_json_fields(current, incoming, &vi); + if (r < 0) + return r; + + return sd_json_variant_equal(vc, vi); +} + uint64_t user_record_ratelimit_next_try(UserRecord *h) { assert(h); diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 0443820890..b539b3f55e 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -383,6 +383,10 @@ typedef struct UserRecord { char **capability_bounding_set; char **capability_ambient_set; + char **self_modifiable_fields; /* fields a user can change about themself w/o auth */ + char **self_modifiable_blobs; + char **self_modifiable_privileged; + sd_json_variant *json; } UserRecord; @@ -431,6 +435,11 @@ 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); +const char **user_record_self_modifiable_fields(UserRecord *h); +const char **user_record_self_modifiable_blobs(UserRecord *h); +const char **user_record_self_modifiable_privileged(UserRecord *h); +int user_record_self_changes_allowed(UserRecord *current, UserRecord *new); + int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret); bool user_record_equal(UserRecord *a, UserRecord *b); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index fcf29a99d3..0adfab253c 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -2222,7 +2222,7 @@ static int verb_list(int argc, char **argv, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to discover images: %m"); - if ((arg_json_format_flags & SD_JSON_FORMAT_OFF) && hashmap_isempty(images)) { + if (hashmap_isempty(images) && !sd_json_format_enabled(arg_json_format_flags)) { log_info("No OS extensions found."); return 0; } diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index 4f6e304816..9d69230097 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -180,10 +180,9 @@ int halt_main(void) { return start_with_fallback(); } - if (geteuid() != 0) { - (void) must_be_root(); - return -EPERM; - } + r = must_be_root(); + if (r < 0) + return r; if (!arg_no_wtmp) { if (sd_booted() > 0) diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index e4ef7cf915..fc64e60c64 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -204,6 +204,10 @@ int logind_check_inhibitors(enum action a) { if (r < 0) return bus_log_parse_error(r); + /* root respects inhibitors since v257 but keeps ignoring sessions by default */ + if (arg_check_inhibitors < 0 && c == 0 && geteuid() == 0) + return 0; + /* Check for current sessions */ sd_get_sessions(&sessions); STRV_FOREACH(s, sessions) { diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 00dd05bff7..5fff5d4f18 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -201,14 +201,17 @@ int verb_start_special(int argc, char *argv[], void *userdata) { case ACTION_KEXEC: case ACTION_HALT: case ACTION_SOFT_REBOOT: - if (arg_when == 0) + if (arg_when == 0) { r = logind_reboot(a); - else + if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) + /* The latter indicates that the requested operation requires auth, + * is not supported or already in progress, in which cases we ignore the error. */ + return r; + } else { r = logind_schedule_shutdown(a); - if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) - /* The latter indicates that the requested operation requires auth, - * is not supported or already in progress, in which cases we ignore the error. */ - return r; + if (r != -ENOSYS) + return r; + } /* On all other errors, try low-level operation. In order to minimize the difference * between operation with and without logind, we explicitly enable non-blocking mode diff --git a/src/systemd/_sd-common.h b/src/systemd/_sd-common.h index dbe9fa035e..5792dd8106 100644 --- a/src/systemd/_sd-common.h +++ b/src/systemd/_sd-common.h @@ -45,6 +45,10 @@ typedef void (*_sd_destroy_t)(void *userdata); # define _sd_pure_ __attribute__((__pure__)) #endif +#ifndef _sd_const_ +# define _sd_const_ __attribute__((__const__)) +#endif + /* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6 * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h index 7573991c20..7be690400d 100644 --- a/src/systemd/sd-id128.h +++ b/src/systemd/sd-id128.h @@ -117,24 +117,24 @@ int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret); #define SD_ID128_MAKE_UUID_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ #a #b #c #d "-" #e #f "-" #g #h "-" #i #j "-" #k #l #m #n #o #p -_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { +_sd_const_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { return a.qwords[0] == b.qwords[0] && a.qwords[1] == b.qwords[1]; } int sd_id128_string_equal(const char *s, sd_id128_t id); -_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) { +_sd_const_ static __inline__ int sd_id128_is_null(sd_id128_t a) { return a.qwords[0] == 0 && a.qwords[1] == 0; } -_sd_pure_ static __inline__ int sd_id128_is_allf(sd_id128_t a) { +_sd_const_ static __inline__ int sd_id128_is_allf(sd_id128_t a) { return a.qwords[0] == UINT64_C(0xFFFFFFFFFFFFFFFF) && a.qwords[1] == UINT64_C(0xFFFFFFFFFFFFFFFF); } #define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) #define SD_ID128_ALLF ((const sd_id128_t) { .qwords = { UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF) }}) -_sd_pure_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { +_sd_const_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { for (;;) { sd_id128_t b = va_arg(ap, sd_id128_t); @@ -146,7 +146,7 @@ _sd_pure_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { } } -_sd_pure_ static __inline__ int sd_id128_in_set_sentinel(sd_id128_t a, ...) { +_sd_const_ static __inline__ int sd_id128_in_set_sentinel(sd_id128_t a, ...) { va_list ap; int r; diff --git a/src/systemd/sd-json.h b/src/systemd/sd-json.h index f3c9a27257..3930d82b0d 100644 --- a/src/systemd/sd-json.h +++ b/src/systemd/sd-json.h @@ -342,6 +342,10 @@ int sd_json_variant_unhex(sd_json_variant *v, void **ret, size_t *ret_size); const char* sd_json_variant_type_to_string(sd_json_variant_type_t t); sd_json_variant_type_t sd_json_variant_type_from_string(const char *s); +_sd_const_ static __inline__ int sd_json_format_enabled(sd_json_format_flags_t flags) { + return !(flags & SD_JSON_FORMAT_OFF); +} + _SD_END_DECLARATIONS; #endif diff --git a/src/systemd/sd-varlink-idl.h b/src/systemd/sd-varlink-idl.h index e5e8a57498..fe65c00eb6 100644 --- a/src/systemd/sd-varlink-idl.h +++ b/src/systemd/sd-varlink-idl.h @@ -18,7 +18,6 @@ ***/ #include <errno.h> -#include <stdbool.h> #include <stdio.h> #include "sd-json.h" @@ -61,7 +60,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_symbol_flags_t) { } sd_varlink_symbol_flags_t; __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_field_type_t) { - _SD_VARLINK_FIELD_TYPE_END_MARKER = 0, /* zero type means: this is the last entry in the fields[] array of VarlinkSymbol */ + _SD_VARLINK_FIELD_TYPE_END_MARKER = 0, /* zero type means: this is the last entry in the fields[] array of sd_varlink_symbol */ SD_VARLINK_STRUCT, SD_VARLINK_ENUM, SD_VARLINK_NAMED_TYPE, diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 4596561ed3..fbf575ffdf 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -68,7 +68,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_server_flags_t) { SD_VARLINK_SERVER_ROOT_ONLY = 1 << 0, /* Only accessible by root */ SD_VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */ SD_VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */ - SD_VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */ + SD_VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from sd_varlink_server userdata */ SD_VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark all connection input as sensitive */ _SD_ENUM_FORCE_S64(SD_VARLINK_SERVER) } sd_varlink_server_flags_t; diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index 0c7c469a62..baf3e93a8d 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -40,7 +40,7 @@ executables += [ libexec_template + { 'name' : 'systemd-sysupdated', 'dbus' : true, - 'conditions' : ['ENABLE_SYSUPDATE'], + 'conditions' : ['ENABLE_SYSUPDATED'], 'sources' : files('sysupdated.c'), 'dependencies' : threads, }, @@ -48,11 +48,11 @@ executables += [ 'name' : 'updatectl', 'public' : true, 'sources' : systemd_updatectl_sources, - 'conditions' : ['ENABLE_SYSUPDATE'], + 'conditions' : ['ENABLE_SYSUPDATED'], }, ] -if conf.get('ENABLE_SYSUPDATE') == 1 +if conf.get('ENABLE_SYSUPDATED') == 1 install_data('org.freedesktop.sysupdate1.conf', install_dir : dbuspolicydir) install_data('org.freedesktop.sysupdate1.service', diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 40a274b879..2bdd71e742 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -585,7 +585,7 @@ static int context_show_version(Context *c, const char *version) { if (arg_json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) (void) pager_open(arg_pager_flags); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (!sd_json_format_enabled(arg_json_format_flags)) printf("%s%s%s Version: %s\n" " State: %s%s%s\n" "Installed: %s%s\n" @@ -784,7 +784,7 @@ static int context_show_version(Context *c, const char *version) { if (!have_sha256) (void) table_hide_column_from_display(t, 12); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { printf("%s%s%s Version: %s\n" " State: %s%s%s\n" "Installed: %s%s%s%s%s\n" @@ -873,7 +873,7 @@ static int context_vacuum( disabled_count++; } - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { if (count > 0 && disabled_count > 0) log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count); else if (count > 0) @@ -1157,7 +1157,7 @@ static int verb_list(int argc, char **argv, void *userdata) { if (version) return context_show_version(context, version); - else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + else if (!sd_json_format_enabled(arg_json_format_flags)) return context_show_table(context); else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; @@ -1351,7 +1351,7 @@ static int verb_check_new(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { if (!context->candidate) { log_debug("No candidate found."); return EXIT_FAILURE; @@ -1611,7 +1611,7 @@ static int verb_components(int argc, char **argv, void *userdata) { strv_sort(z); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { if (!has_default_component && set_isempty(names)) { log_info("No components defined."); return 0; diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index e7e4b374da..7cfccb66f4 100644 --- a/src/sysupdate/updatectl.c +++ b/src/sysupdate/updatectl.c @@ -843,23 +843,23 @@ static int update_render_progress(sd_event_source *source, void *userdata) { int progress = PTR_TO_INT(p); if (progress == UPDATE_PROGRESS_FAILED) { - clear_progress_bar_impl(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Unknown failure\n", target, RED_CROSS_MARK()); total += 100; } else if (progress == -EALREADY) { - clear_progress_bar_impl(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK()); n--; /* Don't consider this target in the total */ } else if (progress < 0) { - clear_progress_bar_impl(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress)); total += 100; } else if (progress == UPDATE_PROGRESS_DONE) { - clear_progress_bar_impl(target); + clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Done\n", target, GREEN_CHECK_MARK()); total += 100; } else { - draw_progress_bar_impl(target, progress); + draw_progress_bar_unbuffered(target, progress); fputs("\n", stderr); total += progress; } @@ -867,9 +867,9 @@ static int update_render_progress(sd_event_source *source, void *userdata) { if (n > 1) { if (exiting) - clear_progress_bar_impl(target); + clear_progress_bar_unbuffered(target); else { - draw_progress_bar_impl("Total", (double) total / n); + draw_progress_bar_unbuffered("Total", (double) total / n); if (terminal_is_dumb()) fputs("\n", stderr); } diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 8ec1373479..44253483db 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -86,6 +86,8 @@ typedef struct Item { bool uid_set; + bool locked; + bool todo_user; bool todo_group; } Item; @@ -654,7 +656,7 @@ static int write_temporary_shadow( .sp_max = -1, .sp_warn = -1, .sp_inact = -1, - .sp_expire = -1, + .sp_expire = i->locked ? 1 : -1, /* Negative expiration means "unset". Expiration 0 or 1 means "locked" */ .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */ }; @@ -1707,10 +1709,17 @@ static int parse_line( return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Trailing garbage."); - /* Verify action */ - if (strlen(action) != 1) - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), - "Unknown modifier '%s'.", action); + if (isempty(action)) + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Empty command specification."); + + bool locked = false; + for (int pos = 1; action[pos]; pos++) + if (action[pos] == '!' && !locked) + locked = true; + else + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Unknown modifiers in command '%s'.", action); if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), @@ -1793,6 +1802,10 @@ static int parse_line( switch (action[0]) { case ADD_RANGE: + if (locked) + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Flag '!' not permitted on lines of type 'r'."); + if (resolved_name) return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Lines of type 'r' don't take a name field."); @@ -1820,6 +1833,10 @@ static int parse_line( return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Lines of type 'm' require a user name in the second field."); + if (locked) + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Flag '!' not permitted on lines of type 'm'."); + if (!resolved_id) return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Lines of type 'm' require a group name in the third field."); @@ -1886,6 +1903,7 @@ static int parse_line( i->description = TAKE_PTR(resolved_description); i->home = TAKE_PTR(resolved_home); i->shell = TAKE_PTR(resolved_shell); + i->locked = locked; h = c->users; break; @@ -1895,6 +1913,10 @@ static int parse_line( return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Lines of type 'g' require a user name in the second field."); + if (locked) + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Flag '!' not permitted on lines of type 'g'."); + if (description || home || shell) return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Lines of type '%c' don't take a %s field.", diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c index f3b447095a..fde8c057e3 100644 --- a/src/sysv-generator/sysv-generator.c +++ b/src/sysv-generator/sysv-generator.c @@ -763,12 +763,13 @@ static int enumerate_sysv(const LookupPaths *lp, Hashmap *all_services) { return log_oom(); log_struct(LOG_WARNING, - LOG_MESSAGE("SysV service '%s' lacks a native systemd unit file. " - "%s Automatically generating a unit file for compatibility. Please update package to include a native systemd unit file, in order to make it safe, robust and future-proof. " + LOG_MESSAGE("SysV service '%s' lacks a native systemd unit file, " + "automatically generating a unit file for compatibility.\n" + "Please update package to include a native systemd unit file.\n" "%s This compatibility logic is deprecated, expect removal soon. %s", fpath, - special_glyph(SPECIAL_GLYPH_RECYCLING), - special_glyph(SPECIAL_GLYPH_WARNING_SIGN), special_glyph(SPECIAL_GLYPH_WARNING_SIGN)), + special_glyph(SPECIAL_GLYPH_WARNING_SIGN), + special_glyph(SPECIAL_GLYPH_WARNING_SIGN)), "MESSAGE_ID=" SD_MESSAGE_SYSV_GENERATOR_DEPRECATED_STR, "SYSVSCRIPT=%s", fpath, "UNIT=%s", name); diff --git a/src/test/meson.build b/src/test/meson.build index b8699a016b..9f74a7b56a 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -183,6 +183,7 @@ simple_tests += files( 'test-umask-util.c', 'test-unaligned.c', 'test-unit-file.c', + 'test-user-record.c', 'test-user-util.c', 'test-utf8.c', 'test-verbs.c', @@ -378,7 +379,6 @@ executables += [ }, test_template + { 'sources' : files('test-progress-bar.c'), - 'type' : 'manual', }, test_template + { 'sources' : files('test-qrcode-util.c'), diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index e2c009dc9c..7eda66bd3e 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -581,4 +581,15 @@ TEST(getenv_path_list) { assert_se(unsetenv("TEST_GETENV_PATH_LIST") >= 0); } +TEST(strv_env_get_merged) { + char **l = STRV_MAKE("ONE", "1", "TWO", "2", "THREE", "3", "FOUR", "4", "FIVE", "5"), + **expected = STRV_MAKE("ONE=1", "TWO=2", "THREE=3", "FOUR=4", "FIVE=5"); + _cleanup_strv_free_ char **m = NULL; + + ASSERT_OK(strv_env_get_merged(NULL, &m)); + ASSERT_NULL(m); + ASSERT_OK(strv_env_get_merged(l, &m)); + ASSERT_TRUE(strv_equal(m, expected)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-progress-bar.c b/src/test/test-progress-bar.c index abc1d292dc..b199741cf5 100644 --- a/src/test/test-progress-bar.c +++ b/src/test/test-progress-bar.c @@ -14,7 +14,7 @@ TEST(progress_bar) { for (double d = 0; d <= 100; d += 0.5) { usleep_safe(random_u64_range(20 * USEC_PER_MSEC)); - draw_progress_bar(PROGRESS_PREFIX, d); + draw_progress_barf(d, PROGRESS_PREFIX "[" PID_FMT "]", getpid_cached()); if (!paused && d >= 50) { clear_progress_bar(PROGRESS_PREFIX); @@ -25,9 +25,9 @@ TEST(progress_bar) { } } - draw_progress_bar(PROGRESS_PREFIX, 100); + draw_progress_barf(100, PROGRESS_PREFIX "[" PID_FMT "]", getpid_cached()); usleep_safe(300 * MSEC_PER_SEC); - clear_progress_bar(PROGRESS_PREFIX); + clear_progress_bar(PROGRESS_PREFIX "[0123456789]" ); fputs("Done.\n", stdout); } diff --git a/src/test/test-sbat.c b/src/test/test-sbat.c index d8546b1ad9..cf9c155c9d 100644 --- a/src/test/test-sbat.c +++ b/src/test/test-sbat.c @@ -8,17 +8,26 @@ #include "sbat.h" #include "tests.h" -TEST(sbat_section_text) { - log_info("---SBAT-----------&<----------------------------------------\n" +TEST(BOOT_SBAT) { + log_info("---SBAT-----------&<-----------------------------------------\n" "%s" + "------------------>&-----------------------------------------", +#ifdef SBAT_DISTRO + SBAT_BOOT_SECTION_TEXT +#else + "(not defined)" +#endif + ); +} + +TEST(STUB_SBAT) { + log_info("---SBAT-----------&<-----------------------------------------\n" "%s" "------------------>&-----------------------------------------", #ifdef SBAT_DISTRO - SBAT_BOOT_SECTION_TEXT, SBAT_STUB_SECTION_TEXT #else - "(not defined)", - "" + "(not defined)" #endif ); } diff --git a/src/test/test-strip-tab-ansi.c b/src/test/test-strip-tab-ansi.c index 3ad0fcd90e..00bd7cdaf1 100644 --- a/src/test/test-strip-tab-ansi.c +++ b/src/test/test-strip-tab-ansi.c @@ -67,6 +67,15 @@ TEST(strip_tab_ansi) { assert_se(strip_tab_ansi(&q, NULL, NULL)); ASSERT_STREQ(q, qq); } + + /* Test that both kinds of ST are recognized after OSC */ + assert_se(p = strdup("before" ANSI_OSC "inside1" ANSI_ST + "between1" ANSI_OSC "inside2\a" + "between2" ANSI_OSC "inside3\x1b\x5c" + "after")); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + ASSERT_STREQ(p, "beforebetween1between2after"); + free(p); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index ad800a4111..38c9f7e34a 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -25,6 +25,10 @@ "in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " \ "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +TEST(colors_enabled) { + log_info("colors_enabled: %s", yes_no(colors_enabled())); +} + TEST(default_term_for_tty) { puts(default_term_for_tty("/dev/tty23")); puts(default_term_for_tty("/dev/ttyS23")); @@ -212,14 +216,13 @@ TEST(terminal_fix_size) { TEST(terminal_is_pty_fd) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; - _cleanup_free_ char *peer = NULL; int r; - fd1 = openpt_allocate(O_RDWR, &peer); + fd1 = openpt_allocate(O_RDWR, /* ret_peer_path= */ NULL); assert_se(fd1 >= 0); assert_se(terminal_is_pty_fd(fd1) > 0); - fd2 = open_terminal(peer, O_RDWR|O_CLOEXEC|O_NOCTTY); + fd2 = pty_open_peer(fd1, O_RDWR|O_CLOEXEC|O_NOCTTY); assert_se(fd2 >= 0); assert_se(terminal_is_pty_fd(fd2) > 0); @@ -291,4 +294,24 @@ TEST(terminal_reset_defensive) { log_notice_errno(r, "Failed to reset terminal: %m"); } +TEST(pty_open_peer) { + _cleanup_close_ int pty_fd = -EBADF, peer_fd = -EBADF; + _cleanup_free_ char *pty_path = NULL; + + pty_fd = openpt_allocate(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK, &pty_path); + assert(pty_fd >= 0); + assert(pty_path); + + peer_fd = pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC); + assert(peer_fd >= 0); + + static const char x[] = { 'x', '\n' }; + assert(write(pty_fd, x, sizeof(x)) == 2); + + char buf[3]; + assert(read(peer_fd, &buf, sizeof(buf)) == sizeof(x)); + assert(buf[0] == x[0]); + assert(buf[1] == x[1]); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-user-record.c b/src/test/test-user-record.c new file mode 100644 index 0000000000..3a7e8e28af --- /dev/null +++ b/src/test/test-user-record.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "json-util.h" +#include "macro.h" +#include "tests.h" +#include "user-record.h" + +#define USER(ret, ...) \ + ({ \ + typeof(ret) _r = (ret); \ + user_record_unref(*_r); \ + assert_se(user_record_build((ret), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) >= 0); \ + 0; \ + }) + +TEST(self_changes) { + _cleanup_(user_record_unrefp) UserRecord *curr = NULL, *new = NULL; + + /* not allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111)); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)); + assert_se(!user_record_self_changes_allowed(curr, new)); + + /* manually allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111), + SD_JSON_BUILD_PAIR_ARRAY("selfModifiableFields", SD_JSON_BUILD_STRING("notInHardCodedList"))); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_ARRAY("selfModifiableFields", SD_JSON_BUILD_STRING("notInHardCodedList")), + /* change in order shouldn't affect things */ + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)); + assert_se(user_record_self_changes_allowed(curr, new)); + + /* default allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("realName", "Old Name")); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("realName", "New Name")); + assert_se(user_record_self_changes_allowed(curr, new)); + + /* introduced new default allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test")); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_STRING("realName", "New Name")); + assert_se(user_record_self_changes_allowed(curr, new)); + + /* introduced new not allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test")); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999)); + assert_se(!user_record_self_changes_allowed(curr, new)); + + /* privileged section: default allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_OBJECT("privileged", + SD_JSON_BUILD_PAIR_STRING("passwordHint", "Old Hint"))); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_OBJECT("privileged", + SD_JSON_BUILD_PAIR_STRING("passwordHint", "New Hint"))); + assert_se(user_record_self_changes_allowed(curr, new)); + + /* privileged section: not allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_OBJECT("privileged", + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111))); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_OBJECT("privileged", + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999))); + assert_se(!user_record_self_changes_allowed(curr, new)); + + /* privileged section: manually allowlisted */ + USER(&curr, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_ARRAY("selfModifiablePrivileged", SD_JSON_BUILD_STRING("notInHardCodedList")), + SD_JSON_BUILD_PAIR_OBJECT("privileged", + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 11111))); + USER(&new, + SD_JSON_BUILD_PAIR_STRING("userName", "test"), + SD_JSON_BUILD_PAIR_ARRAY("selfModifiablePrivileged", SD_JSON_BUILD_STRING("notInHardCodedList")), + SD_JSON_BUILD_PAIR_OBJECT("privileged", + SD_JSON_BUILD_PAIR_UNSIGNED("notInHardCodedList", 99999))); + assert_se(user_record_self_changes_allowed(curr, new)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index cafe98871b..40d972b6b6 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -33,14 +33,20 @@ static int method_something(sd_varlink *link, sd_json_variant *parameters, sd_va int r; a = sd_json_variant_by_key(parameters, "a"); - if (!a) - return sd_varlink_error(link, "io.test.BadParameters", NULL); + if (!a) { + r = sd_varlink_error(link, "io.test.BadParameters", NULL); + assert_se(r == -EBADR); + return r; + } x = sd_json_variant_integer(a); b = sd_json_variant_by_key(parameters, "b"); - if (!b) - return sd_varlink_error(link, "io.test.BadParameters", NULL); + if (!b) { + r = sd_varlink_error(link, "io.test.BadParameters", NULL); + assert_se(r == -EBADR); + return r; + } y = sd_json_variant_integer(b); @@ -105,8 +111,11 @@ static int method_passfd(sd_varlink *link, sd_json_variant *parameters, sd_varli int r; a = sd_json_variant_by_key(parameters, "fd"); - if (!a) - return sd_varlink_error(link, "io.test.BadParameters", NULL); + if (!a) { + r = sd_varlink_error_invalid_parameter_name(link, "fd"); + assert_se(r == -EINVAL); + return r; + } ASSERT_STREQ(sd_json_variant_string(a), "whoop"); @@ -242,8 +251,7 @@ static void *thread(void *arg) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *i = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *wrong = NULL; sd_json_variant *o = NULL, *k = NULL, *j = NULL; - const char *error_id; - const char *e; + const char *error_id, *e; int x = 0; assert_se(sd_json_build(&i, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_INTEGER(88)), @@ -288,6 +296,7 @@ static void *thread(void *arg) { assert_se(sd_varlink_push_fd(c, fd3) == 2); assert_se(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("fd", SD_JSON_BUILD_STRING("whoop")))) >= 0); + assert_se(!e); int fd4 = sd_varlink_peek_fd(c, 0); int fd5 = sd_varlink_peek_fd(c, 1); @@ -298,6 +307,9 @@ static void *thread(void *arg) { test_fd(fd4, "miau", 4); test_fd(fd5, "wuff", 4); + assert_se(sd_varlink_callb(c, "io.test.PassFD", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("fdx", SD_JSON_BUILD_STRING("whoopx")))) >= 0); + ASSERT_TRUE(sd_varlink_error_is_invalid_parameter(e, o, "fd")); + assert_se(sd_varlink_callb(c, "io.test.IDontExist", &o, &e, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("x", SD_JSON_BUILD_REAL(5.5)))) >= 0); ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(o, "method")), "io.test.IDontExist"); ASSERT_STREQ(e, SD_VARLINK_ERROR_METHOD_NOT_FOUND); diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 0a1da3ed31..57f063b010 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -274,6 +274,8 @@ static int print_device_chain_in_json(sd_device *device) { assert(device); + arg_json_format_flags |=SD_JSON_FORMAT_SEQ; + r = print_all_attributes_in_json(device, /* is_parent = */ false); if (r < 0) return r; @@ -453,9 +455,7 @@ static int export_devices(sd_device_enumerator *e) { pager_open(arg_pager_flags); FOREACH_DEVICE_AND_SUBSYSTEM(e, d) - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) - (void) print_record(d, NULL); - else { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; r = record_to_json(d, &v); @@ -463,7 +463,8 @@ static int export_devices(sd_device_enumerator *e) { return r; (void) sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } + } else + (void) print_record(d, NULL); return 0; } @@ -629,9 +630,7 @@ static int query_device(QueryType query, sd_device* device) { return 0; case QUERY_ALL: - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) - return print_record(device, NULL); - else { + if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; r = record_to_json(device, &v); @@ -639,7 +638,8 @@ static int query_device(QueryType query, sd_device* device) { return r; (void) sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } + } else + return print_record(device, NULL); return 0; @@ -1229,10 +1229,10 @@ int info_main(int argc, char *argv[], void *userdata) { if (action == ACTION_QUERY) r = query_device(query, device); else if (action == ACTION_ATTRIBUTE_WALK) { - if (arg_json_format_flags & SD_JSON_FORMAT_OFF) - r = print_device_chain(device); - else + if (sd_json_format_enabled(arg_json_format_flags)) r = print_device_chain_in_json(device); + else + r = print_device_chain(device); } else if (action == ACTION_TREE) r = print_tree(device); else diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 7a8a53f7e8..e40843cf35 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -65,6 +65,8 @@ static int get_startup_monotonic_time(Context *c, usec_t *ret) { return 0; } +#define MAX_ATTEMPTS 64u + static int get_current_runlevel(Context *c) { static const struct { const int runlevel; @@ -84,12 +86,13 @@ static int get_current_runlevel(Context *c) { for (unsigned n_attempts = 0;;) { if (n_attempts++ > 0) { /* systemd might have dropped off momentarily, let's not make this an error, - * and wait some random time. Let's pick a random time in the range 0ms…250ms, + * and wait some random time. Let's pick a random time in the range 100ms…2000ms, * linearly scaled by the number of failed attempts. */ c->bus = sd_bus_flush_close_unref(c->bus); - usec_t usec = random_u64_range(UINT64_C(10) * USEC_PER_MSEC + - UINT64_C(240) * USEC_PER_MSEC * n_attempts/64); + usec_t usec = + UINT64_C(100) * USEC_PER_MSEC + + random_u64_range(UINT64_C(1900) * USEC_PER_MSEC * n_attempts / MAX_ATTEMPTS); (void) usleep_safe(usec); r = bus_connect_system_systemd(&c->bus); @@ -121,7 +124,7 @@ static int get_current_runlevel(Context *c) { sd_bus_error_has_names(&error, SD_BUS_ERROR_NO_REPLY, SD_BUS_ERROR_DISCONNECTED)) && - n_attempts < 64) { + n_attempts < MAX_ATTEMPTS) { log_debug_errno(r, "Failed to get state of %s, retrying after a slight delay: %s", e->special, bus_error_message(&error, r)); break; diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index d74251fd86..16c8f385d6 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1287,7 +1287,7 @@ static int parse_argv(int argc, char *argv[]) { if (r <= 0) return r; - arg_output = FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) ? _OUTPUT_INVALID : OUTPUT_JSON; + arg_output = sd_json_format_enabled(arg_json_format_flags) ? OUTPUT_JSON : _OUTPUT_INVALID; break; case 'j': diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index b6c6c0da42..76c9e4850a 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -287,7 +287,7 @@ static int verb_info(int argc, char *argv[], void *userdata) { pager_open(arg_pager_flags); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { static const sd_json_dispatch_field dispatch_table[] = { { "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetInfoData, vendor), SD_JSON_MANDATORY }, { "product", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetInfoData, product), SD_JSON_MANDATORY }, @@ -426,7 +426,7 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) || list_methods) { + if (!sd_json_format_enabled(arg_json_format_flags) || list_methods) { static const sd_json_dispatch_field dispatch_table[] = { { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, SD_JSON_MANDATORY }, {} @@ -478,7 +478,7 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { strv_sort_uniq(methods); - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + if (!sd_json_format_enabled(arg_json_format_flags)) strv_print(methods); else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; @@ -539,7 +539,7 @@ static int verb_call(int argc, char *argv[], void *userdata) { parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL; /* No JSON mode explicitly configured? Then default to the same as -j */ - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (!sd_json_format_enabled(arg_json_format_flags)) { arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; arg_json_format_flags |= SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; } |