diff options
Diffstat (limited to 'src')
54 files changed, 1881 insertions, 554 deletions
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index a9402fdb28..ac0470b20d 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -1362,7 +1362,7 @@ static void help(void) { " blame Print list of running units ordered by time to init\n" " critical-chain Print a tree of the time critical chain of units\n" " plot Output SVG graphic showing service initialization\n" - " dot Output dependency graph in dot(1) format\n" + " dot Output dependency graph in man:dot(1) format\n" " set-log-level LEVEL Set logging threshold for manager\n" " set-log-target TARGET Set logging target for manager\n" " dump Output state serialization of service manager\n" diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index b5780194df..b8f0f5d03d 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -137,7 +137,6 @@ int conf_files_list(char ***strv, const char *suffix, const char *root, const ch va_list ap; assert(strv); - assert(suffix); va_start(ap, dir); dirs = strv_new_ap(dir, ap); @@ -153,7 +152,6 @@ int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, c _cleanup_strv_free_ char **dirs = NULL; assert(strv); - assert(suffix); dirs = strv_split_nulstr(d); if (!dirs) diff --git a/src/basic/copy.c b/src/basic/copy.c index 6273ac9b47..e120b9eb4e 100644 --- a/src/basic/copy.c +++ b/src/basic/copy.c @@ -558,7 +558,7 @@ int copy_file_atomic(const char *from, const char *to, mode_t mode, unsigned cha } else r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); if (r < 0) { - (void) unlink_noerrno(t); + (void) unlink(t); return r; } diff --git a/src/basic/def.h b/src/basic/def.h index fbab066564..200ea973c1 100644 --- a/src/basic/def.h +++ b/src/basic/def.h @@ -75,18 +75,18 @@ #define NOTIFY_BUFFER_MAX PIPE_BUF #ifdef HAVE_SPLIT_USR -#define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" +# define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" #else -#define _CONF_PATHS_SPLIT_USR(n) +# define _CONF_PATHS_SPLIT_USR(n) #endif /* Return a nulstr for a standard cascade of configuration paths, * suitable to pass to conf_files_list_nulstr() or config_parse_many_nulstr() * to implement drop-in directories for extending configuration * files. */ -#define CONF_PATHS_NULSTR(n) \ - "/etc/" n "\0" \ - "/run/" n "\0" \ - "/usr/local/lib/" n "\0" \ - "/usr/lib/" n "\0" \ +#define CONF_PATHS_NULSTR(n) \ + "/etc/" n "\0" \ + "/run/" n "\0" \ + "/usr/local/lib/" n "\0" \ + "/usr/lib/" n "\0" \ _CONF_PATHS_SPLIT_USR(n) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 96da38d45e..1ec574e8a0 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -26,6 +26,7 @@ #include "alloc-util.h" #include "env-util.h" +#include "escape.h" #include "extract-word.h" #include "macro.h" #include "parse-util.h" @@ -247,7 +248,7 @@ fail: return NULL; } -_pure_ static bool env_match(const char *t, const char *pattern) { +static bool env_match(const char *t, const char *pattern) { assert(t); assert(pattern); @@ -273,6 +274,19 @@ _pure_ static bool env_match(const char *t, const char *pattern) { return false; } +static bool env_entry_has_name(const char *entry, const char *name) { + const char *t; + + assert(entry); + assert(name); + + t = startswith(entry, name); + if (!t) + return false; + + return *t == '='; +} + char **strv_env_delete(char **x, unsigned n_lists, ...) { size_t n, i = 0; char **k, **r; @@ -386,18 +400,24 @@ char **strv_env_unset_many(char **l, ...) { int strv_env_replace(char ***l, char *p) { char **f; + const char *t, *name; assert(p); /* Replace first occurrence of the env var or add a new one in the * string list. Drop other occurences. Edits in-place. Does not copy p. + * p must be a valid key=value assignment. */ + t = strchr(p, '='); + assert(t); + + name = strndupa(p, t - p); + for (f = *l; f && *f; f++) - if (env_match(*f, p)) { - free(*f); - *f = p; - strv_env_unset(f + 1, p); + if (env_entry_has_name(*f, name)) { + free_and_replace(*f, p); + strv_env_unset(f + 1, *f); return 0; } @@ -434,7 +454,7 @@ fail: return NULL; } -char *strv_env_get_n(char **l, const char *name, size_t k) { +char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) { char **i; assert(name); @@ -442,18 +462,25 @@ char *strv_env_get_n(char **l, const char *name, size_t k) { if (k <= 0) return NULL; - STRV_FOREACH(i, l) + STRV_FOREACH_BACKWARDS(i, l) if (strneq(*i, name, k) && (*i)[k] == '=') return *i + k + 1; + if (flags & REPLACE_ENV_USE_ENVIRONMENT) { + const char *t; + + t = strndupa(name, k); + return getenv(t); + }; + return NULL; } char *strv_env_get(char **l, const char *name) { assert(name); - return strv_env_get_n(l, name, strlen(name)); + return strv_env_get_n(l, name, strlen(name), 0); } char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { @@ -492,19 +519,26 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha return e; } -char *replace_env(const char *format, char **env) { +char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { enum { WORD, CURLY, - VARIABLE + VARIABLE, + VARIABLE_RAW, + TEST, + DEFAULT_VALUE, + ALTERNATE_VALUE, } state = WORD; - const char *e, *word = format; - char *r = NULL, *k; + const char *e, *word = format, *test_value; + char *k; + _cleanup_free_ char *r = NULL; + size_t i, len; + int nest = 0; assert(format); - for (e = format; *e; e ++) { + for (e = format, i = 0; *e && i < n; e ++, i ++) { switch (state) { @@ -517,24 +551,36 @@ char *replace_env(const char *format, char **env) { if (*e == '{') { k = strnappend(r, word, e-word-1); if (!k) - goto fail; + return NULL; free(r); r = k; word = e-1; state = VARIABLE; - + nest++; } else if (*e == '$') { k = strnappend(r, word, e-word); if (!k) - goto fail; + return NULL; free(r); r = k; word = e+1; state = WORD; + + } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) { + k = strnappend(r, word, e-word-1); + if (!k) + return NULL; + + free(r); + r = k; + + word = e-1; + state = VARIABLE_RAW; + } else state = WORD; break; @@ -543,31 +589,109 @@ char *replace_env(const char *format, char **env) { if (*e == '}') { const char *t; - t = strempty(strv_env_get_n(env, word+2, e-word-2)); + t = strv_env_get_n(env, word+2, e-word-2, flags); k = strappend(r, t); if (!k) - goto fail; + return NULL; free(r); r = k; word = e+1; state = WORD; + } else if (*e == ':') { + if (!(flags & REPLACE_ENV_ALLOW_EXTENDED)) + /* Treat this as unsupported syntax, i.e. do no replacement */ + state = WORD; + else { + len = e-word-2; + state = TEST; + } + } + break; + + case TEST: + if (*e == '-') + state = DEFAULT_VALUE; + else if (*e == '+') + state = ALTERNATE_VALUE; + else { + state = WORD; + break; + } + + test_value = e+1; + break; + + case DEFAULT_VALUE: /* fall through */ + case ALTERNATE_VALUE: + assert(flags & REPLACE_ENV_ALLOW_EXTENDED); + + if (*e == '{') { + nest++; + break; + } + + if (*e != '}') + break; + + nest--; + if (nest == 0) { + const char *t; + _cleanup_free_ char *v = NULL; + + t = strv_env_get_n(env, word+2, len, flags); + + if (t && state == ALTERNATE_VALUE) + t = v = replace_env_n(test_value, e-test_value, env, flags); + else if (!t && state == DEFAULT_VALUE) + t = v = replace_env_n(test_value, e-test_value, env, flags); + + k = strappend(r, t); + if (!k) + return NULL; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + + case VARIABLE_RAW: + assert(flags & REPLACE_ENV_ALLOW_BRACELESS); + + if (!strchr(VALID_CHARS_ENV_NAME, *e)) { + const char *t; + + t = strv_env_get_n(env, word+1, e-word-1, flags); + + k = strappend(r, t); + if (!k) + return NULL; + + free(r); + r = k; + + word = e--; + i--; + state = WORD; } break; } } - k = strnappend(r, word, e-word); - if (!k) - goto fail; + if (state == VARIABLE_RAW) { + const char *t; - free(r); - return k; + assert(flags & REPLACE_ENV_ALLOW_BRACELESS); -fail: - return mfree(r); + t = strv_env_get_n(env, word+1, e-word-1, flags); + return strappend(r, t); + } else + return strnappend(r, word, e-word); } char **replace_env_argv(char **argv, char **env) { @@ -623,7 +747,7 @@ char **replace_env_argv(char **argv, char **env) { } /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env); + ret[k] = replace_env(*i, env, 0); if (!ret[k]) { strv_free(ret); return NULL; @@ -644,3 +768,39 @@ int getenv_bool(const char *p) { return parse_boolean(e); } + +int serialize_environment(FILE *f, char **environment) { + char **e; + + STRV_FOREACH(e, environment) { + _cleanup_free_ char *ce; + + ce = cescape(*e); + if (!ce) + return -ENOMEM; + + fprintf(f, "env=%s\n", *e); + } + + /* caller should call ferror() */ + + return 0; +} + +int deserialize_environment(char ***environment, const char *line) { + char *uce = NULL; + int r; + + assert(line); + assert(environment); + + assert(startswith(line, "env=")); + r = cunescape(line + 4, UNESCAPE_RELAX, &uce); + if (r < 0) + return r; + + if (!env_assignment_is_valid(uce)) + return -EINVAL; + + return strv_env_replace(environment, uce); +} diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 8cb0fc2131..e88fa6aac0 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -21,6 +21,7 @@ #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include "macro.h" @@ -28,9 +29,19 @@ bool env_name_is_valid(const char *e); bool env_value_is_valid(const char *e); bool env_assignment_is_valid(const char *e); -char *replace_env(const char *format, char **env); +enum { + REPLACE_ENV_USE_ENVIRONMENT = 1u, + REPLACE_ENV_ALLOW_BRACELESS = 2u, + REPLACE_ENV_ALLOW_EXTENDED = 4u, +}; + +char *replace_env_n(const char *format, size_t n, char **env, unsigned flags); char **replace_env_argv(char **argv, char **env); +static inline char *replace_env(const char *format, char **env, unsigned flags) { + return replace_env_n(format, strlen(format), env, flags); +} + 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); @@ -46,7 +57,10 @@ char **strv_env_unset(char **l, const char *p); /* In place ... */ char **strv_env_unset_many(char **l, ...) _sentinel_; int strv_env_replace(char ***l, char *p); /* In place ... */ -char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; +char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; char *strv_env_get(char **x, const char *n) _pure_; int getenv_bool(const char *p); + +int serialize_environment(FILE *f, char **environment); +int deserialize_environment(char ***environment, const char *line); diff --git a/src/basic/exec-util.c b/src/basic/exec-util.c new file mode 100644 index 0000000000..aced9e8e3d --- /dev/null +++ b/src/basic/exec-util.c @@ -0,0 +1,360 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <dirent.h> +#include <errno.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "env-util.h" +#include "exec-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "macro.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +/* Put this test here for a lack of better place */ +assert_cc(EAGAIN == EWOULDBLOCK); + +static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid) { + + pid_t _pid; + + if (null_or_empty_path(path)) { + log_debug("%s is empty (a mask).", path); + return 0; + } + + _pid = fork(); + if (_pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + if (_pid == 0) { + char *_argv[2]; + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + if (stdout_fd >= 0) { + /* If the fd happens to be in the right place, go along with that */ + if (stdout_fd != STDOUT_FILENO && + dup2(stdout_fd, STDOUT_FILENO) < 0) + return -errno; + + fd_cloexec(STDOUT_FILENO, false); + } + + if (!argv) { + _argv[0] = (char*) path; + _argv[1] = NULL; + argv = _argv; + } else + argv[0] = (char*) path; + + execv(path, argv); + log_error_errno(errno, "Failed to execute %s: %m", path); + _exit(EXIT_FAILURE); + } + + log_debug("Spawned %s as " PID_FMT ".", path, _pid); + *pid = _pid; + return 1; +} + +static int do_execute( + char **directories, + usec_t timeout, + gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX], + void* const callback_args[_STDOUT_CONSUME_MAX], + int output_fd, + char *argv[]) { + + _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_strv_free_ char **paths = NULL; + char **path; + int r; + + /* We fork this all off from a child process so that we can somewhat cleanly make + * use of SIGALRM to set a time limit. + * + * If callbacks is nonnull, execution is serial. Otherwise, we default to parallel. + */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + r = conf_files_list_strv(&paths, NULL, NULL, (const char* const*) directories); + if (r < 0) + return r; + + if (!callbacks) { + pids = hashmap_new(NULL); + if (!pids) + return log_oom(); + } + + /* Abort execution of this process after the timout. We simply rely on SIGALRM as + * default action terminating the process, and turn on alarm(). */ + + if (timeout != USEC_INFINITY) + alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + + STRV_FOREACH(path, paths) { + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = -1; + pid_t pid; + + t = strdup(*path); + if (!t) + return log_oom(); + + if (callbacks) { + fd = open_serialization_fd(basename(*path)); + if (fd < 0) + return log_error_errno(fd, "Failed to open serialization file: %m"); + } + + r = do_spawn(t, argv, fd, &pid); + if (r <= 0) + continue; + + if (pids) { + r = hashmap_put(pids, PID_TO_PTR(pid), t); + if (r < 0) + return log_oom(); + t = NULL; + } else { + r = wait_for_terminate_and_warn(t, pid, true); + if (r < 0) + continue; + + if (lseek(fd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek on serialization fd: %m"); + + r = callbacks[STDOUT_GENERATE](fd, callback_args[STDOUT_GENERATE]); + fd = -1; + if (r < 0) + return log_error_errno(r, "Failed to process output from %s: %m", *path); + } + } + + if (callbacks) { + r = callbacks[STDOUT_COLLECT](output_fd, callback_args[STDOUT_COLLECT]); + if (r < 0) + return log_error_errno(r, "Callback two failed: %m"); + } + + while (!hashmap_isempty(pids)) { + _cleanup_free_ char *t = NULL; + pid_t pid; + + pid = PTR_TO_PID(hashmap_first_key(pids)); + assert(pid > 0); + + t = hashmap_remove(pids, PID_TO_PTR(pid)); + assert(t); + + wait_for_terminate_and_warn(t, pid, true); + } + + return 0; +} + +int execute_directories( + const char* const* directories, + usec_t timeout, + gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX], + void* const callback_args[_STDOUT_CONSUME_MAX], + char *argv[]) { + + pid_t executor_pid; + char *name; + char **dirs = (char**) directories; + _cleanup_close_ int fd = -1; + int r; + + assert(!strv_isempty(dirs)); + + name = basename(dirs[0]); + assert(!isempty(name)); + + if (callbacks) { + assert(callback_args); + assert(callbacks[STDOUT_GENERATE]); + assert(callbacks[STDOUT_COLLECT]); + assert(callbacks[STDOUT_CONSUME]); + + fd = open_serialization_fd(name); + if (fd < 0) + return log_error_errno(fd, "Failed to open serialization file: %m"); + } + + /* Executes all binaries in the directories serially or in parallel and waits for + * them to finish. Optionally a timeout is applied. If a file with the same name + * exists in more than one directory, the earliest one wins. */ + + executor_pid = fork(); + if (executor_pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (executor_pid == 0) { + r = do_execute(dirs, timeout, callbacks, callback_args, fd, argv); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + r = wait_for_terminate_and_warn(name, executor_pid, true); + if (r < 0) + return log_error_errno(r, "Execution failed: %m"); + if (r > 0) { + /* non-zero return code from child */ + log_error("Forker process failed."); + return -EREMOTEIO; + } + + if (!callbacks) + return 0; + + if (lseek(fd, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to rewind serialization fd: %m"); + + r = callbacks[STDOUT_CONSUME](fd, callback_args[STDOUT_CONSUME]); + fd = -1; + if (r < 0) + return log_error_errno(r, "Failed to parse returned data: %m"); + return 0; +} + +static int gather_environment_generate(int fd, void *arg) { + char ***env = arg, **x, **y; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **new; + int r; + + /* Read a series of VAR=value assignments from fd, use them to update the list of + * variables in env. Also update the exported environment. + * + * fd is always consumed, even on error. + */ + + assert(env); + + f = fdopen(fd, "r"); + if (!f) { + safe_close(fd); + return -errno; + } + + r = load_env_file_pairs(f, NULL, NULL, &new); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(x, y, new) { + char *p; + + if (!env_name_is_valid(*x)) { + log_warning("Invalid variable assignment \"%s=...\", ignoring.", *x); + continue; + } + + p = strjoin(*x, "=", *y); + if (!p) + return -ENOMEM; + + r = strv_env_replace(env, p); + if (r < 0) + return r; + + if (setenv(*x, *y, true) < 0) + return -errno; + } + + return r; +} + +static int gather_environment_collect(int fd, void *arg) { + char ***env = arg; + _cleanup_fclose_ FILE *f = NULL; + int r; + + /* Write out a series of env=cescape(VAR=value) assignments to fd. */ + + assert(env); + + f = fdopen(fd, "w"); + if (!f) { + safe_close(fd); + return -errno; + } + + r = serialize_environment(f, *env); + if (r < 0) + return r; + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +static int gather_environment_consume(int fd, void *arg) { + char ***env = arg; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + int r = 0, k; + + /* Read a series of env=cescape(VAR=value) assignments from fd into env. */ + + assert(env); + + f = fdopen(fd, "r"); + if (!f) { + safe_close(fd); + return -errno; + } + + FOREACH_LINE(line, f, return -EIO) { + truncate_nl(line); + + k = deserialize_environment(env, line); + if (k < 0) + log_error_errno(k, "Invalid line \"%s\": %m", line); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +const gather_stdout_callback_t gather_environment[] = { + gather_environment_generate, + gather_environment_collect, + gather_environment_consume, +}; diff --git a/src/basic/exec-util.h b/src/basic/exec-util.h new file mode 100644 index 0000000000..72009799b2 --- /dev/null +++ b/src/basic/exec-util.h @@ -0,0 +1,40 @@ +/*** + This file is part of systemd. + + Copyright 2017 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdbool.h> + +#include "time-util.h" + +typedef int (*gather_stdout_callback_t) (int fd, void *arg); + +enum { + STDOUT_GENERATE, /* from generators to helper process */ + STDOUT_COLLECT, /* from helper process to main process */ + STDOUT_CONSUME, /* process data in main process */ + _STDOUT_CONSUME_MAX, +}; + +int execute_directories( + const char* const* directories, + usec_t timeout, + gather_stdout_callback_t const callbacks[_STDOUT_CONSUME_MAX], + void* const callback_args[_STDOUT_CONSUME_MAX], + char *argv[]); + +extern const gather_stdout_callback_t gather_environment[_STDOUT_CONSUME_MAX]; diff --git a/src/basic/fileio.c b/src/basic/fileio.c index c43b0583a4..7c2c2b38f5 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -30,6 +30,7 @@ #include "alloc-util.h" #include "ctype.h" +#include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -553,13 +554,14 @@ static int parse_env_file_internal( } } - if (state == PRE_VALUE || - state == VALUE || - state == VALUE_ESCAPE || - state == SINGLE_QUOTE_VALUE || - state == SINGLE_QUOTE_VALUE_ESCAPE || - state == DOUBLE_QUOTE_VALUE || - state == DOUBLE_QUOTE_VALUE_ESCAPE) { + if (IN_SET(state, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + SINGLE_QUOTE_VALUE_ESCAPE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE)) { key[n_key] = 0; @@ -586,14 +588,9 @@ fail: return r; } -static int parse_env_file_push( +static int check_utf8ness_and_warn( const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - - const char *k; - va_list aq, *ap = userdata; + const char *key, char *value) { if (!utf8_is_valid(key)) { _cleanup_free_ char *p = NULL; @@ -611,6 +608,23 @@ static int parse_env_file_push( return -EINVAL; } + return 0; +} + +static int parse_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + const char *k; + va_list aq, *ap = userdata; + int r; + + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; + va_copy(aq, *ap); while ((k = va_arg(aq, const char *))) { @@ -662,27 +676,19 @@ static int load_env_file_push( char *p; int r; - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; - p = strjoin(key, "=", strempty(value)); + p = strjoin(key, "=", value); if (!p) return -ENOMEM; - r = strv_consume(m, p); - if (r < 0) + r = strv_env_replace(m, p); + if (r < 0) { + free(p); return r; + } if (n_pushed) (*n_pushed)++; @@ -716,19 +722,9 @@ static int load_env_file_push_pairs( char ***m = userdata; int r; - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; r = strv_extend(m, key); if (r < 0) @@ -767,6 +763,52 @@ int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ** return 0; } +static int merge_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + char ***env = userdata; + char *expanded_value; + + assert(env); + + if (!value) { + log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key); + return 0; + } + + if (!env_name_is_valid(key)) { + log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key); + free(value); + return 0; + } + + expanded_value = replace_env(value, *env, + REPLACE_ENV_USE_ENVIRONMENT| + REPLACE_ENV_ALLOW_BRACELESS| + REPLACE_ENV_ALLOW_EXTENDED); + if (!expanded_value) + return -ENOMEM; + + free_and_replace(value, expanded_value); + + return load_env_file_push(filename, line, key, value, env, n_pushed); +} + +int merge_env_file( + char ***env, + FILE *f, + const char *fname) { + + /* NOTE: this function supports braceful and braceless variable expansions, + * plus "extended" substitutions, unlike other exported parsing functions. + */ + + return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL); +} + static void write_env_var(FILE *f, const char *v) { const char *p; @@ -1342,6 +1384,25 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { return fd; } +int open_serialization_fd(const char *ident) { + int fd = -1; + + fd = memfd_create(ident, MFD_CLOEXEC); + if (fd < 0) { + const char *path; + + path = getpid() == 1 ? "/run/systemd" : "/tmp"; + fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC); + if (fd < 0) + return fd; + + log_debug("Serializing %s to %s.", ident, path); + } else + log_debug("Serializing %s to memfd.", ident); + + return fd; +} + int link_tmpfile(int fd, const char *path, const char *target) { assert(fd >= 0); diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 17b38a5d60..e547614cc4 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -48,6 +48,8 @@ int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); +int merge_env_file(char ***env, FILE *f, const char *fname); + int write_env_file(const char *fname, char **l); int executable_is_script(const char *path, char **interpreter); @@ -84,6 +86,7 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) int open_tmpfile_unlinkable(const char *directory, int flags); int open_tmpfile_linkable(const char *target, int flags, char **ret_path); +int open_serialization_fd(const char *ident); int link_tmpfile(int fd, const char *path, const char *target); diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 5fe5c71ff0..094acf1799 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -91,3 +91,9 @@ static inline void rmdir_and_free(char *p) { free(p); } DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free); + +static inline void unlink_and_free(char *p) { + (void) unlink(p); + free(p); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); diff --git a/src/basic/log.c b/src/basic/log.c index e6d2d61d72..36efc9ac7d 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -72,6 +72,7 @@ static bool show_color = false; static bool show_location = false; static bool upgrade_syslog_to_journal = false; +static bool always_reopen_console = false; /* Akin to glibc's __abort_msg; which is private and we hence cannot * use here. */ @@ -95,7 +96,7 @@ static int log_open_console(void) { if (console_fd >= 0) return 0; - if (getpid() == 1) { + if (always_reopen_console) { console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); if (console_fd < 0) return console_fd; @@ -1171,3 +1172,7 @@ int log_syntax_internal( unit_fmt, unit, NULL); } + +void log_set_always_reopen_console(bool b) { + always_reopen_console = b; +} diff --git a/src/basic/log.h b/src/basic/log.h index 9cacbb6b70..72714e02e5 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -220,6 +220,7 @@ LogTarget log_target_from_string(const char *s) _pure_; void log_received_signal(int level, const struct signalfd_siginfo *si); void log_set_upgrade_syslog_to_journal(bool b); +void log_set_always_reopen_console(bool b); int log_syntax_internal( const char *unit, diff --git a/src/basic/process-util.c b/src/basic/process-util.c index eead8b00da..87e5586027 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -703,7 +703,7 @@ int kill_and_sigcont(pid_t pid, int sig) { /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't * affected by a process being suspended anyway. */ - if (r >= 0 && !IN_SET(SIGCONT, SIGKILL)) + if (r >= 0 && !IN_SET(sig, SIGCONT, SIGKILL)) (void) kill(pid, SIGCONT); return r; diff --git a/src/basic/util.c b/src/basic/util.c index 6204906f37..3dce0ea92e 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -59,9 +59,6 @@ #include "user-util.h" #include "util.h" -/* Put this test here for a lack of better place */ -assert_cc(EAGAIN == EWOULDBLOCK); - int saved_argc = 0; char **saved_argv = NULL; static int saved_in_initrd = -1; @@ -80,146 +77,6 @@ size_t page_size(void) { return pgsz; } -static int do_execute(char **directories, usec_t timeout, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_set_free_free_ Set *seen = NULL; - char **directory; - - /* We fork this all off from a child process so that we can - * somewhat cleanly make use of SIGALRM to set a time limit */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pids = hashmap_new(NULL); - if (!pids) - return log_oom(); - - seen = set_new(&string_hash_ops); - if (!seen) - return log_oom(); - - STRV_FOREACH(directory, directories) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - - d = opendir(*directory); - if (!d) { - if (errno == ENOENT) - continue; - - return log_error_errno(errno, "Failed to open directory %s: %m", *directory); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_free_ char *path = NULL; - pid_t pid; - int r; - - if (!dirent_is_file(de)) - continue; - - if (set_contains(seen, de->d_name)) { - log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); - continue; - } - - r = set_put_strdup(seen, de->d_name); - if (r < 0) - return log_oom(); - - path = strjoin(*directory, "/", de->d_name); - if (!path) - return log_oom(); - - if (null_or_empty_path(path)) { - log_debug("%s is empty (a mask).", path); - continue; - } - - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - continue; - } else if (pid == 0) { - char *_argv[2]; - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - if (!argv) { - _argv[0] = path; - _argv[1] = NULL; - argv = _argv; - } else - argv[0] = path; - - execv(path, argv); - return log_error_errno(errno, "Failed to execute %s: %m", path); - } - - log_debug("Spawned %s as " PID_FMT ".", path, pid); - - r = hashmap_put(pids, PID_TO_PTR(pid), path); - if (r < 0) - return log_oom(); - path = NULL; - } - } - - /* Abort execution of this process after the timout. We simply - * rely on SIGALRM as default action terminating the process, - * and turn on alarm(). */ - - if (timeout != USEC_INFINITY) - alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - - while (!hashmap_isempty(pids)) { - _cleanup_free_ char *path = NULL; - pid_t pid; - - pid = PTR_TO_PID(hashmap_first_key(pids)); - assert(pid > 0); - - path = hashmap_remove(pids, PID_TO_PTR(pid)); - assert(path); - - wait_for_terminate_and_warn(path, pid, true); - } - - return 0; -} - -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) { - pid_t executor_pid; - int r; - char *name; - char **dirs = (char**) directories; - - assert(!strv_isempty(dirs)); - - name = basename(dirs[0]); - assert(!isempty(name)); - - /* Executes all binaries in the directories in parallel and waits - * for them to finish. Optionally a timeout is applied. If a file - * with the same name exists in more than one directory, the - * earliest one wins. */ - - executor_pid = fork(); - if (executor_pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - return; - - } else if (executor_pid == 0) { - r = do_execute(dirs, timeout, argv); - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - wait_for_terminate_and_warn(name, executor_pid, true); -} - bool plymouth_running(void) { return access("/run/plymouth/pid", F_OK) >= 0; } diff --git a/src/basic/util.h b/src/basic/util.h index c3802a811c..c7da6c39bf 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -65,8 +65,6 @@ static inline const char* enable_disable(bool b) { return b ? "enable" : "disable"; } -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); - bool plymouth_running(void); bool display_is_local(const char *display) _pure_; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index b747a95133..116608bbd3 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -38,20 +38,22 @@ #include "alloc-util.h" #include "blkid-util.h" +#include "copy.h" #include "dirent-util.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "locale-util.h" #include "parse-util.h" #include "rm-rf.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "umask-util.h" #include "util.h" #include "verbs.h" #include "virt.h" -#include "stat-util.h" static char *arg_path = NULL; static bool arg_touch_variables = true; @@ -476,16 +478,16 @@ static int compare_version(const char *a, const char *b) { return strverscmp(a, b); } -static int version_check(int fd, const char *from, const char *to) { +static int version_check(int fd_from, const char *from, int fd_to, const char *to) { _cleanup_free_ char *a = NULL, *b = NULL; - _cleanup_close_ int fd2 = -1; int r; - assert(fd >= 0); + assert(fd_from >= 0); assert(from); + assert(fd_to >= 0); assert(to); - r = get_file_version(fd, &a); + r = get_file_version(fd_from, &a); if (r < 0) return r; if (r == 0) { @@ -493,15 +495,7 @@ static int version_check(int fd, const char *from, const char *to) { return -EINVAL; } - fd2 = open(to, O_RDONLY|O_CLOEXEC); - if (fd2 < 0) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to); - } - - r = get_file_version(fd2, &b); + r = get_file_version(fd_to, &b); if (r < 0) return r; if (r == 0 || compare_product(a, b) != 0) { @@ -517,90 +511,59 @@ static int version_check(int fd, const char *from, const char *to) { return 0; } -static int copy_file(const char *from, const char *to, bool force) { - _cleanup_fclose_ FILE *f = NULL, *g = NULL; - char *p; +static int copy_file_with_version_check(const char *from, const char *to, bool force) { + _cleanup_close_ int fd_from = -1, fd_to = -1; + _cleanup_free_ char *t = NULL; int r; - struct timespec t[2]; - struct stat st; - assert(from); - assert(to); - - f = fopen(from, "re"); - if (!f) + fd_from = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd_from < 0) return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from); if (!force) { - /* If this is an update, then let's compare versions first */ - r = version_check(fileno(f), from, to); - if (r < 0) - return r; - } - - p = strjoina(to, "~"); - g = fopen(p, "wxe"); - if (!g) { - /* Directory doesn't exist yet? Then let's skip this... */ - if (!force && errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", to); - } + fd_to = open(to, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd_to < 0) { + if (errno != -ENOENT) + return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to); + } else { + r = version_check(fd_from, from, fd_to, to); + if (r < 0) + return r; - rewind(f); - do { - size_t k; - uint8_t buf[32*1024]; + if (lseek(fd_from, 0, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek in \%s\": %m", from); - k = fread(buf, 1, sizeof(buf), f); - if (ferror(f)) { - r = log_error_errno(EIO, "Failed to read \"%s\": %m", from); - goto error; + fd_to = safe_close(fd_to); } + } - if (k == 0) - break; - - fwrite(buf, 1, k, g); - if (ferror(g)) { - r = log_error_errno(EIO, "Failed to write \"%s\": %m", to); - goto error; - } - } while (!feof(f)); + r = tempfn_random(to, NULL, &t); + if (r < 0) + return log_oom(); - r = fflush_and_check(g); - if (r < 0) { - log_error_errno(r, "Failed to write \"%s\": %m", to); - goto error; + RUN_WITH_UMASK(0000) { + fd_to = open(t, O_WRONLY|O_CREAT|O_CLOEXEC|O_EXCL|O_NOFOLLOW, 0644); + if (fd_to < 0) + return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", t); } - r = fstat(fileno(f), &st); + r = copy_bytes(fd_from, fd_to, (uint64_t) -1, COPY_REFLINK); if (r < 0) { - r = log_error_errno(errno, "Failed to get file timestamps of \"%s\": %m", from); - goto error; + unlink(t); + return log_error_errno(errno, "Failed to copy data from \"%s\" to \"%s\": %m", from, t); } - t[0] = st.st_atim; - t[1] = st.st_mtim; + (void) copy_times(fd_from, fd_to); - r = futimens(fileno(g), t); + r = renameat(AT_FDCWD, t, AT_FDCWD, to); if (r < 0) { - r = log_error_errno(errno, "Failed to set file timestamps on \"%s\": %m", p); - goto error; - } - - if (rename(p, to) < 0) { - r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", p, to); - goto error; + (void) unlink_noerrno(t); + return log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", t, to); } log_info("Copied \"%s\" to \"%s\".", from, to); - return 0; -error: - (void) unlink(p); - return r; + return 0; } static int mkdir_one(const char *prefix, const char *suffix) { @@ -644,7 +607,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { p = strjoina(BOOTLIBDIR "/", name); q = strjoina(esp_path, "/EFI/systemd/", name); - r = copy_file(p, q, force); + r = copy_file_with_version_check(p, q, force); if (startswith(name, "systemd-boot")) { int k; @@ -654,7 +617,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { v = strjoina(esp_path, "/EFI/BOOT/BOOT", name + strlen("systemd-boot")); ascii_strupper(strrchr(v, '/') + 1); - k = copy_file(p, v, force); + k = copy_file_with_version_check(p, v, force); if (k < 0 && r == 0) r = k; } @@ -950,20 +913,31 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { static int install_loader_config(const char *esp_path) { - _cleanup_fclose_ FILE *f = NULL; char machine_string[SD_ID128_STRING_MAX]; + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; sd_id128_t machine_id; const char *p; - int r; + int r, fd; r = sd_id128_get_machine(&machine_id); if (r < 0) return log_error_errno(r, "Failed to get machine did: %m"); p = strjoina(esp_path, "/loader/loader.conf"); - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to open loader.conf for writing: %m"); + + if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ + return 0; + + fd = open_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t); + if (fd < 0) + return log_error_errno(fd, "Failed to open \"%s\" for writing: %m", p); + + f = fdopen(fd, "we"); + if (!f) { + safe_close(fd); + return log_oom(); + } fprintf(f, "#timeout 3\n"); fprintf(f, "default %s-*\n", sd_id128_to_string(machine_id, machine_string)); @@ -972,7 +946,15 @@ static int install_loader_config(const char *esp_path) { if (r < 0) return log_error_errno(r, "Failed to write \"%s\": %m", p); - return 0; + r = link_tmpfile(fd, t, p); + if (r == -EEXIST) + return 0; /* Silently skip creation if the file exists now (recheck) */ + if (r < 0) + return log_error_errno(r, "Failed to move \"%s\" into place: %m", p); + + t = mfree(t); + + return 1; } static int help(int argc, char *argv[], void *userdata) { diff --git a/src/core/execute.c b/src/core/execute.c index f455afa962..d7798387c5 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1144,11 +1144,13 @@ static int setup_pam( /* Tell the parent that our setup is done. This is especially * important regarding dropping privileges. Otherwise, unit - * setup might race against our setresuid(2) call. */ - barrier_place(&barrier); + * setup might race against our setresuid(2) call. + * + * If the parent aborted, we'll detect this below, hence ignore + * return failure here. */ + (void) barrier_place(&barrier); - /* Check if our parent process might already have - * died? */ + /* Check if our parent process might already have died? */ if (getppid() == parent_pid) { sigset_t ss; diff --git a/src/core/killall.c b/src/core/killall.c index 7a9df546ee..3fe9fa2ed0 100644 --- a/src/core/killall.c +++ b/src/core/killall.c @@ -213,7 +213,8 @@ static int killall(int sig, Set *pids, bool send_sighup) { if (get_ctty_devnr(pid, NULL) >= 0) - kill(pid, SIGHUP); + /* it's OK if the process is gone, just ignore the result */ + (void) kill(pid, SIGHUP); } } diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in index 8d7ce1c238..a2a7edd1ee 100644 --- a/src/core/macros.systemd.in +++ b/src/core/macros.systemd.in @@ -31,6 +31,8 @@ %_binfmtdir @binfmtdir@ %_systemdgeneratordir @systemgeneratordir@ %_systemdusergeneratordir @usergeneratordir@ +%_systemd_system_env_generator_dir @systemenvgeneratordir@ +%_systemd_user_env_generator_dir @userenvgeneratordir@ %systemd_requires \ Requires(post): systemd \ diff --git a/src/core/main.c b/src/core/main.c index ad2ce1330e..bcf9ea5f25 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1414,10 +1414,17 @@ int main(int argc, char *argv[]) { log_set_upgrade_syslog_to_journal(true); - /* Disable the umask logic */ - if (getpid() == 1) + if (getpid() == 1) { + /* Disable the umask logic */ umask(0); + /* Always reopen /dev/console when running as PID 1 or one of its pre-execve() children. This is + * important so that we never end up logging to any foreign stderr, for example if we have to log in a + * child process right before execve()'ing the actual binary, at a point in time where socket + * activation stderr/stdout area already set up. */ + log_set_always_reopen_console(true); + } + if (getpid() == 1 && detect_container() <= 0) { /* Running outside of a container as PID 1 */ @@ -1830,8 +1837,10 @@ int main(int argc, char *argv[]) { before_startup = now(CLOCK_MONOTONIC); r = manager_startup(m, arg_serialization, fds); - if (r < 0) + if (r < 0) { log_error_errno(r, "Failed to fully start up daemon: %m"); + goto finish; + } /* This will close all file descriptors that were opened, but * not claimed by any unit. */ diff --git a/src/core/manager.c b/src/core/manager.c index b2cd352a0c..ea80585329 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -52,6 +52,7 @@ #include "dirent-util.h" #include "env-util.h" #include "escape.h" +#include "exec-util.h" #include "exit-status.h" #include "fd-util.h" #include "fileio.h" @@ -102,6 +103,7 @@ static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32 static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); +static int manager_run_environment_generators(Manager *m); static int manager_run_generators(Manager *m); static void manager_watch_jobs_in_progress(Manager *m) { @@ -530,9 +532,9 @@ static int manager_default_environment(Manager *m) { if (MANAGER_IS_SYSTEM(m)) { /* The system manager always starts with a clean * environment for its children. It does not import - * the kernel or the parents exported variables. + * the kernel's or the parents' exported variables. * - * The initial passed environ is untouched to keep + * The initial passed environment is untouched to keep * /proc/self/environ valid; it is used for tagging * the init process inside containers. */ m->environment = strv_new("PATH=" DEFAULT_PATH, @@ -540,11 +542,10 @@ static int manager_default_environment(Manager *m) { /* Import locale variables LC_*= from configuration */ locale_setup(&m->environment); - } else { + } else /* The user manager passes its own environment * along to its children. */ m->environment = strv_copy(environ); - } if (!m->environment) return -ENOMEM; @@ -1262,6 +1263,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { if (r < 0) return r; + r = manager_run_environment_generators(m); + if (r < 0) + return r; + /* Make sure the transient directory always exists, so that it remains in the search path */ if (!m->test_run) { r = mkdir_p_label(m->lookup_paths.transient, 0755); @@ -2437,22 +2442,14 @@ void manager_send_unit_plymouth(Manager *m, Unit *u) { } int manager_open_serialization(Manager *m, FILE **_f) { - int fd = -1; + int fd; FILE *f; assert(_f); - fd = memfd_create("systemd-serialization", MFD_CLOEXEC); - if (fd < 0) { - const char *path; - - path = MANAGER_IS_SYSTEM(m) ? "/run/systemd" : "/tmp"; - fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC); - if (fd < 0) - return -errno; - log_debug("Serializing state to %s.", path); - } else - log_debug("Serializing state to memfd."); + fd = open_serialization_fd("systemd-state"); + if (fd < 0) + return fd; f = fdopen(fd, "w+"); if (!f) { @@ -2461,7 +2458,6 @@ int manager_open_serialization(Manager *m, FILE **_f) { } *_f = f; - return 0; } @@ -2469,7 +2465,6 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { Iterator i; Unit *u; const char *t; - char **e; int r; assert(m); @@ -2499,17 +2494,8 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { dual_timestamp_serialize(f, "units-load-finish-timestamp", &m->units_load_finish_timestamp); } - if (!switching_root) { - STRV_FOREACH(e, m->environment) { - _cleanup_free_ char *ce; - - ce = cescape(*e); - if (!ce) - return -ENOMEM; - - fprintf(f, "env=%s\n", *e); - } - } + if (!switching_root) + (void) serialize_environment(f, m->environment); if (m->notify_fd >= 0) { int copy; @@ -2672,21 +2658,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { else if ((val = startswith(l, "units-load-finish-timestamp="))) dual_timestamp_deserialize(val, &m->units_load_finish_timestamp); else if (startswith(l, "env=")) { - _cleanup_free_ char *uce = NULL; - char **e; - - r = cunescape(l + 4, UNESCAPE_RELAX, &uce); + r = deserialize_environment(&m->environment, l); if (r < 0) - goto finish; - - e = strv_env_set(m->environment, uce); - if (!e) { - r = -ENOMEM; - goto finish; - } - - strv_free(m->environment); - m->environment = e; + return r; } else if ((val = startswith(l, "notify-fd="))) { int fd; @@ -2827,6 +2801,10 @@ int manager_reload(Manager *m) { if (q < 0 && r >= 0) r = q; + q = manager_run_environment_generators(m); + if (q < 0 && r >= 0) + r = q; + /* Find new unit paths */ q = manager_run_generators(m); if (q < 0 && r >= 0) @@ -3018,10 +2996,56 @@ void manager_check_finished(Manager *m) { manager_invalidate_startup_units(m); } +static bool generator_path_any(const char* const* paths) { + char **path; + bool found = false; + + /* Optimize by skipping the whole process by not creating output directories + * if no generators are found. */ + STRV_FOREACH(path, (char**) paths) + if (access(*path, F_OK) == 0) + found = true; + else if (errno != ENOENT) + log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); + + return found; +} + +static const char* system_env_generator_binary_paths[] = { + "/run/systemd/system-environment-generators", + "/etc/systemd/system-environment-generators", + "/usr/local/lib/systemd/system-environment-generators", + SYSTEM_ENV_GENERATOR_PATH, + NULL +}; + +static const char* user_env_generator_binary_paths[] = { + "/run/systemd/user-environment-generators", + "/etc/systemd/user-environment-generators", + "/usr/local/lib/systemd/user-environment-generators", + USER_ENV_GENERATOR_PATH, + NULL +}; + +static int manager_run_environment_generators(Manager *m) { + char **tmp = NULL; /* this is only used in the forked process, no cleanup here */ + const char **paths; + void* args[] = {&tmp, &tmp, &m->environment}; + + if (m->test_run) + return 0; + + paths = MANAGER_IS_SYSTEM(m) ? system_env_generator_binary_paths : user_env_generator_binary_paths; + + if (!generator_path_any(paths)) + return 0; + + return execute_directories(paths, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL); +} + static int manager_run_generators(Manager *m) { _cleanup_strv_free_ char **paths = NULL; const char *argv[5]; - char **path; int r; assert(m); @@ -3033,18 +3057,9 @@ static int manager_run_generators(Manager *m) { if (!paths) return log_oom(); - /* Optimize by skipping the whole process by not creating output directories - * if no generators are found. */ - STRV_FOREACH(path, paths) { - if (access(*path, F_OK) >= 0) - goto found; - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); - } - - return 0; + if (!generator_path_any((const char* const*) paths)) + return 0; - found: r = lookup_paths_mkdir_generator(&m->lookup_paths); if (r < 0) goto finish; @@ -3056,7 +3071,8 @@ static int manager_run_generators(Manager *m) { argv[4] = NULL; RUN_WITH_UMASK(0022) - execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); + execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, + NULL, NULL, (char**) argv); finish: lookup_paths_trim_generator(&m->lookup_paths); diff --git a/src/core/shutdown.c b/src/core/shutdown.c index a795d875bb..a2309b7726 100644 --- a/src/core/shutdown.c +++ b/src/core/shutdown.c @@ -32,6 +32,7 @@ #include "alloc-util.h" #include "cgroup-util.h" #include "def.h" +#include "exec-util.h" #include "fileio.h" #include "killall.h" #include "log.h" @@ -321,7 +322,7 @@ int main(int argc, char *argv[]) { arguments[0] = NULL; arguments[1] = arg_verb; arguments[2] = NULL; - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments); if (!in_container && !in_initrd() && access("/run/initramfs/shutdown", X_OK) == 0) { diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 6abd99430b..43dba82c3a 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -24,10 +24,13 @@ #include <string.h> #include <unistd.h> +#include "sd-bus.h" #include "sd-journal.h" #include "sd-messages.h" #include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" #include "compress.h" #include "fd-util.h" #include "fileio.h" @@ -871,9 +874,65 @@ finish: return r; } +static int check_units_active(void) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int c = 0, r; + const char *state; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to acquire bus: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitsByPatterns"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, NULL); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, STRV_MAKE("systemd-coredump@*.service")); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to check if any systemd-coredump@.service units are running: %s", + bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read( + reply, "(ssssssouso)", + NULL, NULL, NULL, &state, NULL, + NULL, NULL, NULL, NULL, NULL)) > 0) + if (!STR_IN_SET(state, "dead", "failed")) + c++; + + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return c; +} + int main(int argc, char *argv[]) { _cleanup_(sd_journal_closep) sd_journal*j = NULL; - int r = 0; + int r = 0, units_active; setlocale(LC_ALL, ""); log_parse_environment(); @@ -913,6 +972,8 @@ int main(int argc, char *argv[]) { log_debug("Journal filter: %s", filter); } + units_active = check_units_active(); /* error is treated the same as 0 */ + switch(arg_action) { case ACTION_LIST: @@ -933,6 +994,11 @@ int main(int argc, char *argv[]) { assert_not_reached("Shouldn't be here"); } + if (units_active > 0) + printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n", + ansi_highlight_red(), + units_active, units_active == 1 ? "unit is running" : "units are running", + ansi_normal()); end: pager_close(); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 59bd7d9e84..06564e94b1 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -208,6 +208,14 @@ int main(int argc, char *argv[]) { log_error_errno(r, "No root partition for specified root hash found in %s.", arg_image); goto finish; } + if (r == -ENOTUNIQ) { + log_error_errno(r, "Multiple suitable root partitions found in image %s.", arg_image); + goto finish; + } + if (r == -ENXIO) { + log_error_errno(r, "No suitable root partition found in image %s.", arg_image); + goto finish; + } if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); goto finish; diff --git a/src/environment-d-generator/Makefile b/src/environment-d-generator/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/environment-d-generator/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/environment-d-generator/environment-d-generator.c b/src/environment-d-generator/environment-d-generator.c new file mode 100644 index 0000000000..2d4c4235e4 --- /dev/null +++ b/src/environment-d-generator/environment-d-generator.c @@ -0,0 +1,107 @@ +/*** + This file is part of systemd. + + Copyright 2017 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-path.h" + +#include "conf-files.h" +#include "def.h" +#include "escape.h" +#include "fileio.h" +#include "log.h" +#include "path-lookup.h" + +static int environment_dirs(char ***ret) { + _cleanup_strv_free_ char **dirs = NULL; + _cleanup_free_ char *c = NULL; + int r; + + dirs = strv_split_nulstr(CONF_PATHS_NULSTR("environment.d")); + if (!dirs) + return -ENOMEM; + + /* ~/.config/systemd/environment.d */ + r = sd_path_home(SD_PATH_USER_CONFIGURATION, "environment.d", &c); + if (r < 0) + return r; + + r = strv_extend_front(&dirs, c); + if (r < 0) + return r; + + *ret = dirs; + dirs = NULL; + return 0; +} + +static int load_and_print(void) { + _cleanup_strv_free_ char **dirs = NULL, **files = NULL, **env = NULL; + char **i; + int r; + + r = environment_dirs(&dirs); + if (r < 0) + return r; + + r = conf_files_list_strv(&files, ".conf", NULL, (const char **) dirs); + if (r < 0) + return r; + + /* This will mutate the existing environment, based on the presumption + * that in case of failure, a partial update is better than none. */ + + STRV_FOREACH(i, files) { + r = merge_env_file(&env, NULL, *i); + if (r == -ENOMEM) + return r; + } + + STRV_FOREACH(i, env) { + char *t; + _cleanup_free_ char *q = NULL; + + t = strchr(*i, '='); + assert(t); + + q = shell_maybe_quote(t + 1); + if (!q) + return log_oom(); + + printf("%.*s=%s\n", (int) (t - *i), *i, q); + } + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + + log_parse_environment(); + log_open(); + + if (argc > 1) { + log_error("This program takes no arguments."); + return EXIT_FAILURE; + } + + r = load_and_print(); + if (r < 0) + log_error_errno(r, "Failed to load environment.d: %m"); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/import/importd.c b/src/import/importd.c index 9d31a956a5..3d379d6de9 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -449,8 +449,11 @@ static int transfer_start(Transfer *t) { stdio_unset_cloexec(); - setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1); - setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1); + if (setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1) < 0 || + setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1) < 0) { + log_error_errno(errno, "setenv() failed: %m"); + _exit(EXIT_FAILURE); + } if (IN_SET(t->type, TRANSFER_IMPORT_TAR, TRANSFER_IMPORT_RAW)) cmd[k++] = SYSTEMD_IMPORT_PATH; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 22133c51eb..d785b32f1c 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -912,7 +912,7 @@ static int generate_new_id128(void) { SD_ID128_FORMAT_STR "\n\n" "As UUID:\n" "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n" - "As sd-id128(3) macro:\n" + "As man:sd-id128(3) macro:\n" "#define MESSAGE_XYZ SD_ID128_MAKE(", SD_ID128_FORMAT_VAL(id), SD_ID128_FORMAT_VAL(id)); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 451f16483f..6466e46ccc 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -760,7 +760,8 @@ static void dispatch_message_real( const char *label, size_t label_len, const char *unit_id, int priority, - pid_t object_pid) { + pid_t object_pid, + char *cgroup) { char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)], @@ -846,7 +847,12 @@ static void dispatch_message_real( } #endif - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); + r = 0; + if (cgroup) + c = cgroup; + else + r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); + if (r >= 0) { _cleanup_free_ char *raw_unit = NULL, *raw_slice = NULL; char *session = NULL; @@ -904,7 +910,8 @@ static void dispatch_message_real( } } - free(c); + if (!cgroup) + free(c); } else if (unit_id) { x = strjoina("_SYSTEMD_UNIT=", unit_id); IOVEC_SET_STRING(iovec[n++], x); @@ -1093,7 +1100,7 @@ void server_driver_message(Server *s, const char *message_id, const char *format ucred.gid = getgid(); if (r >= 0) - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0, NULL); while (m < n) free(iovec[m++].iov_base); @@ -1107,7 +1114,7 @@ void server_driver_message(Server *s, const char *message_id, const char *format n = 3; IOVEC_SET_STRING(iovec[n++], "PRIORITY=4"); IOVEC_SET_STRING(iovec[n++], buf); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0, NULL); } } @@ -1124,7 +1131,7 @@ void server_dispatch_message( int rl, r; _cleanup_free_ char *path = NULL; uint64_t available = 0; - char *c; + char *c = NULL; assert(s); assert(iovec || n == 0); @@ -1175,7 +1182,10 @@ void server_dispatch_message( NULL); finish: - dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); + /* restore cgroup path for logging */ + if (c) + *c = '/'; + dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid, path); } int server_flush_to_var(Server *s, bool require_flag_file) { diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index b4bf75a3dc..7c0317640f 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -825,6 +825,15 @@ static int client_send_request(sd_dhcp_client *client) { return r; } + if (client->vendor_class_identifier) { + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + strlen(client->vendor_class_identifier), + client->vendor_class_identifier); + if (r < 0) + return r; + } + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index efeadf0cd4..04ead29338 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -1859,8 +1859,7 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, _cleanup_free_ char *value = NULL; const char *syspath; char *path; - struct stat statbuf; - size_t value_len = 0; + size_t len = 0; ssize_t size; int r; @@ -1878,8 +1877,14 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, return r; path = strjoina(syspath, "/", sysattr); - r = lstat(path, &statbuf); - if (r < 0) { + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); + if (fd < 0) { + if (errno == ELOOP) + return -EINVAL; + if (errno == EISDIR) + return -EISDIR; + value = strdup(""); if (!value) return -ENOMEM; @@ -1891,46 +1896,30 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, return -ENXIO; } - if (S_ISLNK(statbuf.st_mode)) - return -EINVAL; - - /* skip directories */ - if (S_ISDIR(statbuf.st_mode)) - return -EISDIR; - - /* skip non-readable files */ - if ((statbuf.st_mode & S_IRUSR) == 0) - return -EACCES; - - value_len = strlen(_value); + len = strlen(_value); /* drop trailing newlines */ - while (value_len > 0 && _value[value_len - 1] == '\n') - _value[--value_len] = '\0'; + while (len > 0 && _value[len - 1] == '\n') + len --; /* value length is limited to 4k */ - if (value_len > 4096) + if (len > 4096) return -EINVAL; - fd = open(path, O_WRONLY | O_CLOEXEC); - if (fd < 0) - return -errno; - - value = strdup(_value); + value = strndup(_value, len); if (!value) return -ENOMEM; - size = write(fd, value, value_len); + size = write(fd, value, len); if (size < 0) return -errno; - if ((size_t)size != value_len) + if ((size_t)size != len) return -EIO; r = device_add_sysattr_value(device, sysattr, value); if (r < 0) return r; - value = NULL; return 0; diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c index a0fd8a3ac9..129bfd2d80 100644 --- a/src/libsystemd/sd-netlink/netlink-socket.c +++ b/src/libsystemd/sd-netlink/netlink-socket.c @@ -281,7 +281,7 @@ static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool else if (errno == EAGAIN) log_debug("rtnl: no data in socket"); - return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; + return IN_SET(errno, EAGAIN, EINTR) ? 0 : -errno; } if (sender.nl.nl_pid != 0) { @@ -292,7 +292,7 @@ static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool /* drop the message */ r = recvmsg(fd, &msg, 0); if (r < 0) - return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; + return IN_SET(errno, EAGAIN, EINTR) ? 0 : -errno; } return 0; diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index 43114eb825..68435564de 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -276,6 +276,10 @@ static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { if (rtnl->rqueue_size <= 0) { /* Try to read a new message */ r = socket_read_message(rtnl); + if (r == -ENOBUFS) { /* FIXME: ignore buffer overruns for now */ + log_debug_errno(r, "Got ENOBUFS from netlink socket, ignoring."); + return 1; + } if (r <= 0) return r; } diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index e4836fcaab..30dac7997b 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -379,7 +379,8 @@ int seat_read_active_vt(Seat *s) { if (!seat_has_vts(s)) return 0; - lseek(s->manager->console_active_fd, SEEK_SET, 0); + if (lseek(s->manager->console_active_fd, SEEK_SET, 0) < 0) + return log_error_errno(errno, "lseek on console_active_fd failed: %m"); k = read(s->manager->console_active_fd, t, sizeof(t)-1); if (k <= 0) { @@ -396,10 +397,8 @@ int seat_read_active_vt(Seat *s) { } r = safe_atou(t+3, &vtnr); - if (r < 0) { - log_error("Failed to parse VT number %s", t+3); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to parse VT number \"%s\": %m", t+3); if (!vtnr) { log_error("VT number invalid: %s", t+3); diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 4b3cac8a22..b709166aa9 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1025,7 +1025,7 @@ static int list_devices(void) { j = items + n++; for (c = 0; c < _COLUMN_MAX; c++) { - const char *x; + const char *x = NULL; size_t k; switch (c) { diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 9d0311d76f..e34793e50b 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -85,7 +85,11 @@ int address_new_static(Network *network, const char *filename, unsigned section_ if (filename) { address->section = n; - hashmap_put(network->addresses_by_section, n, address); + n = NULL; + + r = hashmap_put(network->addresses_by_section, address->section, address); + if (r < 0) + return r; } address->network = network; @@ -94,7 +98,6 @@ int address_new_static(Network *network, const char *filename, unsigned section_ *ret = address; address = NULL; - n = NULL; return 0; } diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index a8ba293ae8..570083f180 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -113,7 +113,7 @@ int route_new_static(Network *network, const char *filename, unsigned section_li route->section = n; n = NULL; - r = hashmap_put(network->routes_by_section, n, route); + r = hashmap_put(network->routes_by_section, route->section, route); if (r < 0) return r; } diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 13f08f8a6c..3c62550872 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -203,13 +203,13 @@ static void write_resolv_conf_search( static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { Iterator i; - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" + fputs("# This file is managed by man:systemd-resolved(8). Do not edit.\n#\n" "# This is a dynamic resolv.conf file for connecting local clients directly to\n" "# all known DNS servers.\n#\n" "# Third party programs must not access this file directly, but only through the\n" - "# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,\n" + "# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,\n" "# replace this symlink by a static file or a different symlink.\n#\n" - "# See systemd-resolved.service(8) for details about the supported modes of\n" + "# See man:systemd-resolved.service(8) for details about the supported modes of\n" "# operation for /etc/resolv.conf.\n\n", f); if (ordered_set_isempty(dns)) diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 9c29b0afca..952fc48c45 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -76,8 +76,11 @@ static int entry_fill_basics( } if (out_interface) { + size_t l = strlen(out_interface); + assert(l < sizeof entry->ip.outiface && l < sizeof entry->ip.outiface_mask); + strcpy(entry->ip.outiface, out_interface); - memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); + memset(entry->ip.outiface_mask, 0xFF, l + 1); } if (destination) { entry->ip.dst = destination->in; diff --git a/src/shared/pager.c b/src/shared/pager.c index af667a83f4..f00ba9e1e7 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -104,7 +104,8 @@ int pager_open(bool no_pager, bool jump_to_end) { less_opts = "FRSXMK"; if (jump_to_end) less_opts = strjoina(less_opts, " +G"); - setenv("LESS", less_opts, 1); + if (setenv("LESS", less_opts, 1) < 0) + _exit(EXIT_FAILURE); /* Initialize a good charset for less. This is * particularly important if we output UTF-8 @@ -112,8 +113,9 @@ int pager_open(bool no_pager, bool jump_to_end) { less_charset = getenv("SYSTEMD_LESSCHARSET"); if (!less_charset && is_locale_utf8()) less_charset = "utf-8"; - if (less_charset) - setenv("LESSCHARSET", less_charset, 1); + if (less_charset && + setenv("LESSCHARSET", less_charset, 1) < 0) + _exit(EXIT_FAILURE); /* Make sure the pager goes away when the parent dies */ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c index 09a44534e2..e2b3f8b742 100644 --- a/src/shared/path-lookup.c +++ b/src/shared/path-lookup.c @@ -520,8 +520,7 @@ int lookup_paths_init( append = true; } - /* FIXME: empty components in other places should be - * rejected. */ + /* FIXME: empty components in other places should be rejected. */ r = path_split_and_make_absolute(e, &paths); if (r < 0) diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index ea96615222..3bac78b3e4 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -25,6 +25,7 @@ #include "sd-messages.h" #include "def.h" +#include "exec-util.h" #include "fd-util.h" #include "fileio.h" #include "log.h" @@ -106,7 +107,7 @@ static int execute(char **modes, char **states) { if (r < 0) return r; - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments); log_struct(LOG_INFO, "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR, @@ -125,7 +126,7 @@ static int execute(char **modes, char **states) { NULL); arguments[1] = (char*) "post"; - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments); return r; } diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 60f8bc3df5..157a46865f 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3482,6 +3482,8 @@ static int set_exit_code(uint8_t code) { static int start_special(int argc, char *argv[], void *userdata) { enum action a; int r; + bool termination_action; /* an action that terminates the manager, + * can be performed also by signal. */ assert(argv); @@ -3521,40 +3523,43 @@ static int start_special(int argc, char *argv[], void *userdata) { return r; } - if (arg_force >= 2 && - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT)) + termination_action = IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT); + if (termination_action && arg_force >= 2) return halt_now(a); if (arg_force >= 1 && - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC, - ACTION_EXIT)) - return trivial_method(argc, argv, userdata); + (termination_action || IN_SET(a, ACTION_KEXEC, ACTION_EXIT))) + r = trivial_method(argc, argv, userdata); + else { + /* First try logind, to allow authentication with polkit */ + if (IN_SET(a, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_SUSPEND, + ACTION_HIBERNATE, + ACTION_HYBRID_SLEEP)) { + + r = logind_reboot(a); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not supported or already in progress */ + return r; - /* First try logind, to allow authentication with polkit */ - if (IN_SET(a, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_SUSPEND, - ACTION_HIBERNATE, - ACTION_HYBRID_SLEEP)) { - r = logind_reboot(a); - if (r >= 0) - return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* requested operation is not supported or already in progress */ - return r; + /* On all other errors, try low-level operation */ + } - /* On all other errors, try low-level operation */ + r = start_unit(argc, argv, userdata); } - return start_unit(argc, argv, userdata); + if (termination_action && arg_force < 2 && + IN_SET(r, -ENOENT, -ETIMEDOUT)) + log_notice("It is possible to perform action directly, see discussion of --force --force in man:systemctl(1)."); + + return r; } static int start_system_special(int argc, char *argv[], void *userdata) { @@ -6806,29 +6811,54 @@ static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { return r; STRV_FOREACH(name, names) { - _cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL; + _cleanup_free_ char *path = NULL, *new_path = NULL, *tmp_path = NULL, *tmp_name = NULL; + const char *unit_name; r = unit_find_paths(bus, *name, &lp, &path, NULL); if (r < 0) return r; - else if (!arg_force) { - if (r == 0) { - log_error("Run 'systemctl edit --force %s' to create a new unit.", *name); - return -ENOENT; - } else if (!path) { - // FIXME: support units with path==NULL (no FragmentPath) - log_error("No fragment exists for %s.", *name); + + if (r == 0) { + assert(!path); + + if (!arg_force) { + log_error("Run 'systemctl edit%s --force %s' to create a new unit.", + arg_scope == UNIT_FILE_GLOBAL ? " --global" : + arg_scope == UNIT_FILE_USER ? " --user" : "", + *name); return -ENOENT; } - } - if (path) { + /* Create a new unit from scratch */ + unit_name = *name; + r = unit_file_create_new(&lp, unit_name, + arg_full ? NULL : ".d/override.conf", + &new_path, &tmp_path); + } else { + assert(path); + + unit_name = basename(path); + /* We follow unit aliases, but we need to propagate the instance */ + if (unit_name_is_valid(*name, UNIT_NAME_INSTANCE) && + unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { + _cleanup_free_ char *instance = NULL; + + r = unit_name_to_instance(*name, &instance); + if (r < 0) + return r; + + r = unit_name_replace_instance(unit_name, instance, &tmp_name); + if (r < 0) + return r; + + unit_name = tmp_name; + } + if (arg_full) - r = unit_file_create_copy(&lp, basename(path), path, &new_path, &tmp_path); + r = unit_file_create_copy(&lp, unit_name, path, &new_path, &tmp_path); else - r = unit_file_create_new(&lp, basename(path), ".d/override.conf", &new_path, &tmp_path); - } else - r = unit_file_create_new(&lp, *name, NULL, &new_path, &tmp_path); + r = unit_file_create_new(&lp, unit_name, ".d/override.conf", &new_path, &tmp_path); + } if (r < 0) return r; @@ -7300,7 +7330,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { case 't': { if (isempty(optarg)) { - log_error("--type requires arguments."); + log_error("--type= requires arguments."); return -EINVAL; } @@ -7540,7 +7570,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { case ARG_STATE: { if (isempty(optarg)) { - log_error("--signal requires arguments."); + log_error("--state= requires arguments."); return -EINVAL; } @@ -7549,7 +7579,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { r = extract_first_word(&p, &s, ",", 0); if (r < 0) - return log_error_errno(r, "Failed to parse signal: %s", optarg); + return log_error_errno(r, "Failed to parse state: %s", optarg); if (r == 0) break; diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h index e8f84eb545..3563a2b126 100644 --- a/src/systemd/sd-bus-vtable.h +++ b/src/systemd/sd-bus-vtable.h @@ -86,18 +86,26 @@ struct sd_bus_vtable { { \ .type = _SD_BUS_VTABLE_START, \ .flags = _flags, \ - .x.start.element_size = sizeof(sd_bus_vtable), \ + .x = { \ + .start = { \ + .element_size = sizeof(sd_bus_vtable) \ + }, \ + }, \ } #define SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, _offset, _flags) \ { \ .type = _SD_BUS_VTABLE_METHOD, \ .flags = _flags, \ - .x.method.member = _member, \ - .x.method.signature = _signature, \ - .x.method.result = _result, \ - .x.method.handler = _handler, \ - .x.method.offset = _offset, \ + .x = { \ + .method = { \ + .member = _member, \ + .signature = _signature, \ + .result = _result, \ + .handler = _handler, \ + .offset = _offset, \ + }, \ + }, \ } #define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, 0, _flags) @@ -106,29 +114,41 @@ struct sd_bus_vtable { { \ .type = _SD_BUS_VTABLE_SIGNAL, \ .flags = _flags, \ - .x.signal.member = _member, \ - .x.signal.signature = _signature, \ + .x = { \ + .signal = { \ + .member = _member, \ + .signature = _signature, \ + }, \ + }, \ } #define SD_BUS_PROPERTY(_member, _signature, _get, _offset, _flags) \ { \ .type = _SD_BUS_VTABLE_PROPERTY, \ .flags = _flags, \ - .x.property.member = _member, \ - .x.property.signature = _signature, \ - .x.property.get = _get, \ - .x.property.offset = _offset, \ + .x = { \ + .property = { \ + .member = _member, \ + .signature = _signature, \ + .get = _get, \ + .offset = _offset, \ + }, \ + }, \ } #define SD_BUS_WRITABLE_PROPERTY(_member, _signature, _get, _set, _offset, _flags) \ { \ .type = _SD_BUS_VTABLE_WRITABLE_PROPERTY, \ .flags = _flags, \ - .x.property.member = _member, \ - .x.property.signature = _signature, \ - .x.property.get = _get, \ - .x.property.set = _set, \ - .x.property.offset = _offset, \ + .x = { \ + .property = { \ + .member = _member, \ + .signature = _signature, \ + .get = _get, \ + .set = _set, \ + .offset = _offset, \ + }, \ + }, \ } #define SD_BUS_VTABLE_END \ diff --git a/src/test/test-conf-files.c b/src/test/test-conf-files.c index 03b3a9fa5c..22b7c61204 100644 --- a/src/test/test-conf-files.c +++ b/src/test/test-conf-files.c @@ -47,13 +47,16 @@ static void setup_test_dir(char *tmp_dir, const char *files, ...) { static void test_conf_files_list(bool use_root) { char tmp_dir[] = "/tmp/test-conf-files-XXXXXX"; - _cleanup_strv_free_ char **found_files = NULL; - const char *root_dir, *search_1, *search_2, *expect_a, *expect_b; + _cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL; + const char *root_dir, *search_1, *search_2, *expect_a, *expect_b, *expect_c; + + log_debug("/* %s */", __func__); setup_test_dir(tmp_dir, "/dir1/a.conf", "/dir2/a.conf", "/dir2/b.conf", + "/dir2/c.foo", NULL); if (use_root) { @@ -68,6 +71,9 @@ static void test_conf_files_list(bool use_root) { expect_a = strjoina(tmp_dir, "/dir1/a.conf"); expect_b = strjoina(tmp_dir, "/dir2/b.conf"); + expect_c = strjoina(tmp_dir, "/dir2/c.foo"); + + log_debug("/* Check when filtered by suffix */"); assert_se(conf_files_list(&found_files, ".conf", root_dir, search_1, search_2, NULL) == 0); strv_print(found_files); @@ -77,10 +83,24 @@ static void test_conf_files_list(bool use_root) { assert_se(streq_ptr(found_files[1], expect_b)); assert_se(found_files[2] == NULL); + log_debug("/* Check when unfiltered */"); + assert_se(conf_files_list(&found_files2, NULL, root_dir, search_1, search_2, NULL) == 0); + strv_print(found_files2); + + assert_se(found_files2); + assert_se(streq_ptr(found_files2[0], expect_a)); + assert_se(streq_ptr(found_files2[1], expect_b)); + assert_se(streq_ptr(found_files2[2], expect_c)); + assert_se(found_files2[3] == NULL); + assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); } int main(int argc, char **argv) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + test_conf_files_list(false); test_conf_files_list(true); return 0; diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index 35bb62906e..e5cc2a2df8 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -45,6 +45,16 @@ static void test_strv_env_delete(void) { assert_se(strv_length(d) == 2); } +static void test_strv_env_get(void) { + char **l; + + l = STRV_MAKE("ONE_OR_TWO=1", "THREE=3", "ONE_OR_TWO=2", "FOUR=4"); + + assert_se(streq(strv_env_get(l, "ONE_OR_TWO"), "2")); + assert_se(streq(strv_env_get(l, "THREE"), "3")); + assert_se(streq(strv_env_get(l, "FOUR"), "4")); +} + static void test_strv_env_unset(void) { _cleanup_strv_free_ char **l = NULL; @@ -102,7 +112,90 @@ static void test_strv_env_merge(void) { assert_se(strv_length(r) == 5); } -static void test_replace_env_arg(void) { +static void test_env_strv_get_n(void) { + const char *_env[] = { + "FOO=NO NO NO", + "FOO=BAR BAR", + "BAR=waldo", + "PATH=unset", + NULL + }; + char **env = (char**) _env; + + assert_se(streq(strv_env_get_n(env, "FOO__", 3, 0), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO__", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO", 3, 0), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + + assert_se(streq(strv_env_get_n(env, "PATH__", 4, 0), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH", 4, 0), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + + env[3] = NULL; /* kill our $PATH */ + + assert_se(!strv_env_get_n(env, "PATH__", 4, 0)); + assert_se(!strv_env_get_n(env, "PATH", 4, 0)); + assert_se(streq(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), + getenv("PATH"))); + assert_se(streq(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), + getenv("PATH"))); +} + +static void test_replace_env(bool braceless) { + const char *env[] = { + "FOO=BAR BAR", + "BAR=waldo", + NULL + }; + _cleanup_free_ char *t = NULL, *s = NULL, *q = NULL, *r = NULL, *p = NULL; + unsigned flags = REPLACE_ENV_ALLOW_BRACELESS*braceless; + + t = replace_env("FOO=$FOO=${FOO}", (char**) env, flags); + assert_se(streq(t, braceless ? "FOO=BAR BAR=BAR BAR" : "FOO=$FOO=BAR BAR")); + + s = replace_env("BAR=$BAR=${BAR}", (char**) env, flags); + assert_se(streq(s, braceless ? "BAR=waldo=waldo" : "BAR=$BAR=waldo")); + + q = replace_env("BARBAR=$BARBAR=${BARBAR}", (char**) env, flags); + assert_se(streq(q, braceless ? "BARBAR==" : "BARBAR=$BARBAR=")); + + r = replace_env("BAR=$BAR$BAR${BAR}${BAR}", (char**) env, flags); + assert_se(streq(r, braceless ? "BAR=waldowaldowaldowaldo" : "BAR=$BAR$BARwaldowaldo")); + + p = replace_env("${BAR}$BAR$BAR", (char**) env, flags); + assert_se(streq(p, braceless ? "waldowaldowaldo" : "waldo$BAR$BAR")); +} + +static void test_replace_env2(bool extended) { + const char *env[] = { + "FOO=foo", + "BAR=bar", + NULL + }; + _cleanup_free_ char *t = NULL, *s = NULL, *q = NULL, *r = NULL, *p = NULL, *x = NULL; + unsigned flags = REPLACE_ENV_ALLOW_EXTENDED*extended; + + t = replace_env("FOO=${FOO:-${BAR}}", (char**) env, flags); + assert_se(streq(t, extended ? "FOO=foo" : "FOO=${FOO:-bar}")); + + s = replace_env("BAR=${XXX:-${BAR}}", (char**) env, flags); + assert_se(streq(s, extended ? "BAR=bar" : "BAR=${XXX:-bar}")); + + q = replace_env("XXX=${XXX:+${BAR}}", (char**) env, flags); + assert_se(streq(q, extended ? "XXX=" : "XXX=${XXX:+bar}")); + + r = replace_env("FOO=${FOO:+${BAR}}", (char**) env, flags); + assert_se(streq(r, extended ? "FOO=bar" : "FOO=${FOO:+bar}")); + + p = replace_env("FOO=${FOO:-${BAR}post}", (char**) env, flags); + assert_se(streq(p, extended ? "FOO=foo" : "FOO=${FOO:-barpost}")); + + x = replace_env("XXX=${XXX:+${BAR}post}", (char**) env, flags); + assert_se(streq(x, extended ? "XXX=" : "XXX=${XXX:+barpost}")); +} + +static void test_replace_env_argv(void) { const char *env[] = { "FOO=BAR BAR", "BAR=waldo", @@ -120,6 +213,12 @@ static void test_replace_env_arg(void) { "${FOO", "FOO$$${FOO}", "$$FOO${FOO}", + "${FOO:-${BAR}}", + "${QUUX:-${FOO}}", + "${FOO:+${BAR}}", + "${QUUX:+${BAR}}", + "${FOO:+|${BAR}|}}", + "${FOO:+|${BAR}{|}", NULL }; _cleanup_strv_free_ char **r = NULL; @@ -137,7 +236,13 @@ static void test_replace_env_arg(void) { assert_se(streq(r[8], "${FOO")); assert_se(streq(r[9], "FOO$BAR BAR")); assert_se(streq(r[10], "$FOOBAR BAR")); - assert_se(strv_length(r) == 11); + assert_se(streq(r[11], "${FOO:-waldo}")); + assert_se(streq(r[12], "${QUUX:-BAR BAR}")); + assert_se(streq(r[13], "${FOO:+waldo}")); + assert_se(streq(r[14], "${QUUX:+waldo}")); + assert_se(streq(r[15], "${FOO:+|waldo|}}")); + assert_se(streq(r[16], "${FOO:+|waldo{|}")); + assert_se(strv_length(r) == 17); } static void test_env_clean(void) { @@ -211,10 +316,16 @@ static void test_env_assignment_is_valid(void) { int main(int argc, char *argv[]) { test_strv_env_delete(); + test_strv_env_get(); test_strv_env_unset(); test_strv_env_set(); test_strv_env_merge(); - test_replace_env_arg(); + test_env_strv_get_n(); + test_replace_env(false); + test_replace_env(true); + test_replace_env2(false); + test_replace_env2(true); + test_replace_env_argv(); test_env_clean(); test_env_name_is_valid(); test_env_value_is_valid(); diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c new file mode 100644 index 0000000000..482b0751b9 --- /dev/null +++ b/src/test/test-exec-util.c @@ -0,0 +1,348 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Thomas H.P. Andersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "copy.h" +#include "def.h" +#include "env-util.h" +#include "exec-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" + +static int here = 0, here2 = 0, here3 = 0; +void *ignore_stdout_args[] = {&here, &here2, &here3}; + +/* noop handlers, just check that arguments are passed correctly */ +static int ignore_stdout_func(int fd, void *arg) { + assert(fd >= 0); + assert(arg == &here); + safe_close(fd); + + return 0; +} +static int ignore_stdout_func2(int fd, void *arg) { + assert(fd >= 0); + assert(arg == &here2); + safe_close(fd); + + return 0; +} +static int ignore_stdout_func3(int fd, void *arg) { + assert(fd >= 0); + assert(arg == &here3); + safe_close(fd); + + return 0; +} + +static const gather_stdout_callback_t ignore_stdout[] = { + ignore_stdout_func, + ignore_stdout_func2, + ignore_stdout_func3, +}; + +static void test_execute_directory(bool gather_stdout) { + char template_lo[] = "/tmp/test-exec-util.XXXXXXX"; + char template_hi[] = "/tmp/test-exec-util.XXXXXXX"; + const char * dirs[] = {template_hi, template_lo, NULL}; + const char *name, *name2, *name3, *overridden, *override, *masked, *mask; + + log_info("/* %s (%s) */", __func__, gather_stdout ? "gathering stdout" : "asynchronous"); + + assert_se(mkdtemp(template_lo)); + assert_se(mkdtemp(template_hi)); + + name = strjoina(template_lo, "/script"); + name2 = strjoina(template_hi, "/script2"); + name3 = strjoina(template_lo, "/useless"); + overridden = strjoina(template_lo, "/overridden"); + override = strjoina(template_hi, "/overridden"); + masked = strjoina(template_lo, "/masked"); + mask = strjoina(template_hi, "/masked"); + + assert_se(write_string_file(name, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(overridden, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(override, + "#!/bin/sh\necho 'Executing '$0", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(masked, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(symlink("/dev/null", mask) == 0); + assert_se(touch(name3) >= 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(overridden, 0755) == 0); + assert_se(chmod(override, 0755) == 0); + assert_se(chmod(masked, 0755) == 0); + + if (gather_stdout) + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL); + else + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL); + + assert_se(chdir(template_lo) == 0); + assert_se(access("it_works", F_OK) >= 0); + assert_se(access("failed", F_OK) < 0); + + assert_se(chdir(template_hi) == 0); + assert_se(access("it_works2", F_OK) >= 0); + assert_se(access("failed", F_OK) < 0); + + (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL); +} + +static void test_execution_order(void) { + char template_lo[] = "/tmp/test-exec-util-lo.XXXXXXX"; + char template_hi[] = "/tmp/test-exec-util-hi.XXXXXXX"; + const char *dirs[] = {template_hi, template_lo, NULL}; + const char *name, *name2, *name3, *overridden, *override, *masked, *mask; + const char *output, *t; + _cleanup_free_ char *contents = NULL; + + assert_se(mkdtemp(template_lo)); + assert_se(mkdtemp(template_hi)); + + output = strjoina(template_hi, "/output"); + + log_info("/* %s >>%s */", __func__, output); + + /* write files in "random" order */ + name2 = strjoina(template_lo, "/90-bar"); + name = strjoina(template_hi, "/80-foo"); + name3 = strjoina(template_lo, "/last"); + overridden = strjoina(template_lo, "/30-override"); + override = strjoina(template_hi, "/30-override"); + masked = strjoina(template_lo, "/10-masked"); + mask = strjoina(template_hi, "/10-masked"); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(name, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(name2, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(name3, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho OVERRIDDEN >>", output); + assert_se(write_string_file(overridden, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(override, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho MASKED >>", output); + assert_se(write_string_file(masked, t, WRITE_STRING_FILE_CREATE) == 0); + + assert_se(symlink("/dev/null", mask) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + assert_se(chmod(overridden, 0755) == 0); + assert_se(chmod(override, 0755) == 0); + assert_se(chmod(masked, 0755) == 0); + + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL); + + assert_se(read_full_file(output, &contents, NULL) >= 0); + assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n")); + + (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL); +} + +static int gather_stdout_one(int fd, void *arg) { + char ***s = arg, *t; + char buf[128] = {}; + + assert_se(s); + assert_se(read(fd, buf, sizeof buf) >= 0); + safe_close(fd); + + assert_se(t = strndup(buf, sizeof buf)); + assert_se(strv_push(s, t) >= 0); + + return 0; +} +static int gather_stdout_two(int fd, void *arg) { + char ***s = arg, **t; + + STRV_FOREACH(t, *s) + assert_se(write(fd, *t, strlen(*t)) == (ssize_t) strlen(*t)); + safe_close(fd); + + return 0; +} +static int gather_stdout_three(int fd, void *arg) { + char **s = arg; + char buf[128] = {}; + + assert_se(read(fd, buf, sizeof buf - 1) > 0); + safe_close(fd); + assert_se(*s = strndup(buf, sizeof buf)); + + return 0; +} + +const gather_stdout_callback_t const gather_stdout[] = { + gather_stdout_one, + gather_stdout_two, + gather_stdout_three, +}; + + +static void test_stdout_gathering(void) { + char template[] = "/tmp/test-exec-util.XXXXXXX"; + const char *dirs[] = {template, NULL}; + const char *name, *name2, *name3; + int r; + + char **tmp = NULL; /* this is only used in the forked process, no cleanup here */ + _cleanup_free_ char *output = NULL; + + void* args[] = {&tmp, &tmp, &output}; + + assert_se(mkdtemp(template)); + + log_info("/* %s */", __func__); + + /* write files */ + name = strjoina(template, "/10-foo"); + name2 = strjoina(template, "/20-bar"); + name3 = strjoina(template, "/30-last"); + + assert_se(write_string_file(name, + "#!/bin/sh\necho a\necho b\necho c\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\necho d\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name3, + "#!/bin/sh\nsleep 1", + WRITE_STRING_FILE_CREATE) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + + r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdout, args, NULL); + assert_se(r >= 0); + + log_info("got: %s", output); + + assert_se(streq(output, "a\nb\nc\nd\n")); +} + +static void test_environment_gathering(void) { + char template[] = "/tmp/test-exec-util.XXXXXXX", **p; + const char *dirs[] = {template, NULL}; + const char *name, *name2, *name3; + int r; + + char **tmp = NULL; /* this is only used in the forked process, no cleanup here */ + _cleanup_strv_free_ char **env = NULL; + + void* const args[] = { &tmp, &tmp, &env }; + + assert_se(mkdtemp(template)); + + log_info("/* %s */", __func__); + + /* write files */ + name = strjoina(template, "/10-foo"); + name2 = strjoina(template, "/20-bar"); + name3 = strjoina(template, "/30-last"); + + assert_se(write_string_file(name, + "#!/bin/sh\n" + "echo A=23\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\n" + "echo A=22:$A\n\n\n", /* substitution from previous generator */ + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name3, + "#!/bin/sh\n" + "echo A=$A:24\n" + "echo B=12\n" + "echo C=000\n" + "echo C=001\n" /* variable overwriting */ + /* various invalid entries */ + "echo unset A\n" + "echo unset A=\n" + "echo unset A=B\n" + "echo unset \n" + "echo A B=C\n" + "echo A\n" + /* test variable assignment without newline */ + "echo PATH=$PATH:/no/such/file", /* no newline */ + WRITE_STRING_FILE_CREATE) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + + r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL); + assert_se(r >= 0); + + STRV_FOREACH(p, env) + log_info("got env: \"%s\"", *p); + + assert_se(streq(strv_env_get(env, "A"), "22:23:24")); + assert_se(streq(strv_env_get(env, "B"), "12")); + assert_se(streq(strv_env_get(env, "C"), "001")); + assert_se(endswith(strv_env_get(env, "PATH"), ":/no/such/file")); +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + test_execute_directory(true); + test_execute_directory(false); + test_execution_order(); + test_stdout_gathering(); + test_environment_gathering(); + + return 0; +} diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index f555bb976c..4425b5fe5f 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -94,10 +94,20 @@ static void test_same_fd(void) { assert_se(same_fd(b, a) == 0); } +static void test_open_serialization_fd(void) { + _cleanup_close_ int fd = -1; + + fd = open_serialization_fd("test"); + assert_se(fd >= 0); + + write(fd, "test\n", 5); +} + int main(int argc, char *argv[]) { test_close_many(); test_close_nointr(); test_same_fd(); + test_open_serialization_fd(); return 0; } diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 56316904a3..b1d688c89e 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -71,6 +71,8 @@ static void test_parse_env_file(void) { "seven=\"sevenval\" #nocomment\n" "eight=eightval #nocomment\n" "export nine=nineval\n" + "ten=ignored\n" + "ten=ignored\n" "ten=", f); fflush(f); @@ -204,6 +206,113 @@ static void test_parse_multiline_env_file(void) { unlink(p); } +static void test_merge_env_file(void) { + char t[] = "/tmp/test-fileio-XXXXXX"; + int fd, r; + FILE *f; + _cleanup_strv_free_ char **a = NULL; + char **i; + + fd = mkostemp_safe(t); + assert_se(fd >= 0); + + log_info("/* %s (%s) */", __func__, t); + + f = fdopen(fd, "w"); + assert_se(f); + + r = write_string_stream(f, + "one=1 \n" + "twelve=${one}2\n" + "twentyone=2${one}\n" + "one=2\n" + "twentytwo=2${one}\n" + "xxx_minus_three=$xxx - 3\n" + "xxx=0x$one$one$one\n" + "yyy=${one:-fallback}\n" + "zzz=${one:+replacement}\n" + "zzzz=${foobar:-${nothing}}\n" + "zzzzz=${nothing:+${nothing}}\n" + , false); + assert(r >= 0); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + strv_sort(a); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(streq(a[0], "one=2")); + assert_se(streq(a[1], "twelve=12")); + assert_se(streq(a[2], "twentyone=21")); + assert_se(streq(a[3], "twentytwo=22")); + assert_se(streq(a[4], "xxx=0x222")); + assert_se(streq(a[5], "xxx_minus_three= - 3")); + assert_se(streq(a[6], "yyy=2")); + assert_se(streq(a[7], "zzz=replacement")); + assert_se(streq(a[8], "zzzz=")); + assert_se(streq(a[9], "zzzzz=")); + assert_se(a[10] == NULL); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + strv_sort(a); + + STRV_FOREACH(i, a) + log_info("Got2: <%s>", *i); + + assert_se(streq(a[0], "one=2")); + assert_se(streq(a[1], "twelve=12")); + assert_se(streq(a[2], "twentyone=21")); + assert_se(streq(a[3], "twentytwo=22")); + assert_se(streq(a[4], "xxx=0x222")); + assert_se(streq(a[5], "xxx_minus_three=0x222 - 3")); + assert_se(streq(a[6], "yyy=2")); + assert_se(streq(a[7], "zzz=replacement")); + assert_se(streq(a[8], "zzzz=")); + assert_se(streq(a[9], "zzzzz=")); + assert_se(a[10] == NULL); +} + +static void test_merge_env_file_invalid(void) { + char t[] = "/tmp/test-fileio-XXXXXX"; + int fd, r; + FILE *f; + _cleanup_strv_free_ char **a = NULL; + char **i; + + fd = mkostemp_safe(t); + assert_se(fd >= 0); + + log_info("/* %s (%s) */", __func__, t); + + f = fdopen(fd, "w"); + assert_se(f); + + r = write_string_stream(f, + "unset one \n" + "unset one= \n" + "unset one=1 \n" + "one \n" + "one = \n" + "one two =\n" + "\x20two=\n" + "#comment=comment\n" + ";comment2=comment2\n" + "#\n" + "\n\n" /* empty line */ + , false); + assert(r >= 0); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(strv_isempty(a)); +} static void test_executable_is_script(void) { char t[] = "/tmp/test-executable-XXXXXX"; @@ -555,11 +664,14 @@ static void test_tempfn(void) { } int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); log_parse_environment(); log_open(); test_parse_env_file(); test_parse_multiline_env_file(); + test_merge_env_file(); + test_merge_env_file_invalid(); test_executable_is_script(); test_status_field(); test_capeff(); diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 28ca6bc317..3ff2aadea4 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -82,7 +82,9 @@ static void test_path_check_fstype(void) { } static void test_path_is_temporary_fs(void) { - assert_se(path_is_temporary_fs("/run") > 0); + /* run might not be a mount point in build chroots */ + if (path_is_mount_point("/run", NULL, AT_SYMLINK_FOLLOW) > 0) + assert_se(path_is_temporary_fs("/run") > 0); assert_se(path_is_temporary_fs("/proc") == 0); assert_se(path_is_temporary_fs("/i-dont-exist") == -ENOENT); } diff --git a/src/test/test-util.c b/src/test/test-util.c index 1b5cba86c1..f8bf0cb875 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -195,50 +195,6 @@ static void test_log2i(void) { assert_se(log2i(INT_MAX) == sizeof(int)*8-2); } -static void test_execute_directory(void) { - char template_lo[] = "/tmp/test-readlink_and_make_absolute-lo.XXXXXXX"; - char template_hi[] = "/tmp/test-readlink_and_make_absolute-hi.XXXXXXX"; - const char * dirs[] = {template_hi, template_lo, NULL}; - const char *name, *name2, *name3, *overridden, *override, *masked, *mask; - - assert_se(mkdtemp(template_lo)); - assert_se(mkdtemp(template_hi)); - - name = strjoina(template_lo, "/script"); - name2 = strjoina(template_hi, "/script2"); - name3 = strjoina(template_lo, "/useless"); - overridden = strjoina(template_lo, "/overridden"); - override = strjoina(template_hi, "/overridden"); - masked = strjoina(template_lo, "/masked"); - mask = strjoina(template_hi, "/masked"); - - assert_se(write_string_file(name, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works", WRITE_STRING_FILE_CREATE) == 0); - assert_se(write_string_file(name2, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2", WRITE_STRING_FILE_CREATE) == 0); - assert_se(write_string_file(overridden, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", WRITE_STRING_FILE_CREATE) == 0); - assert_se(write_string_file(override, "#!/bin/sh\necho 'Executing '$0", WRITE_STRING_FILE_CREATE) == 0); - assert_se(write_string_file(masked, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", WRITE_STRING_FILE_CREATE) == 0); - assert_se(symlink("/dev/null", mask) == 0); - assert_se(chmod(name, 0755) == 0); - assert_se(chmod(name2, 0755) == 0); - assert_se(chmod(overridden, 0755) == 0); - assert_se(chmod(override, 0755) == 0); - assert_se(chmod(masked, 0755) == 0); - assert_se(touch(name3) >= 0); - - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL); - - assert_se(chdir(template_lo) == 0); - assert_se(access("it_works", F_OK) >= 0); - assert_se(access("failed", F_OK) < 0); - - assert_se(chdir(template_hi) == 0); - assert_se(access("it_works2", F_OK) >= 0); - assert_se(access("failed", F_OK) < 0); - - (void) rm_rf(template_lo, REMOVE_ROOT|REMOVE_PHYSICAL); - (void) rm_rf(template_hi, REMOVE_ROOT|REMOVE_PHYSICAL); -} - static void test_raw_clone(void) { pid_t parent, pid, pid2; @@ -359,7 +315,6 @@ int main(int argc, char *argv[]) { test_protect_errno(); test_in_set(); test_log2i(); - test_execute_directory(); test_raw_clone(); test_physical_memory(); test_physical_memory_scale(); diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index b5a88c667c..bd7b789cad 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -426,11 +426,10 @@ static int names_bcma(struct udev_device *dev, struct netnames *names) { static int names_ccw(struct udev_device *dev, struct netnames *names) { struct udev_device *cdev; - const char *bus_id; + const char *bus_id, *subsys; size_t bus_id_len; size_t bus_id_start; int rc; - char *subsys; assert(dev); assert(names); diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index 48c2a3fff4..d466e1b759 100644 --- a/src/update-done/update-done.c +++ b/src/update-done/update-done.c @@ -26,7 +26,7 @@ #define MESSAGE \ "# This file was created by systemd-update-done. Its only \n" \ "# purpose is to hold a timestamp of the time this directory\n" \ - "# was updated. See systemd-update-done.service(8).\n" + "# was updated. See man:systemd-update-done.service(8).\n" static int apply_timestamp(const char *path, struct timespec *ts) { struct timespec twice[2] = { |