]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
execute: Add new PassEnvironment= directive
authorFilipe Brandenburger <filbranden@google.com>
Mon, 7 Sep 2015 06:06:53 +0000 (23:06 -0700)
committerFilipe Brandenburger <filbranden@google.com>
Wed, 11 Nov 2015 15:55:23 +0000 (07:55 -0800)
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`.

13 files changed:
TODO
man/systemd.exec.xml
shell-completion/bash/systemd-run
shell-completion/zsh/_systemd-run
src/basic/env-util.c
src/basic/env-util.h
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/shared/bus-util.c

diff --git a/TODO b/TODO
index 5dc3d1ffa36ee94d92ba9f7387a31390b2936292..e0d849994cecf680a1e497aee856371358384e32 100644 (file)
--- a/TODO
+++ b/TODO
@@ -79,8 +79,6 @@ Features:
   prefixed with /sys generally special.
   http://lists.freedesktop.org/archives/systemd-devel/2015-June/032962.html
 
-* Add PassEnvironment= setting to service units, to import select env vars from PID 1 into the service env block
-
 * nspawn: fix logic always print a final newline on output.
   https://github.com/systemd/systemd/pull/272#issuecomment-113153176
 
index 2b090871ff82245345d6e37e61ad3bd24f13f979..5bb97bd98e4c09d3ebb36f8b32955f281a5df5d3 100644 (file)
         earlier setting.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>PassEnvironment=</varname></term>
+
+        <listitem><para>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.</para>
+
+        <para>Variables passed from this setting are overridden by those passed
+        from <varname>Environment=</varname> or
+        <varname>EnvironmentFile=</varname>.</para>
+
+        <para>Example:
+        <programlisting>PassEnvironment=VAR1 VAR2 VAR3</programlisting>
+        passes three variables <literal>VAR1</literal>,
+        <literal>VAR2</literal>, <literal>VAR3</literal>
+        with the values set for those variables in PID1.</para>
+
+        <para>
+        See
+        <citerefentry project='man-pages'><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+        for details about environment variables.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>StandardInput=</varname></term>
         <listitem><para>Controls where file descriptor 0 (STDIN) of
index 7379431b71709289305b7b472083c935d58a67d6..8152b021e779d8c0f1f882ed0f3d7cf5af9242ca 100644 (file)
@@ -86,7 +86,7 @@ _systemd_run() {
                          TTYPath= SyslogIdentifier= SyslogLevelPrefix= SyslogLevel=
                          SyslogFacility= TimerSlackNSec= OOMScoreAdjust= ReadWriteDirectories=
                          ReadOnlyDirectories= InaccessibleDirectories= EnvironmentFile=
-                         ProtectSystem= ProtectHome= RuntimeDirectory='
+                         ProtectSystem= ProtectHome= RuntimeDirectory= PassEnvironment='
 
             COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
             return 0
index 8bb0156a8522b5a76481a0f100e95b6a4ca933b6..c425085cd801c855981875cd935cb4c3bb1e9f40 100644 (file)
@@ -39,7 +39,7 @@ _arguments \
                 TTYPath= SyslogIdentifier= SyslogLevelPrefix= SyslogLevel= \
                 SyslogFacility= TimerSlackNSec= OOMScoreAdjust= ReadWriteDirectories= \
                 ReadOnlyDirectories= InaccessibleDirectories= EnvironmentFile= \
-                ProtectSystem= ProtectHome= RuntimeDirectory= \
+                ProtectSystem= ProtectHome= RuntimeDirectory= PassEnvironment= \
                 ))' \
         '--description=[Description for unit]:description' \
         '--slice=[Run in the specified slice]:slices:__slices' \
index 9ddac5d6a1c39e967c576d6cd0de6356c5375879..441169db311eb3118be49ff36267d26ebd168a83 100644 (file)
@@ -138,6 +138,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;
 
index 6485dade1857e16ed0f1e214b7a39ba8d0a0cbad..5efffa3dc72483952f2a44bc2f853a1e294dd1b8 100644 (file)
@@ -36,6 +36,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, ...);
index db4206a5233d03eb69bda39bf797a0189aaa0e71..093179c003f2612af8aff284e001862a83ada58e 100644 (file)
@@ -629,6 +629,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", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1289,6 +1290,38 @@ 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)) {
+                                c->pass_environment = strv_free(c->pass_environment);
+                                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, true);
+                                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 (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) {
 
                 _cleanup_strv_free_ char **l = NULL;
index d751065af08af847aeb6d12c343345ade93ad159..07979bf8b30934aa6cb245d2fbf0f93d0f162051 100644 (file)
@@ -1332,6 +1332,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,
@@ -1412,7 +1440,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, *wd;
         uid_t uid = UID_INVALID;
@@ -1928,9 +1956,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,
@@ -2088,6 +2123,7 @@ void exec_context_done(ExecContext *c) {
 
         c->environment = strv_free(c->environment);
         c->environment_files = strv_free(c->environment_files);
+        c->pass_environment = strv_free(c->pass_environment);
 
         for (l = 0; l < ELEMENTSOF(c->rlimit); l++)
                 c->rlimit[l] = mfree(c->rlimit[l]);
@@ -2358,6 +2394,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);
+
         fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode);
 
         STRV_FOREACH(d, c->runtime_directory)
index f8995a4203e355dcda7e4f149be069aacd6eba4a..1faff160cbea58d115938ba56ddf004872b85dea 100644 (file)
@@ -99,6 +99,7 @@ struct ExecRuntime {
 struct ExecContext {
         char **environment;
         char **environment_files;
+        char **pass_environment;
 
         struct rlimit *rlimit[_RLIMIT_MAX];
         char *working_directory, *root_directory;
index 75b11ee4f237f25cbad36385941e683e0f6963da..3294054ef737606835625c4aa0d7b0c90c59a6ea 100644 (file)
@@ -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)
index 79cabd26e7a11716e0156f3ca48e555a6c37434c..53d96dee1433d7c2c955040aa325ec7aea0909df 100644 (file)
@@ -2196,6 +2196,70 @@ 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 */
+                *passenv = strv_free(*passenv);
+                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, true);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 int config_parse_ip_tos(const char *unit,
                         const char *filename,
                         unsigned line,
index 0cf821289ccf7d71b549b4c9e94d8ed7750548dc..eb4de1582f54a7ce2d8a58f30ea9267e849974f3 100644 (file)
@@ -84,6 +84,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);
index a13991a9600de211f4fb4734e06c40730c36733a..73ceeba18f986289698cecc7cb1f83e8183950a1 100644 (file)
@@ -1654,7 +1654,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")) {
                 const char *p;
 
                 r = sd_bus_message_open_container(m, 'v', "as");
@@ -1678,9 +1678,16 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
                         if (r == 0)
                                 break;
 
-                        if (!env_assignment_is_valid(word)) {
-                                log_error("Invalid environment assignment: %s", eq);
-                                return -EINVAL;
+                        if (streq(field, "Environment")) {
+                                if (!env_assignment_is_valid(word)) {
+                                        log_error("Invalid environment assignment: %s", word);
+                                        return -EINVAL;
+                                }
+                        } else {  /* PassEnvironment */
+                                if (!env_name_is_valid(word)) {
+                                        log_error("Invalid environment variable name: %s", word);
+                                        return -EINVAL;
+                                }
                         }
 
                         r = sd_bus_message_append_basic(m, 's', word);