]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
basic: support default and alternate values for env expansion
authorRay Strode <rstrode@redhat.com>
Tue, 9 Aug 2016 14:20:22 +0000 (10:20 -0400)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 21 Feb 2017 04:32:53 +0000 (23:32 -0500)
Sometimes it's useful to provide a default value during an environment
expansion, if the environment variable isn't already set.

For instance $XDG_DATA_DIRS is suppose to default to:

/usr/local/share/:/usr/share/

if it's not yet set. That means callers wishing to augment
XDG_DATA_DIRS need to manually add those two values.

This commit changes replace_env to support the following shell
compatible default value syntax:

XDG_DATA_DIRS=/foo:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share}

Likewise, it's useful to provide an alternate value during an
environment expansion, if the environment variable isn't already set.

For instance, $LD_LIBRARY_PATH will inadvertently search the current
working directory if it starts or ends with a colon, so the following
is usually wrong:

LD_LIBRARY_PATH=/foo/lib:${LD_LIBRARY_PATH}

To address that, this changes replace_env to support the following
shell compatible alternate value syntax:

LD_LIBRARY_PATH=/foo/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

[zj: gate the new syntax under REPLACE_ENV_ALLOW_EXTENDED switch, so
existing callers are not modified.]

man/environment.d.xml
src/basic/env-util.c
src/basic/env-util.h
src/basic/fileio.c
src/test/test-env-util.c
src/test/test-fileio.c

index 4022c25c3631e29e0c79c01af217a9e50fffdbcd..be7758a2f92d05b3a136428cf1c2bf5c062c3399 100644 (file)
     <literal><replaceable>KEY</replaceable>=<replaceable>VALUE</replaceable></literal> environment
     variable assignments, separated by newlines. The right hand side of these assignments may
     reference previously defined environment variables, using the <literal>${OTHER_KEY}</literal>
-    and <literal>$OTHER_KEY</literal> format. No other elements of shell syntax are supported.
-    </para>
+    and <literal>$OTHER_KEY</literal> format. It is also possible to use
+
+    <literal>${<replaceable>FOO</replaceable>:-<replaceable>DEFAULT_VALUE</replaceable>}</literal>
+    to expand in the same way as <literal>${<replaceable>FOO</replaceable>}</literal> unless the
+    expansion would be empty, in which case it expands to <replaceable>DEFAULT_VALUE</replaceable>,
+    and use
+    <literal>${<replaceable>FOO</replaceable>:+<replaceable>ALTERNATE_VALUE</replaceable>}</literal>
+    to expand to <replaceable>ALTERNATE_VALUE</replaceable> as long as
+    <literal>${<replaceable>FOO</replaceable>}</literal> would have expanded to a non-empty value.
+    No other elements of shell syntax are supported.</para>
 
     <para>Each<replaceable>KEY</replaceable> must be a valid variable name. Empty lines
     and lines beginning with the comment character <literal>#</literal> are ignored.</para>
         <programlisting>
         FOO_DEBUG=force-software-gl,log-verbose
         PATH=/opt/foo/bin:$PATH
-        LD_LIBRARY_PATH=/opt/foo/lib
-        XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS}
+        LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}/opt/foo/lib
+        XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}
         </programlisting>
       </example>
     </refsect2>
index 1b955ff1d54e468c15d1f580a012a24a6cad3827..2ca64c33019f8bb8930792b7ac0a9e67ce3bf999 100644 (file)
@@ -525,12 +525,16 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                 CURLY,
                 VARIABLE,
                 VARIABLE_RAW,
+                TEST,
+                DEFAULT_VALUE,
+                ALTERNATE_VALUE,
         } state = WORD;
 
-        const char *e, *word = format;
+        const char *e, *word = format, *test_value;
         char *k;
         _cleanup_free_ char *r = NULL;
-        size_t i;
+        size_t i, len;
+        int nest = 0;
 
         assert(format);
 
@@ -554,7 +558,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                                 word = e-1;
                                 state = VARIABLE;
-
+                                nest++;
                         } else if (*e == '$') {
                                 k = strnappend(r, word, e-word);
                                 if (!k)
@@ -594,6 +598,63 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                                 free(r);
                                 r = k;
 
+                                word = e+1;
+                                state = WORD;
+                        } else if (*e == ':') {
+                                if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
+                                        /* Treat this as unsupported syntax, i.e. do no replacement */
+                                        state = WORD;
+                                else {
+                                        len = e-word-2;
+                                        state = TEST;
+                                }
+                        }
+                        break;
+
+                case TEST:
+                        if (*e == '-')
+                                state = DEFAULT_VALUE;
+                        else if (*e == '+')
+                                state = ALTERNATE_VALUE;
+                        else {
+                                state = WORD;
+                                break;
+                        }
+
+                        test_value = e+1;
+                        break;
+
+                case DEFAULT_VALUE: /* fall through */
+                case ALTERNATE_VALUE:
+                        assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
+
+                        if (*e == '{') {
+                                nest++;
+                                break;
+                        }
+
+                        if (*e != '}')
+                                break;
+
+                        nest--;
+                        if (nest == 0) { // || !strchr(e+1, '}')) {
+                                const char *t;
+                                _cleanup_free_ char *v = NULL;
+
+                                t = strv_env_get_n(env, word+2, len, flags);
+
+                                if (t && state == ALTERNATE_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+                                else if (!t && state == DEFAULT_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free(r);
+                                r = k;
+
                                 word = e+1;
                                 state = WORD;
                         }
index 43a1371f5e53395e0a4aefe29dbed99bd863260f..e88fa6aac04e5568e3b3b91ec17555b820b0896f 100644 (file)
@@ -32,6 +32,7 @@ bool env_assignment_is_valid(const char *e);
 enum {
         REPLACE_ENV_USE_ENVIRONMENT = 1u,
         REPLACE_ENV_ALLOW_BRACELESS = 2u,
+        REPLACE_ENV_ALLOW_EXTENDED  = 4u,
 };
 
 char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
index 8185f67e004bdeeaec654f951d7a42caa4ba80cc..b9a9f748928058dbe30b99f06ad8346996c9791c 100644 (file)
@@ -784,7 +784,9 @@ static int merge_env_file_push(
         }
 
         expanded_value = replace_env(value, *env,
-                                     REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS);
+                                     REPLACE_ENV_USE_ENVIRONMENT|
+                                     REPLACE_ENV_ALLOW_BRACELESS|
+                                     REPLACE_ENV_ALLOW_EXTENDED);
         if (!expanded_value)
                 return -ENOMEM;
 
@@ -799,7 +801,7 @@ int merge_env_file(
                 const char *fname) {
 
         /* NOTE: this function supports braceful and braceless variable expansions,
-         * unlike other exported parsing functions.
+         * plus "extended" substitutions, unlike other exported parsing functions.
          */
 
         return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL);
index 77a5219d82e51a41175bb3fbfe604c9605447077..dfcd9cb724474c02830ad9c3b82297e012dc0645 100644 (file)
@@ -185,6 +185,12 @@ static void test_replace_env_argv(void) {
                 "${FOO",
                 "FOO$$${FOO}",
                 "$$FOO${FOO}",
+                "${FOO:-${BAR}}",
+                "${QUUX:-${FOO}}",
+                "${FOO:+${BAR}}",
+                "${QUUX:+${BAR}}",
+                "${FOO:+|${BAR}|}}",
+                "${FOO:+|${BAR}{|}",
                 NULL
         };
         _cleanup_strv_free_ char **r = NULL;
@@ -202,7 +208,13 @@ static void test_replace_env_argv(void) {
         assert_se(streq(r[8], "${FOO"));
         assert_se(streq(r[9], "FOO$BAR BAR"));
         assert_se(streq(r[10], "$FOOBAR BAR"));
-        assert_se(strv_length(r) == 11);
+        assert_se(streq(r[11], "${FOO:-waldo}"));
+        assert_se(streq(r[12], "${QUUX:-BAR BAR}"));
+        assert_se(streq(r[13], "${FOO:+waldo}"));
+        assert_se(streq(r[14], "${QUUX:+waldo}"));
+        assert_se(streq(r[15], "${FOO:+|waldo|}}"));
+        assert_se(streq(r[16], "${FOO:+|waldo{|}"));
+        assert_se(strv_length(r) == 17);
 }
 
 static void test_env_clean(void) {
index b117335db88555175ca4b8579f814fa700c734b8..b1d688c89e3a1d966980d6e677ad421045365a15 100644 (file)
@@ -229,6 +229,10 @@ static void test_merge_env_file(void) {
                                 "twentytwo=2${one}\n"
                                 "xxx_minus_three=$xxx - 3\n"
                                 "xxx=0x$one$one$one\n"
+                                "yyy=${one:-fallback}\n"
+                                "zzz=${one:+replacement}\n"
+                                "zzzz=${foobar:-${nothing}}\n"
+                                "zzzzz=${nothing:+${nothing}}\n"
                                 , false);
         assert(r >= 0);
 
@@ -245,7 +249,11 @@ static void test_merge_env_file(void) {
         assert_se(streq(a[3], "twentytwo=22"));
         assert_se(streq(a[4], "xxx=0x222"));
         assert_se(streq(a[5], "xxx_minus_three= - 3"));
-        assert_se(a[6] == NULL);
+        assert_se(streq(a[6], "yyy=2"));
+        assert_se(streq(a[7], "zzz=replacement"));
+        assert_se(streq(a[8], "zzzz="));
+        assert_se(streq(a[9], "zzzzz="));
+        assert_se(a[10] == NULL);
 
         r = merge_env_file(&a, NULL, t);
         assert_se(r >= 0);
@@ -260,7 +268,11 @@ static void test_merge_env_file(void) {
         assert_se(streq(a[3], "twentytwo=22"));
         assert_se(streq(a[4], "xxx=0x222"));
         assert_se(streq(a[5], "xxx_minus_three=0x222 - 3"));
-        assert_se(a[6] == NULL);
+        assert_se(streq(a[6], "yyy=2"));
+        assert_se(streq(a[7], "zzz=replacement"));
+        assert_se(streq(a[8], "zzzz="));
+        assert_se(streq(a[9], "zzzzz="));
+        assert_se(a[10] == NULL);
 }
 
 static void test_merge_env_file_invalid(void) {