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.]
This commit is contained in:
parent
4bed076c5f
commit
b82f58bfe3
|
@ -78,8 +78,16 @@
|
||||||
<literal><replaceable>KEY</replaceable>=<replaceable>VALUE</replaceable></literal> environment
|
<literal><replaceable>KEY</replaceable>=<replaceable>VALUE</replaceable></literal> environment
|
||||||
variable assignments, separated by newlines. The right hand side of these assignments may
|
variable assignments, separated by newlines. The right hand side of these assignments may
|
||||||
reference previously defined environment variables, using the <literal>${OTHER_KEY}</literal>
|
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.
|
and <literal>$OTHER_KEY</literal> format. It is also possible to use
|
||||||
</para>
|
|
||||||
|
<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
|
<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>
|
and lines beginning with the comment character <literal>#</literal> are ignored.</para>
|
||||||
|
@ -95,8 +103,8 @@
|
||||||
<programlisting>
|
<programlisting>
|
||||||
FOO_DEBUG=force-software-gl,log-verbose
|
FOO_DEBUG=force-software-gl,log-verbose
|
||||||
PATH=/opt/foo/bin:$PATH
|
PATH=/opt/foo/bin:$PATH
|
||||||
LD_LIBRARY_PATH=/opt/foo/lib
|
LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}/opt/foo/lib
|
||||||
XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS}
|
XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</example>
|
</example>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
|
|
|
@ -525,12 +525,16 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
|
||||||
CURLY,
|
CURLY,
|
||||||
VARIABLE,
|
VARIABLE,
|
||||||
VARIABLE_RAW,
|
VARIABLE_RAW,
|
||||||
|
TEST,
|
||||||
|
DEFAULT_VALUE,
|
||||||
|
ALTERNATE_VALUE,
|
||||||
} state = WORD;
|
} state = WORD;
|
||||||
|
|
||||||
const char *e, *word = format;
|
const char *e, *word = format, *test_value;
|
||||||
char *k;
|
char *k;
|
||||||
_cleanup_free_ char *r = NULL;
|
_cleanup_free_ char *r = NULL;
|
||||||
size_t i;
|
size_t i, len;
|
||||||
|
int nest = 0;
|
||||||
|
|
||||||
assert(format);
|
assert(format);
|
||||||
|
|
||||||
|
@ -554,7 +558,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
|
||||||
|
|
||||||
word = e-1;
|
word = e-1;
|
||||||
state = VARIABLE;
|
state = VARIABLE;
|
||||||
|
nest++;
|
||||||
} else if (*e == '$') {
|
} else if (*e == '$') {
|
||||||
k = strnappend(r, word, e-word);
|
k = strnappend(r, word, e-word);
|
||||||
if (!k)
|
if (!k)
|
||||||
|
@ -594,6 +598,63 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
|
||||||
free(r);
|
free(r);
|
||||||
r = k;
|
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;
|
word = e+1;
|
||||||
state = WORD;
|
state = WORD;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ bool env_assignment_is_valid(const char *e);
|
||||||
enum {
|
enum {
|
||||||
REPLACE_ENV_USE_ENVIRONMENT = 1u,
|
REPLACE_ENV_USE_ENVIRONMENT = 1u,
|
||||||
REPLACE_ENV_ALLOW_BRACELESS = 2u,
|
REPLACE_ENV_ALLOW_BRACELESS = 2u,
|
||||||
|
REPLACE_ENV_ALLOW_EXTENDED = 4u,
|
||||||
};
|
};
|
||||||
|
|
||||||
char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
|
char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
|
||||||
|
|
|
@ -784,7 +784,9 @@ static int merge_env_file_push(
|
||||||
}
|
}
|
||||||
|
|
||||||
expanded_value = replace_env(value, *env,
|
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)
|
if (!expanded_value)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
@ -799,7 +801,7 @@ int merge_env_file(
|
||||||
const char *fname) {
|
const char *fname) {
|
||||||
|
|
||||||
/* NOTE: this function supports braceful and braceless variable expansions,
|
/* 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);
|
return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL);
|
||||||
|
|
|
@ -185,6 +185,12 @@ static void test_replace_env_argv(void) {
|
||||||
"${FOO",
|
"${FOO",
|
||||||
"FOO$$${FOO}",
|
"FOO$$${FOO}",
|
||||||
"$$FOO${FOO}",
|
"$$FOO${FOO}",
|
||||||
|
"${FOO:-${BAR}}",
|
||||||
|
"${QUUX:-${FOO}}",
|
||||||
|
"${FOO:+${BAR}}",
|
||||||
|
"${QUUX:+${BAR}}",
|
||||||
|
"${FOO:+|${BAR}|}}",
|
||||||
|
"${FOO:+|${BAR}{|}",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
_cleanup_strv_free_ char **r = 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[8], "${FOO"));
|
||||||
assert_se(streq(r[9], "FOO$BAR BAR"));
|
assert_se(streq(r[9], "FOO$BAR BAR"));
|
||||||
assert_se(streq(r[10], "$FOOBAR 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) {
|
static void test_env_clean(void) {
|
||||||
|
|
|
@ -229,6 +229,10 @@ static void test_merge_env_file(void) {
|
||||||
"twentytwo=2${one}\n"
|
"twentytwo=2${one}\n"
|
||||||
"xxx_minus_three=$xxx - 3\n"
|
"xxx_minus_three=$xxx - 3\n"
|
||||||
"xxx=0x$one$one$one\n"
|
"xxx=0x$one$one$one\n"
|
||||||
|
"yyy=${one:-fallback}\n"
|
||||||
|
"zzz=${one:+replacement}\n"
|
||||||
|
"zzzz=${foobar:-${nothing}}\n"
|
||||||
|
"zzzzz=${nothing:+${nothing}}\n"
|
||||||
, false);
|
, false);
|
||||||
assert(r >= 0);
|
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[3], "twentytwo=22"));
|
||||||
assert_se(streq(a[4], "xxx=0x222"));
|
assert_se(streq(a[4], "xxx=0x222"));
|
||||||
assert_se(streq(a[5], "xxx_minus_three= - 3"));
|
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);
|
r = merge_env_file(&a, NULL, t);
|
||||||
assert_se(r >= 0);
|
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[3], "twentytwo=22"));
|
||||||
assert_se(streq(a[4], "xxx=0x222"));
|
assert_se(streq(a[4], "xxx=0x222"));
|
||||||
assert_se(streq(a[5], "xxx_minus_three=0x222 - 3"));
|
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) {
|
static void test_merge_env_file_invalid(void) {
|
||||||
|
|
Loading…
Reference in a new issue