diff --git a/man/homectl.xml b/man/homectl.xml index 4b0b120ca8..245ebcee00 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -288,12 +288,16 @@ - VARIABLE=VALUE + VARIABLE[=VALUE] - Takes an environment variable assignment to set for all user processes. Note that a - number of other settings also result in environment variables to be set for the user, including - , and . May be used - multiple times to set multiple environment variables. + Takes an environment variable assignment to set for all user processes. May be used + multiple times to set multiple environment variables. When = and + VALUE are omitted, the value of the variable with the same name in the + program environment will be used. + + Note that a number of other settings also result in environment variables to be set for the + user, including , and + . diff --git a/man/machinectl.xml b/man/machinectl.xml index ad47b6102e..7ecb8885a2 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -712,14 +712,16 @@ - - + + - When used with the shell command, sets an environment - variable to pass to the executed shell. Takes an environment variable name and value, - separated by =. This switch may be used multiple times to set multiple - environment variables. Note that this switch is not supported for the - login command (see below). + When used with the shell command, sets an environment variable for + the executed shell. This option may be used more than once to set multiple variables. When + = and VALUE are omitted, the value of the variable with + the same name in the program environment will be used. + + Note that this option is not supported for the login command. + diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 3623ef015a..e84ac6ae42 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -527,14 +527,14 @@ - - + + - Specifies an environment variable assignment - to pass to the init process in the container, in the format - NAME=VALUE. This may be used to override - the default variables or to set additional variables. This - parameter may be used more than once. + Specifies an environment variable to pass to the init process in the container. This + may be used to override the default variables or to set additional variables. It may be used more + than once to set multiple variables. When = and VALUE + are omitted, the value of the variable with the same name in the program environment will be used. + diff --git a/man/systemd-run.xml b/man/systemd-run.xml index fc8716ea79..99726dabfb 100644 --- a/man/systemd-run.xml +++ b/man/systemd-run.xml @@ -239,11 +239,15 @@ - - + + - Runs the service process with the specified environment variable set. - Also see Environment= in + Runs the service process with the specified environment variable set. This parameter + may be used more than once to set multiple variables. When = and + VALUE are omitted, the value of the variable with the same name in the + program environment will be used. + + Also see Environment= in systemd.exec5. diff --git a/src/activate/activate.c b/src/activate/activate.c index 18ce491a84..0c32152671 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -9,6 +9,7 @@ #include "sd-daemon.h" #include "alloc-util.h" +#include "env-util.h" #include "errno-util.h" #include "escape.h" #include "fd-util.h" @@ -121,11 +122,9 @@ static int open_sockets(int *epoll_fd, bool accept) { return count; } -static int exec_process(const char *name, char **argv, char **env, int start_fd, size_t n_fds) { +static int exec_process(const char *name, char **argv, int start_fd, size_t n_fds) { _cleanup_strv_free_ char **envp = NULL; - _cleanup_free_ char *joined = NULL; - size_t n_env = 0, length; - const char *tocopy; + const char *var; char **s; int r; @@ -133,55 +132,16 @@ static int exec_process(const char *name, char **argv, char **env, int start_fd, return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--inetd only supported for single file descriptors."); - length = strv_length(arg_setenv); - - /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */ - envp = new0(char *, length + 8); - if (!envp) - return log_oom(); - - STRV_FOREACH(s, arg_setenv) { - - if (strchr(*s, '=')) { - char *k; - - k = strdup(*s); - if (!k) - return log_oom(); - - envp[n_env++] = k; - } else { - _cleanup_free_ char *p = NULL; - const char *n; - - p = strjoin(*s, "="); - if (!p) - return log_oom(); - - n = strv_find_prefix(env, p); - if (!n) - continue; - - envp[n_env] = strdup(n); - if (!envp[n_env]) - return log_oom(); - - n_env++; - } - } - - FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { + FOREACH_STRING(var, "TERM", "PATH", "USER", "HOME") { const char *n; - n = strv_find_prefix(env, tocopy); + n = strv_find_prefix(environ, var); if (!n) continue; - envp[n_env] = strdup(n); - if (!envp[n_env]) - return log_oom(); - - n_env++; + r = strv_extend(&envp, n); + if (r < 0) + return r; } if (arg_inetd) { @@ -201,16 +161,17 @@ static int exec_process(const char *name, char **argv, char **env, int start_fd, safe_close(start_fd); } - if (asprintf((char **) (envp + n_env++), "LISTEN_FDS=%zu", n_fds) < 0) - return log_oom(); + r = strv_extendf(&envp, "LISTEN_FDS=%zu", n_fds); + if (r < 0) + return r; - if (asprintf((char **) (envp + n_env++), "LISTEN_PID=" PID_FMT, getpid_cached()) < 0) - return log_oom(); + r = strv_extendf(&envp, "LISTEN_PID=" PID_FMT, getpid_cached()); + if (r < 0) + return r; if (arg_fdnames) { _cleanup_free_ char *names = NULL; size_t len; - char *e; len = strv_length(arg_fdnames); if (len == 1) @@ -226,15 +187,23 @@ static int exec_process(const char *name, char **argv, char **env, int start_fd, if (!names) return log_oom(); - e = strjoin("LISTEN_FDNAMES=", names); - if (!e) + char *t = strjoin("LISTEN_FDNAMES=", names); + if (!t) return log_oom(); - envp[n_env++] = e; + r = strv_consume(&envp, t); + if (r < 0) + return r; } } - joined = strv_join(argv, " "); + STRV_FOREACH(s, arg_setenv) { + r = strv_env_replace_strdup(&envp, *s); + if (r < 0) + return r; + } + + _cleanup_free_ char *joined = strv_join(argv, " "); if (!joined) return log_oom(); @@ -244,7 +213,7 @@ static int exec_process(const char *name, char **argv, char **env, int start_fd, return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined); } -static int fork_and_exec_process(const char *child, char **argv, char **env, int fd) { +static int fork_and_exec_process(const char *child, char **argv, int fd) { _cleanup_free_ char *joined = NULL; pid_t child_pid; int r; @@ -260,7 +229,7 @@ static int fork_and_exec_process(const char *child, char **argv, char **env, int return r; if (r == 0) { /* In the child */ - exec_process(child, argv, env, fd, 1); + exec_process(child, argv, fd, 1); _exit(EXIT_FAILURE); } @@ -268,7 +237,7 @@ static int fork_and_exec_process(const char *child, char **argv, char **env, int return 0; } -static int do_accept(const char *name, char **argv, char **envp, int fd) { +static int do_accept(const char *name, char **argv, int fd) { _cleanup_free_ char *local = NULL, *peer = NULL; _cleanup_close_ int fd_accepted = -1; @@ -284,7 +253,7 @@ static int do_accept(const char *name, char **argv, char **envp, int fd) { (void) getpeername_pretty(fd_accepted, true, &peer); log_info("Connection from %s to %s", strna(peer), strna(local)); - return fork_and_exec_process(name, argv, envp, fd_accepted); + return fork_and_exec_process(name, argv, fd_accepted); } /* SIGCHLD handler. */ @@ -414,10 +383,9 @@ static int parse_argv(int argc, char *argv[]) { break; case 'E': - r = strv_extend(&arg_setenv, optarg); + r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); if (r < 0) - return log_oom(); - + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); break; case ARG_FDNAME: { @@ -471,7 +439,7 @@ static int parse_argv(int argc, char *argv[]) { return 1 /* work to do */; } -int main(int argc, char **argv, char **envp) { +int main(int argc, char **argv) { int r, n; int epoll_fd = -1; @@ -508,14 +476,14 @@ int main(int argc, char **argv, char **envp) { log_info("Communication attempt on fd %i.", event.data.fd); if (arg_accept) { - r = do_accept(argv[optind], argv + optind, envp, event.data.fd); + r = do_accept(argv[optind], argv + optind, event.data.fd); if (r < 0) return EXIT_FAILURE; } else break; } - exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, (size_t) n); + exec_process(argv[optind], argv + optind, SD_LISTEN_FDS_START, (size_t) n); return EXIT_SUCCESS; } diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 81b1e3f10e..cb49125644 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -183,39 +183,51 @@ static int env_append(char **r, char ***k, char **a) { return 0; } -char **strv_env_merge(size_t n_lists, ...) { - _cleanup_strv_free_ char **ret = NULL; - size_t n = 0; - char **l, **k; +char** _strv_env_merge(char **first, ...) { + _cleanup_strv_free_ char **merged = NULL; + char **k; va_list ap; /* Merges an arbitrary number of environment sets */ - va_start(ap, n_lists); - for (size_t i = 0; i < n_lists; i++) { + size_t n = strv_length(first); + + va_start(ap, first); + for (;;) { + char **l; + l = va_arg(ap, char**); + if (l == POINTER_MAX) + break; + n += strv_length(l); } va_end(ap); - ret = new(char*, n+1); - if (!ret) + k = merged = new(char*, n + 1); + if (!merged) + return NULL; + merged[0] = NULL; + + if (env_append(merged, &k, first) < 0) return NULL; - *ret = NULL; - k = ret; + va_start(ap, first); + for (;;) { + char **l; - va_start(ap, n_lists); - for (size_t i = 0; i < n_lists; i++) { l = va_arg(ap, char**); - if (env_append(ret, &k, l) < 0) { + if (l == POINTER_MAX) + break; + + if (env_append(merged, &k, l) < 0) { va_end(ap); return NULL; } } va_end(ap); - return TAKE_PTR(ret); + return TAKE_PTR(merged); } static bool env_match(const char *t, const char *pattern) { @@ -409,6 +421,32 @@ int strv_env_replace_strdup(char ***l, const char *assignment) { return strv_env_replace_consume(l, p); } +int strv_env_replace_strdup_passthrough(char ***l, const char *assignment) { + /* Like strv_env_replace_strdup(), but pulls the variable from the environment of + * the calling program, if a variable name without value is specified. + */ + char *p; + + if (strchr(assignment, '=')) { + if (!env_assignment_is_valid(assignment)) + return -EINVAL; + + p = strdup(assignment); + } else { + if (!env_name_is_valid(assignment)) + return -EINVAL; + + /* If we can't find the variable in our environment, we will use + * the empty string. This way "passthrough" is equivalent to passing + * --setenv=FOO=$FOO in the shell. */ + p = strjoin(assignment, "=", secure_getenv(assignment)); + } + if (!p) + return -ENOMEM; + + return strv_env_replace_consume(l, p); +} + int strv_env_assign(char ***l, const char *key, const char *value) { if (!env_name_is_valid(key)) return -EINVAL; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 1fbe7e4270..bee284b168 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -39,13 +39,15 @@ char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const cha bool strv_env_name_is_valid(char **l); bool strv_env_name_or_assignment_is_valid(char **l); -char **strv_env_merge(size_t n_lists, ...); +char** _strv_env_merge(char **first, ...); +#define strv_env_merge(first, ...) _strv_env_merge(first, __VA_ARGS__, POINTER_MAX) char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ char **strv_env_unset(char **l, const char *p); /* In place ... */ char **strv_env_unset_many(char **l, ...) _sentinel_; int strv_env_replace_consume(char ***l, char *p); /* In place ... */ int strv_env_replace_strdup(char ***l, const char *assignment); +int strv_env_replace_strdup_passthrough(char ***l, const char *assignment); int strv_env_assign(char ***l, const char *key, const char *value); char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index f6783e924a..5ea97b9194 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -2888,7 +2888,7 @@ int bus_exec_context_set_transient_property( if (!joined) return -ENOMEM; - e = strv_env_merge(2, c->environment, l); + e = strv_env_merge(c->environment, l); if (!e) return -ENOMEM; @@ -2922,7 +2922,7 @@ int bus_exec_context_set_transient_property( if (!joined) return -ENOMEM; - e = strv_env_merge(2, c->unset_environment, l); + e = strv_env_merge(c->unset_environment, l); if (!e) return -ENOMEM; diff --git a/src/core/execute.c b/src/core/execute.c index 6635a35abf..c1fad61554 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -4158,8 +4158,7 @@ static int exec_child( return log_oom(); } - accum_env = strv_env_merge(5, - params->environment, + accum_env = strv_env_merge(params->environment, our_env, pass_env, context->environment, @@ -5214,7 +5213,7 @@ static int exec_context_load_environment(const Unit *unit, const ExecContext *c, else { char **m; - m = strv_env_merge(2, r, p); + m = strv_env_merge(r, p); strv_free(r); strv_free(p); if (!m) diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c index 64761ddb11..59ddb9c487 100644 --- a/src/core/locale-setup.c +++ b/src/core/locale-setup.c @@ -85,7 +85,7 @@ int locale_setup(char ***environment) { else { char **merged; - merged = strv_env_merge(2, *environment, add); + merged = strv_env_merge(*environment, add); if (!merged) return -ENOMEM; diff --git a/src/core/manager.c b/src/core/manager.c index aab230e114..677b08e098 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -3672,7 +3672,7 @@ int manager_transient_environment_add(Manager *m, char **plus) { if (strv_isempty(plus)) return 0; - a = strv_env_merge(2, m->transient_environment, plus); + a = strv_env_merge(m->transient_environment, plus); if (!a) return log_oom(); @@ -3704,7 +3704,7 @@ int manager_client_environment_modify( } if (!strv_isempty(plus)) { - b = strv_env_merge(2, l, plus); + b = strv_env_merge(l, plus); if (!b) { strv_free(a); return -ENOMEM; @@ -3731,7 +3731,7 @@ int manager_get_effective_environment(Manager *m, char ***ret) { assert(m); assert(ret); - l = strv_env_merge(2, m->transient_environment, m->client_environment); + l = strv_env_merge(m->transient_environment, m->client_environment); if (!l) return -ENOMEM; diff --git a/src/core/service.c b/src/core/service.c index ddcfeb8523..4115db0a30 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -1546,7 +1546,7 @@ static int service_spawn( if (r < 0) return r; - final_env = strv_env_merge(2, exec_params.environment, our_env, NULL); + final_env = strv_env_merge(exec_params.environment, our_env); if (!final_env) return -ENOMEM; diff --git a/src/home/homectl.c b/src/home/homectl.c index d3dbf2d534..a4c0a47ce5 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2033,143 +2033,143 @@ static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] COMMAND ...\n\n" "%2$sCreate, manipulate or inspect home directories.%3$s\n" "\n%4$sCommands:%5$s\n" - " list List home areas\n" - " activate USER… Activate a home area\n" - " deactivate USER… Deactivate a home area\n" - " inspect USER… Inspect a home area\n" - " authenticate USER… Authenticate a home area\n" - " create USER Create a home area\n" - " remove USER… Remove a home area\n" - " update USER Update a home area\n" - " passwd USER Change password of a home area\n" - " resize USER SIZE Resize a home area\n" - " lock USER… Temporarily lock an active home area\n" - " unlock USER… Unlock a temporarily locked home area\n" - " lock-all Lock all suitable home areas\n" - " deactivate-all Deactivate all active home areas\n" - " with USER [COMMAND…] Run shell or command with access to a home area\n" + " list List home areas\n" + " activate USER… Activate a home area\n" + " deactivate USER… Deactivate a home area\n" + " inspect USER… Inspect a home area\n" + " authenticate USER… Authenticate a home area\n" + " create USER Create a home area\n" + " remove USER… Remove a home area\n" + " update USER Update a home area\n" + " passwd USER Change password of a home area\n" + " resize USER SIZE Resize a home area\n" + " lock USER… Temporarily lock an active home area\n" + " unlock USER… Unlock a temporarily locked home area\n" + " lock-all Lock all suitable home areas\n" + " deactivate-all Deactivate all active home areas\n" + " with USER [COMMAND…] Run shell or command with access to a home area\n" "\n%4$sOptions:%5$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --identity=PATH Read JSON identity from file\n" - " --json=FORMAT Output inspection data in JSON (takes one of\n" - " pretty, short, off)\n" - " -j Equivalent to --json=pretty (on TTY) or\n" - " --json=short (otherwise)\n" - " --export-format= Strip JSON inspection data (full, stripped,\n" - " minimal)\n" - " -E When specified once equals -j --export-format=\n" - " stripped, when specified twice equals\n" - " -j --export-format=minimal\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --identity=PATH Read JSON identity from file\n" + " --json=FORMAT Output inspection data in JSON (takes one of\n" + " pretty, short, off)\n" + " -j Equivalent to --json=pretty (on TTY) or\n" + " --json=short (otherwise)\n" + " --export-format= Strip JSON inspection data (full, stripped,\n" + " minimal)\n" + " -E When specified once equals -j --export-format=\n" + " stripped, when specified twice equals\n" + " -j --export-format=minimal\n" "\n%4$sGeneral User Record Properties:%5$s\n" - " -c --real-name=REALNAME Real name for user\n" - " --realm=REALM Realm to create user in\n" - " --email-address=EMAIL Email address for user\n" - " --location=LOCATION Set location of user on earth\n" - " --icon-name=NAME Icon name for user\n" - " -d --home-dir=PATH Home directory\n" - " -u --uid=UID Numeric UID for user\n" - " -G --member-of=GROUP Add user to group\n" - " --skel=PATH Skeleton directory to use\n" - " --shell=PATH Shell for account\n" - " --setenv=VARIABLE=VALUE Set an environment variable at log-in\n" - " --timezone=TIMEZONE Set a time-zone\n" - " --language=LOCALE Set preferred language\n" + " -c --real-name=REALNAME Real name for user\n" + " --realm=REALM Realm to create user in\n" + " --email-address=EMAIL Email address for user\n" + " --location=LOCATION Set location of user on earth\n" + " --icon-name=NAME Icon name for user\n" + " -d --home-dir=PATH Home directory\n" + " -u --uid=UID Numeric UID for user\n" + " -G --member-of=GROUP Add user to group\n" + " --skel=PATH Skeleton directory to use\n" + " --shell=PATH Shell for account\n" + " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" + " --timezone=TIMEZONE Set a time-zone\n" + " --language=LOCALE Set preferred language\n" " --ssh-authorized-keys=KEYS\n" - " Specify SSH public keys\n" - " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" - " private key and matching X.509 certificate\n" - " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n" - " extension\n" + " Specify SSH public keys\n" + " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" + " private key and matching X.509 certificate\n" + " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n" + " extension\n" " --fido2-with-client-pin=BOOL\n" - " Whether to require entering a PIN to unlock the\n" - " account\n" + " Whether to require entering a PIN to unlock the\n" + " account\n" " --fido2-with-user-presence=BOOL\n" - " Whether to require user presence to unlock the\n" - " account\n" + " Whether to require user presence to unlock the\n" + " account\n" " --fido2-with-user-verification=BOOL\n" - " Whether to require user verification to unlock the\n" - " account\n" - " --recovery-key=BOOL Add a recovery key\n" - "\n%4$sAccount Management User Record Properties:%5$s\n" - " --locked=BOOL Set locked account state\n" - " --not-before=TIMESTAMP Do not allow logins before\n" - " --not-after=TIMESTAMP Do not allow logins after\n" + " Whether to require user verification to unlock\n" + " the account\n" + " --recovery-key=BOOL Add a recovery key\n" + "\n%4$sAccount Management User Record Properties:%5$s\n" + " --locked=BOOL Set locked account state\n" + " --not-before=TIMESTAMP Do not allow logins before\n" + " --not-after=TIMESTAMP Do not allow logins after\n" " --rate-limit-interval=SECS\n" - " Login rate-limit interval in seconds\n" + " Login rate-limit interval in seconds\n" " --rate-limit-burst=NUMBER\n" - " Login rate-limit attempts per interval\n" + " Login rate-limit attempts per interval\n" "\n%4$sPassword Policy User Record Properties:%5$s\n" - " --password-hint=HINT Set Password hint\n" + " --password-hint=HINT Set Password hint\n" " --enforce-password-policy=BOOL\n" - " Control whether to enforce system's password\n" - " policy for this user\n" - " -P Equivalent to --enforce-password-password=no\n" + " Control whether to enforce system's password\n" + " policy for this user\n" + " -P Same as --enforce-password-password=no\n" " --password-change-now=BOOL\n" - " Require the password to be changed on next login\n" + " Require the password to be changed on next login\n" " --password-change-min=TIME\n" - " Require minimum time between password changes\n" + " Require minimum time between password changes\n" " --password-change-max=TIME\n" - " Require maximum time between password changes\n" + " Require maximum time between password changes\n" " --password-change-warn=TIME\n" - " How much time to warn before password expiry\n" + " How much time to warn before password expiry\n" " --password-change-inactive=TIME\n" - " How much time to block password after expiry\n" + " How much time to block password after expiry\n" "\n%4$sResource Management User Record Properties:%5$s\n" - " --disk-size=BYTES Size to assign the user on disk\n" - " --access-mode=MODE User home directory access mode\n" - " --umask=MODE Umask for user when logging in\n" - " --nice=NICE Nice level for user\n" + " --disk-size=BYTES Size to assign the user on disk\n" + " --access-mode=MODE User home directory access mode\n" + " --umask=MODE Umask for user when logging in\n" + " --nice=NICE Nice level for user\n" " --rlimit=LIMIT=VALUE[:VALUE]\n" - " Set resource limits\n" - " --tasks-max=MAX Set maximum number of per-user tasks\n" - " --memory-high=BYTES Set high memory threshold in bytes\n" - " --memory-max=BYTES Set maximum memory limit\n" - " --cpu-weight=WEIGHT Set CPU weight\n" - " --io-weight=WEIGHT Set IO weight\n" + " Set resource limits\n" + " --tasks-max=MAX Set maximum number of per-user tasks\n" + " --memory-high=BYTES Set high memory threshold in bytes\n" + " --memory-max=BYTES Set maximum memory limit\n" + " --cpu-weight=WEIGHT Set CPU weight\n" + " --io-weight=WEIGHT Set IO weight\n" "\n%4$sStorage User Record Properties:%5$s\n" - " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" - " subvolume, cifs)\n" - " --image-path=PATH Path to image file/directory\n" + " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" + " subvolume, cifs)\n" + " --image-path=PATH Path to image file/directory\n" "\n%4$sLUKS Storage User Record Properties:%5$s\n" - " --fs-type=TYPE File system type to use in case of luks\n" - " storage (btrfs, ext4, xfs)\n" - " --luks-discard=BOOL Whether to use 'discard' feature of file system\n" - " when activated (mounted)\n" + " --fs-type=TYPE File system type to use in case of luks\n" + " storage (btrfs, ext4, xfs)\n" + " --luks-discard=BOOL Whether to use 'discard' feature of file system\n" + " when activated (mounted)\n" " --luks-offline-discard=BOOL\n" - " Whether to trim file on logout\n" - " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n" - " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n" + " Whether to trim file on logout\n" + " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n" + " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n" " --luks-volume-key-size=BITS\n" - " Volume key size to use for LUKS encryption\n" - " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n" + " Volume key size to use for LUKS encryption\n" + " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n" " --luks-pbkdf-hash-algorithm=ALGORITHM\n" - " PBKDF hash algorithm to use\n" + " PBKDF hash algorithm to use\n" " --luks-pbkdf-time-cost=SECS\n" - " Time cost for PBKDF in seconds\n" + " Time cost for PBKDF in seconds\n" " --luks-pbkdf-memory-cost=BYTES\n" - " Memory cost for PBKDF in bytes\n" + " Memory cost for PBKDF in bytes\n" " --luks-pbkdf-parallel-threads=NUMBER\n" - " Number of parallel threads for PKBDF\n" + " Number of parallel threads for PKBDF\n" "\n%4$sMounting User Record Properties:%5$s\n" - " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" - " --nodev=BOOL Control the 'nodev' flag of the home mount\n" - " --noexec=BOOL Control the 'noexec' flag of the home mount\n" + " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" + " --nodev=BOOL Control the 'nodev' flag of the home mount\n" + " --noexec=BOOL Control the 'noexec' flag of the home mount\n" "\n%4$sCIFS User Record Properties:%5$s\n" - " --cifs-domain=DOMAIN CIFS (Windows) domain\n" - " --cifs-user-name=USER CIFS (Windows) user name\n" - " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n" + " --cifs-domain=DOMAIN CIFS (Windows) domain\n" + " --cifs-user-name=USER CIFS (Windows) user name\n" + " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n" "\n%4$sLogin Behaviour User Record Properties:%5$s\n" - " --stop-delay=SECS How long to leave user services running after\n" - " logout\n" - " --kill-processes=BOOL Whether to kill user processes when sessions\n" - " terminate\n" - " --auto-login=BOOL Try to log this user in automatically\n" + " --stop-delay=SECS How long to leave user services running after\n" + " logout\n" + " --kill-processes=BOOL Whether to kill user processes when sessions\n" + " terminate\n" + " --auto-login=BOOL Try to log this user in automatically\n" "\nSee the %6$s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -2673,10 +2673,6 @@ static int parse_argv(int argc, char *argv[]) { break; } - if (!env_assignment_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Environment assignment '%s' not valid.", optarg); - e = json_variant_by_key(arg_identity_extra, "environment"); if (e) { r = json_variant_strv(e, &l); @@ -2684,9 +2680,9 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse JSON environment field: %m"); } - r = strv_env_replace_strdup(&l, optarg); + r = strv_env_replace_strdup_passthrough(&l, optarg); if (r < 0) - return log_error_errno(r, "Failed to replace JSON environment field: %m"); + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); strv_sort(l); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index b4c251078b..3f4ad87897 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -2510,7 +2510,7 @@ static int help(int argc, char *argv[], void *userdata) { " --kill-who=WHO Who to send signal to\n" " -s --signal=SIGNAL Which signal to send\n" " --uid=USER Specify user ID to invoke shell as\n" - " -E --setenv=VAR=VALUE Add an environment variable for shell\n" + " -E --setenv=VAR[=VALUE] Add an environment variable for shell\n" " --read-only Create read-only bind mount\n" " --mkdir Create directory before bind mounting, if missing\n" " -n --lines=INTEGER Number of journal entries to show\n" @@ -2765,13 +2765,9 @@ static int parse_argv(int argc, char *argv[]) { break; case 'E': - if (!env_assignment_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Environment assignment invalid: %s", optarg); - - r = strv_extend(&arg_setenv, optarg); + r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); if (r < 0) - return log_oom(); + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); break; case ARG_MAX_ADDRESSES: diff --git a/src/notify/notify.c b/src/notify/notify.c index 49d5f3ec92..b468a5bc44 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -232,7 +232,7 @@ static int run(int argc, char* argv[]) { our_env[i++] = NULL; - final_env = strv_env_merge(2, our_env, argv + optind); + final_env = strv_env_merge(our_env, argv + optind); if (!final_env) return log_oom(); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index d75cce408e..3d7e20e72e 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -338,7 +338,7 @@ static int help(void) { " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" " -b --boot Boot up full system (i.e. invoke init)\n" " --chdir=PATH Set working directory in the container\n" - " -E --setenv=NAME=VALUE Pass an environment variable to PID 1\n" + " -E --setenv=NAME[=VALUE] Pass an environment variable to PID 1\n" " -u --user=USER Run the command under specified user or UID\n" " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" " --notify-ready=BOOLEAN Receive notifications from the child init process\n\n" @@ -1121,17 +1121,13 @@ static int parse_argv(int argc, char *argv[]) { arg_settings_mask |= SETTING_CUSTOM_MOUNTS; break; - case 'E': { - if (!env_assignment_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Environment variable assignment '%s' is not valid.", optarg); - r = strv_env_replace_strdup(&arg_setenv, optarg); + case 'E': + r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); if (r < 0) - return r; + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); arg_settings_mask |= SETTING_ENVIRONMENT; break; - } case 'q': arg_quiet = true; @@ -3189,8 +3185,8 @@ static int inner_child( _cleanup_free_ char *home = NULL; char as_uuid[ID128_UUID_STRING_MAX]; size_t n_env = 1; - const char *envp[] = { - "PATH=" DEFAULT_PATH_COMPAT, + char *envp[] = { + (char*) "PATH=" DEFAULT_PATH_COMPAT, NULL, /* container */ NULL, /* TERM */ NULL, /* HOME */ @@ -3426,17 +3422,17 @@ static int inner_child( n_env++; if (home || !uid_is_valid(arg_uid) || arg_uid == 0) - if (asprintf((char**)(envp + n_env++), "HOME=%s", home ?: "/root") < 0) + if (asprintf(envp + n_env++, "HOME=%s", home ?: "/root") < 0) return log_oom(); if (arg_user || !uid_is_valid(arg_uid) || arg_uid == 0) - if (asprintf((char**)(envp + n_env++), "USER=%s", arg_user ?: "root") < 0 || - asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0) + if (asprintf(envp + n_env++, "USER=%s", arg_user ?: "root") < 0 || + asprintf(envp + n_env++, "LOGNAME=%s", arg_user ? arg_user : "root") < 0) return log_oom(); assert(!sd_id128_is_null(arg_uuid)); - if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_to_uuid_string(arg_uuid, as_uuid)) < 0) + if (asprintf(envp + n_env++, "container_uuid=%s", id128_to_uuid_string(arg_uuid, as_uuid)) < 0) return log_oom(); if (fdset_size(fds) > 0) { @@ -3444,11 +3440,11 @@ static int inner_child( if (r < 0) return log_error_errno(r, "Failed to unset O_CLOEXEC for file descriptors."); - if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", fdset_size(fds)) < 0) || - (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) + if ((asprintf(envp + n_env++, "LISTEN_FDS=%u", fdset_size(fds)) < 0) || + (asprintf(envp + n_env++, "LISTEN_PID=1") < 0)) return log_oom(); } - if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0) + if (asprintf(envp + n_env++, "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0) return log_oom(); if (arg_n_credentials > 0) { @@ -3458,7 +3454,7 @@ static int inner_child( n_env++; } - env_use = strv_env_merge(3, envp, os_release_pairs, arg_setenv); + env_use = strv_env_merge(envp, os_release_pairs, arg_setenv); if (!env_use) return log_oom(); diff --git a/src/run/run.c b/src/run/run.c index 0be974f03a..fb38571228 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -111,7 +111,7 @@ static int help(void) { " --nice=NICE Nice level\n" " --working-directory=PATH Set working directory\n" " -d --same-dir Inherit working directory from caller\n" - " -E --setenv=NAME=VALUE Set environment\n" + " -E --setenv=NAME[=VALUE] Set environment variable\n" " -t --pty Run service on pseudo TTY as STDIN/STDOUT/\n" " STDERR\n" " -P --pipe Pass STDIN/STDOUT/STDERR directly to service\n" @@ -322,8 +322,9 @@ static int parse_argv(int argc, char *argv[]) { break; case 'E': - if (strv_extend(&arg_environment, optarg) < 0) - return log_oom(); + r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); break; @@ -1526,7 +1527,7 @@ static int start_transient_scope(sd_bus *bus) { return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); } - env = strv_env_merge(3, environ, user_env, arg_environment); + env = strv_env_merge(environ, user_env, arg_environment); if (!env) return log_oom(); diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index ed4580e4af..c689d5590e 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -73,31 +73,28 @@ static void test_strv_env_unset(void) { static void test_strv_env_merge(void) { log_info("/* %s */", __func__); - _cleanup_strv_free_ char **a = NULL, **b = NULL, **r = NULL; + char **a = STRV_MAKE("FOO=BAR", "WALDO=WALDO", "WALDO=", "PIEP", "SCHLUMPF=SMURF", "EQ==="); + char **b = STRV_MAKE("FOO=KKK", "FOO=", "PIEP=", "SCHLUMPF=SMURFF", "NANANANA=YES"); - a = strv_new("FOO=BAR", "WALDO=WALDO", "WALDO=", "PIEP", "SCHLUMPF=SMURF"); - assert_se(a); - - b = strv_new("FOO=KKK", "FOO=", "PIEP=", "SCHLUMPF=SMURFF", "NANANANA=YES"); - assert_se(b); - - r = strv_env_merge(2, a, b); + _cleanup_strv_free_ char **r = strv_env_merge(NULL, a, NULL, b, NULL, a, b, b, NULL); assert_se(r); assert_se(streq(r[0], "FOO=")); assert_se(streq(r[1], "WALDO=")); assert_se(streq(r[2], "PIEP")); assert_se(streq(r[3], "SCHLUMPF=SMURFF")); - assert_se(streq(r[4], "PIEP=")); - assert_se(streq(r[5], "NANANANA=YES")); - assert_se(strv_length(r) == 6); + assert_se(streq(r[4], "EQ===")); + assert_se(streq(r[5], "PIEP=")); + assert_se(streq(r[6], "NANANANA=YES")); + assert_se(strv_length(r) == 7); assert_se(strv_env_clean(r) == r); assert_se(streq(r[0], "FOO=")); assert_se(streq(r[1], "WALDO=")); assert_se(streq(r[2], "SCHLUMPF=SMURFF")); - assert_se(streq(r[3], "PIEP=")); - assert_se(streq(r[4], "NANANANA=YES")); - assert_se(strv_length(r) == 5); + assert_se(streq(r[3], "EQ===")); + assert_se(streq(r[4], "PIEP=")); + assert_se(streq(r[5], "NANANANA=YES")); + assert_se(strv_length(r) == 6); } static void test_strv_env_replace_strdup(void) { @@ -108,6 +105,7 @@ static void test_strv_env_replace_strdup(void) { assert_se(strv_env_replace_strdup(&a, "a=a") == 1); assert_se(strv_env_replace_strdup(&a, "b=b") == 1); assert_se(strv_env_replace_strdup(&a, "a=A") == 0); + assert_se(strv_env_replace_strdup(&a, "c") == -EINVAL); assert_se(strv_length(a) == 2); strv_sort(a); @@ -115,6 +113,27 @@ static void test_strv_env_replace_strdup(void) { assert_se(streq(a[1], "b=b")); } +static void test_strv_env_replace_strdup_passthrough(void) { + log_info("/* %s */", __func__); + + _cleanup_strv_free_ char **a = NULL; + + assert_se(putenv((char*) "a=a") == 0); + assert_se(putenv((char*) "b=") == 0); + assert_se(unsetenv("c") == 0); + + assert_se(strv_env_replace_strdup_passthrough(&a, "a") == 1); + assert_se(strv_env_replace_strdup_passthrough(&a, "b") == 1); + assert_se(strv_env_replace_strdup_passthrough(&a, "c") == 1); + assert_se(strv_env_replace_strdup_passthrough(&a, "a") == 0); + assert_se(strv_env_replace_strdup_passthrough(&a, "$a") == -EINVAL); + + assert_se(strv_length(a) == 3); + assert_se(streq(a[0], "a=a")); + assert_se(streq(a[1], "b=")); + assert_se(streq(a[2], "c=")); +} + static void test_strv_env_assign(void) { log_info("/* %s */", __func__); @@ -418,6 +437,7 @@ int main(int argc, char *argv[]) { test_strv_env_unset(); test_strv_env_merge(); test_strv_env_replace_strdup(); + test_strv_env_replace_strdup_passthrough(); test_strv_env_assign(); test_env_strv_get_n(); test_replace_env(false);