diff options
author | Ray Strode <rstrode@redhat.com> | 2016-08-09 10:20:22 -0400 |
---|---|---|
committer | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2017-02-20 23:32:53 -0500 |
commit | b82f58bfe396b395bce3452bc0ba2f972fb01ab8 (patch) | |
tree | 02767a48aced8b08db4aba14890a224a97c05111 | |
parent | 4bed076c5f79ce26451ea3d73950d895f630f9a7 (diff) |
basic: support default and alternate values for env expansion
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.]
-rw-r--r-- | man/environment.d.xml | 16 | ||||
-rw-r--r-- | src/basic/env-util.c | 67 | ||||
-rw-r--r-- | src/basic/env-util.h | 1 | ||||
-rw-r--r-- | src/basic/fileio.c | 6 | ||||
-rw-r--r-- | src/test/test-env-util.c | 14 | ||||
-rw-r--r-- | src/test/test-fileio.c | 16 |
6 files changed, 108 insertions, 12 deletions
diff --git a/man/environment.d.xml b/man/environment.d.xml index 4022c25c36..be7758a2f9 100644 --- a/man/environment.d.xml +++ b/man/environment.d.xml @@ -78,8 +78,16 @@ <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> @@ -95,8 +103,8 @@ <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> diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 1b955ff1d5..2ca64c3301 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -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) @@ -596,6 +600,63 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { 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; } break; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 43a1371f5e..e88fa6aac0 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -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); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 8185f67e00..b9a9f74892 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -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); diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index 77a5219d82..dfcd9cb724 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -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) { diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index b117335db8..b1d688c89e 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -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) { |