From 88d2ec272d3e503412e477d9abaebfe2ca199e78 Mon Sep 17 00:00:00 2001 From: Filipe Brandenburger Date: Sun, 6 Sep 2015 23:06:53 -0700 Subject: [PATCH] execute: Add new PassEnvironment= directive This directive allows passing environment variables from the system manager to spawned services. Variables in the system manager can be set inside a container by passing `--set-env=...` options to systemd-spawn. Tested with an on-disk test.service unit. Tested using multiple variable names on a single line, with an empty setting to clear the current list of variables, with non-existing variables. Tested using `systemd-run -p PassEnvironment=VARNAME` to confirm it works with transient units. Confirmed that `systemctl show` will display the PassEnvironment settings. Checked that man pages are generated correctly. No regressions in `make check`. (cherry picked from commit b4c14404b3e8753c41bac0b1d49369230a15c544) Resolves: #1426214 --- man/systemd.exec.xml | 27 +++++++++++ shell-completion/bash/systemd-run | 2 +- src/core/dbus-execute.c | 34 ++++++++++++++ src/core/execute.c | 44 +++++++++++++++++- src/core/execute.h | 1 + src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/load-fragment.c | 65 +++++++++++++++++++++++++++ src/core/load-fragment.h | 1 + src/libsystemd/sd-bus/bus-util.c | 2 +- src/shared/env-util.c | 15 +++++++ src/shared/env-util.h | 1 + 11 files changed, 189 insertions(+), 4 deletions(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index c5199d3a5..aa5831cc2 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -293,6 +293,33 @@ earlier setting. + + PassEnvironment= + + Pass environment variables from the systemd system + manager to executed processes. Takes a space-separated list of variable + names. This option may be specified more than once, in which case all + listed variables will be set. If the empty string is assigned to this + option, the list of environment variables is reset, all prior + assignments have no effect. Variables that are not set in the system + manager will not be passed and will be silently ignored. + + Variables passed from this setting are overridden by those passed + from Environment= or + EnvironmentFile=. + + Example: + PassEnvironment=VAR1 VAR2 VAR3 + passes three variables VAR1, + VAR2, VAR3 + with the values set for those variables in PID1. + + + See + environ7 + for details about environment variables. + + StandardInput= Controls where file descriptor 0 (STDIN) of diff --git a/shell-completion/bash/systemd-run b/shell-completion/bash/systemd-run index 5145cd3f2..36ffa46db 100644 --- a/shell-completion/bash/systemd-run +++ b/shell-completion/bash/systemd-run @@ -73,7 +73,7 @@ _systemd_run() { KillSignal= LimitCPU= LimitFSIZE= LimitDATA= LimitSTACK= LimitCORE= LimitRSS= LimitNOFILE= LimitAS= LimitNPROC= LimitMEMLOCK= LimitLOCKS= LimitSIGPENDING= LimitMSGQUEUE= - LimitNICE= LimitRTPRIO= LimitRTTIME=' + LimitNICE= LimitRTPRIO= LimitRTTIME= PassEnvironment=' COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index a9f7971cd..da8b10d2b 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -597,6 +597,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LimitCPU", "t", property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LimitFSIZE", "t", property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), @@ -963,6 +964,39 @@ int bus_exec_context_set_transient_property( return 1; + } else if (streq(name, "PassEnvironment")) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + if (!strv_env_name_is_valid(l)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block."); + + if (mode != UNIT_CHECK) { + if (strv_isempty(l)) { + strv_free(c->pass_environment); + c->pass_environment = NULL; + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n"); + } else { + _cleanup_free_ char *joined = NULL; + + r = strv_extend_strv(&c->pass_environment, l); + if (r < 0) + return r; + + joined = strv_join_quoted(c->pass_environment); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined); + } + } + + return 1; + } else if (rlimit_from_string(name) >= 0) { uint64_t rl; rlim_t x; diff --git a/src/core/execute.c b/src/core/execute.c index 863babd76..f72b20966 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1256,6 +1256,34 @@ static int build_environment( return 0; } +static int build_pass_environment(const ExecContext *c, char ***ret) { + _cleanup_strv_free_ char **pass_env = NULL; + size_t n_env = 0, n_bufsize = 0; + char **i; + + STRV_FOREACH(i, c->pass_environment) { + _cleanup_free_ char *x = NULL; + char *v; + + v = getenv(*i); + if (!v) + continue; + x = strjoin(*i, "=", v, NULL); + if (!x) + return -ENOMEM; + if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) + return -ENOMEM; + pass_env[n_env++] = x; + pass_env[n_env] = NULL; + x = NULL; + } + + *ret = pass_env; + pass_env = NULL; + + return 0; +} + static bool exec_needs_mount_namespace( const ExecContext *context, const ExecParameters *params, @@ -1297,7 +1325,7 @@ static int exec_child( char **files_env, int *exit_status) { - _cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; + _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; _cleanup_free_ char *mac_selinux_context_net = NULL; const char *username = NULL, *home = NULL, *shell = NULL; unsigned n_dont_close = 0; @@ -1805,9 +1833,16 @@ static int exec_child( return r; } - final_env = strv_env_merge(5, + r = build_pass_environment(context, &pass_env); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return r; + } + + final_env = strv_env_merge(6, params->environment, our_env, + pass_env, context->environment, files_env, pam_env, @@ -1965,6 +2000,8 @@ void exec_context_done(ExecContext *c) { strv_free(c->environment_files); c->environment_files = NULL; + strv_free(c->pass_environment); + c->pass_environment = NULL; for (l = 0; l < ELEMENTSOF(c->rlimit); l++) { free(c->rlimit[l]); @@ -2267,6 +2304,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { STRV_FOREACH(e, c->environment_files) fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); + STRV_FOREACH(e, c->pass_environment) + fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); + if (c->nice_set) fprintf(f, "%sNice: %i\n", diff --git a/src/core/execute.h b/src/core/execute.h index 6e0c9faa7..cadd0e6b4 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -96,6 +96,7 @@ struct ExecRuntime { struct ExecContext { char **environment; char **environment_files; + char **pass_environment; struct rlimit *rlimit[_RLIMIT_MAX]; char *working_directory, *root_directory; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index c866a9cd0..b50fe45b4 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -33,6 +33,7 @@ $1.CPUAffinity, config_parse_exec_cpu_affinity, 0, $1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) $1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment) $1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) +$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment) $1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input) $1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output) $1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 3a3c456da..c450fe2c7 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2265,6 +2265,71 @@ int config_parse_environ(const char *unit, return 0; } +int config_parse_pass_environ(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + const char *whole_rvalue = rvalue; + char*** passenv = data; + _cleanup_strv_free_ char **n = NULL; + size_t nlen = 0, nbufsize = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + strv_free(*passenv); + *passenv = NULL; + return 0; + } + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue); + break; + } + + if (!env_name_is_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid environment name for %s, ignoring: %s", lvalue, word); + continue; + } + + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + n[nlen++] = word; + n[nlen] = NULL; + word = NULL; + } + + if (n) { + r = strv_extend_strv(passenv, n); + if (r < 0) + return r; + } + + return 0; +} + int config_parse_ip_tos(const char *unit, const char *filename, unsigned line, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 7c69e5369..9dd7d1bda 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -85,6 +85,7 @@ int config_parse_syscall_filter(const char *unit, const char *filename, unsigned int config_parse_syscall_archs(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_syscall_errno(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_environ(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_pass_environ(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_unit_slice(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/libsystemd/sd-bus/bus-util.c b/src/libsystemd/sd-bus/bus-util.c index d35776087..ed0849b63 100644 --- a/src/libsystemd/sd-bus/bus-util.c +++ b/src/libsystemd/sd-bus/bus-util.c @@ -1535,7 +1535,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "v", "i", i); - } else if (streq(field, "Environment")) { + } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { r = sd_bus_message_append(m, "v", "as", 1, eq); diff --git a/src/shared/env-util.c b/src/shared/env-util.c index e8da4c978..581d84a20 100644 --- a/src/shared/env-util.c +++ b/src/shared/env-util.c @@ -136,6 +136,21 @@ bool strv_env_is_valid(char **e) { return true; } +bool strv_env_name_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + bool strv_env_name_or_assignment_is_valid(char **l) { char **p, **q; diff --git a/src/shared/env-util.h b/src/shared/env-util.h index 252d87be1..b8c2d81e4 100644 --- a/src/shared/env-util.h +++ b/src/shared/env-util.h @@ -34,6 +34,7 @@ bool strv_env_is_valid(char **e); #define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); +bool strv_env_name_is_valid(char **l); bool strv_env_name_or_assignment_is_valid(char **l); char **strv_env_merge(unsigned n_lists, ...);