Merge pull request #20419 from keszybz/setenv-no-value

Allow --setenv=FOO in various programs
This commit is contained in:
Lennart Poettering 2021-08-11 17:47:45 +02:00 committed by GitHub
commit a0c5a3f0c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 300 additions and 274 deletions

View file

@ -288,12 +288,16 @@
</varlistentry>
<varlistentry>
<term><option>--setenv=</option><replaceable>VARIABLE</replaceable>=<replaceable>VALUE</replaceable></term>
<term><option>--setenv=</option><replaceable>VARIABLE</replaceable>[=<replaceable>VALUE</replaceable>]</term>
<listitem><para>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
<option>--email=</option>, <option>--timezone=</option> and <option>--language=</option>. May be used
multiple times to set multiple environment variables.</para></listitem>
<listitem><para>Takes an environment variable assignment to set for all user processes. May be used
multiple times to set multiple environment variables. When <literal>=</literal> and
<replaceable>VALUE</replaceable> are omitted, the value of the variable with the same name in the
program environment will be used.</para>
<para>Note that a number of other settings also result in environment variables to be set for the
user, including <option>--email=</option>, <option>--timezone=</option> and
<option>--language=</option>.</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -712,14 +712,16 @@
</varlistentry>
<varlistentry>
<term><option>-E <replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term>
<term><option>-E <replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<listitem><para>When used with the <command>shell</command> command, sets an environment
variable to pass to the executed shell. Takes an environment variable name and value,
separated by <literal>=</literal>. This switch may be used multiple times to set multiple
environment variables. Note that this switch is not supported for the
<command>login</command> command (see below).</para></listitem>
<listitem><para>When used with the <command>shell</command> command, sets an environment variable for
the executed shell. This option may be used more than once to set multiple variables. When
<literal>=</literal> and <replaceable>VALUE</replaceable> are omitted, the value of the variable with
the same name in the program environment will be used.</para>
<para>Note that this option is not supported for the <command>login</command> command.
</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -527,14 +527,14 @@
</varlistentry>
<varlistentry>
<term><option>-E <replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term>
<term><option>-E <replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<listitem><para>Specifies an environment variable assignment
to pass to the init process in the container, in the format
<literal>NAME=VALUE</literal>. This may be used to override
the default variables or to set additional variables. This
parameter may be used more than once.</para></listitem>
<listitem><para>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 <literal>=</literal> and <replaceable>VALUE</replaceable>
are omitted, the value of the variable with the same name in the program environment will be used.
</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -239,11 +239,15 @@
</varlistentry>
<varlistentry>
<term><option>-E <replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>=<replaceable>VALUE</replaceable></option></term>
<term><option>-E <replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<listitem><para>Runs the service process with the specified environment variable set.
Also see <varname>Environment=</varname> in
<listitem><para>Runs the service process with the specified environment variable set. This parameter
may be used more than once to set multiple variables. When <literal>=</literal> and
<replaceable>VALUE</replaceable> are omitted, the value of the variable with the same name in the
program environment will be used.</para>
<para>Also see <varname>Environment=</varname> in
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>

View file

@ -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;
}

View file

@ -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;

View file

@ -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_;

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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:

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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);