From 4ab3d29ff03c0508f47846875d9310cbc5b8cd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 7 Aug 2021 10:16:19 +0200 Subject: [PATCH 1/9] Add implicit sentinel to strv_env_merge() Just to make it a tiny bit nicer to use. --- src/basic/env-util.c | 40 ++++++++++++++++++++++++++-------------- src/basic/env-util.h | 3 ++- src/core/dbus-execute.c | 4 ++-- src/core/execute.c | 5 ++--- src/core/locale-setup.c | 2 +- src/core/manager.c | 6 +++--- src/core/service.c | 2 +- src/notify/notify.c | 2 +- src/nspawn/nspawn.c | 20 ++++++++++---------- src/run/run.c | 2 +- src/test/test-env-util.c | 2 +- 11 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 81b1e3f10e..0c30ddc277 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) { diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 1fbe7e4270..18d10ebab8 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -39,7 +39,8 @@ 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 ... */ 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 4608956259..5bee44ac7b 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 24dfe9fc06..d76b7b2b16 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/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..cf89b27dfa 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3189,8 +3189,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 +3426,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 +3444,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 +3458,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..993f1bc4f4 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1526,7 +1526,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..5bf130ed86 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -81,7 +81,7 @@ static void test_strv_env_merge(void) { b = strv_new("FOO=KKK", "FOO=", "PIEP=", "SCHLUMPF=SMURFF", "NANANANA=YES"); assert_se(b); - r = strv_env_merge(2, a, b); + r = strv_env_merge(a, b); assert_se(r); assert_se(streq(r[0], "FOO=")); assert_se(streq(r[1], "WALDO=")); From 43cadb64d47ffc1d72571b2a2e494f383d8df83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 16:53:00 +0200 Subject: [PATCH 2/9] test-env-util: extend the test for strv_env_merge() a bit --- src/test/test-env-util.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index 5bf130ed86..91c3c252de 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(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) { From a14af47e64ca811c17470e021b22d05a16f4d46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:03:12 +0200 Subject: [PATCH 3/9] basic/env-util: add a mode where we pull in the variable value from environment --- src/basic/env-util.c | 26 ++++++++++++++++++++++++++ src/basic/env-util.h | 1 + src/test/test-env-util.c | 23 +++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 0c30ddc277..cb49125644 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -421,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 18d10ebab8..bee284b168 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -47,6 +47,7 @@ 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/test/test-env-util.c b/src/test/test-env-util.c index 91c3c252de..c689d5590e 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -105,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); @@ -112,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__); @@ -415,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); From 0d2a017986565f23a624bdd8b7b06a267b0676ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:11:56 +0200 Subject: [PATCH 4/9] nspawn: allow --setenv=FOO as equivalent to --setenv=FOO=$FOO systemd-socket-activate has supported such a mode since 5e65c93a433447b15180249166f7b3944c3e6156. '--setenv=FOO=$FOO' is a fairly common use in scripts, and it's nicer to do this automatically without worrying about quoting and whatnot. https://github.com/systemd/mkosi/pull/765 added the same to 'mkosi --environment='. --- man/systemd-nspawn.xml | 14 +++++++------- src/nspawn/nspawn.c | 12 ++++-------- 2 files changed, 11 insertions(+), 15 deletions(-) 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/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index cf89b27dfa..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; From 4bbafcc359bf12f19f1ec232a2412195183d4ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:19:31 +0200 Subject: [PATCH 5/9] homectl: allow --setenv=FOO --- man/homectl.xml | 14 ++- src/home/homectl.c | 226 ++++++++++++++++++++++----------------------- 2 files changed, 120 insertions(+), 120 deletions(-) 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/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); From 0337b3d51c848edb3c0532f58416be3a87ae70b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:25:47 +0200 Subject: [PATCH 6/9] run: allow --setenv=FOO --- man/systemd-run.xml | 12 ++++++++---- src/run/run.c | 7 ++++--- 2 files changed, 12 insertions(+), 7 deletions(-) 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/run/run.c b/src/run/run.c index 993f1bc4f4..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; From 89bf86e0157316a44457fb955c6d3deeb02e9802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:28:47 +0200 Subject: [PATCH 7/9] machinectl: allow --setenv=FOO --- man/machinectl.xml | 16 +++++++++------- src/machine/machinectl.c | 10 +++------- 2 files changed, 12 insertions(+), 14 deletions(-) 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/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: From 2f400671da769b996e9b7fd6d20b1cba74f84148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:34:07 +0200 Subject: [PATCH 8/9] activate: use global variable instead of passing char **envp around The effect should be the same, but the code is less verbose. In particular, the variable was called envp in parts of the code, but in other parts, we had a local envp variable, and envp was called env. --- src/activate/activate.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/activate/activate.c b/src/activate/activate.c index 18ce491a84..79fdd0cdbf 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -121,7 +121,7 @@ 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; @@ -158,7 +158,7 @@ static int exec_process(const char *name, char **argv, char **env, int start_fd, if (!p) return log_oom(); - n = strv_find_prefix(env, p); + n = strv_find_prefix(environ, p); if (!n) continue; @@ -173,7 +173,7 @@ static int exec_process(const char *name, char **argv, char **env, int start_fd, FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { const char *n; - n = strv_find_prefix(env, tocopy); + n = strv_find_prefix(environ, tocopy); if (!n) continue; @@ -244,7 +244,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 +260,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 +268,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 +284,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. */ @@ -471,7 +471,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 +508,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; } From 76e68b3db35fab05940496b45cf9681dc7707db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 10 Aug 2021 17:58:17 +0200 Subject: [PATCH 9/9] activate: simplify/rework implementation of --setenv Previous implementation is simplified by using the new helper. The new code does more looping, but considering that it's unlikely that people set more than a handful of variables through commandline options, this should be OK. If a variable is specified on the command line, it overrides any automatically set variable. Effective behaviour was already were like this, because we would specify two variables, both would be set, and since glibc will return the first matching entry. ('systemd-socket-activate -E TERM=FOO -l 2000 --inetd -a env' would give 'TERM=FOO TERM=xterm-256color PATH=...', and getenv("TERM") returns "FOO".) But it's nicer to filter out any duplicate entries and only pass the intended variable to the child process. --- src/activate/activate.c | 86 +++++++++++++---------------------------- 1 file changed, 27 insertions(+), 59 deletions(-) diff --git a/src/activate/activate.c b/src/activate/activate.c index 79fdd0cdbf..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" @@ -123,9 +124,7 @@ static int open_sockets(int *epoll_fd, bool accept) { 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, int start_fd, size_t n_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(environ, 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(environ, 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, int start_fd, size_t n_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, int start_fd, size_t n_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(); @@ -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: {