Skip to content

Commit b82f58b

Browse files
halflinekeszybz
authored andcommitted
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.]
1 parent 4bed076 commit b82f58b

File tree

6 files changed

+108
-12
lines changed

6 files changed

+108
-12
lines changed

man/environment.d.xml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,16 @@
7878
<literal><replaceable>KEY</replaceable>=<replaceable>VALUE</replaceable></literal> environment
7979
variable assignments, separated by newlines. The right hand side of these assignments may
8080
reference previously defined environment variables, using the <literal>${OTHER_KEY}</literal>
81-
and <literal>$OTHER_KEY</literal> format. No other elements of shell syntax are supported.
82-
</para>
81+
and <literal>$OTHER_KEY</literal> format. It is also possible to use
82+
83+
<literal>${<replaceable>FOO</replaceable>:-<replaceable>DEFAULT_VALUE</replaceable>}</literal>
84+
to expand in the same way as <literal>${<replaceable>FOO</replaceable>}</literal> unless the
85+
expansion would be empty, in which case it expands to <replaceable>DEFAULT_VALUE</replaceable>,
86+
and use
87+
<literal>${<replaceable>FOO</replaceable>:+<replaceable>ALTERNATE_VALUE</replaceable>}</literal>
88+
to expand to <replaceable>ALTERNATE_VALUE</replaceable> as long as
89+
<literal>${<replaceable>FOO</replaceable>}</literal> would have expanded to a non-empty value.
90+
No other elements of shell syntax are supported.</para>
8391

8492
<para>Each<replaceable>KEY</replaceable> must be a valid variable name. Empty lines
8593
and lines beginning with the comment character <literal>#</literal> are ignored.</para>
@@ -95,8 +103,8 @@
95103
<programlisting>
96104
FOO_DEBUG=force-software-gl,log-verbose
97105
PATH=/opt/foo/bin:$PATH
98-
LD_LIBRARY_PATH=/opt/foo/lib
99-
XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS}
106+
LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}/opt/foo/lib
107+
XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/}
100108
</programlisting>
101109
</example>
102110
</refsect2>

src/basic/env-util.c

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,16 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
525525
CURLY,
526526
VARIABLE,
527527
VARIABLE_RAW,
528+
TEST,
529+
DEFAULT_VALUE,
530+
ALTERNATE_VALUE,
528531
} state = WORD;
529532

530-
const char *e, *word = format;
533+
const char *e, *word = format, *test_value;
531534
char *k;
532535
_cleanup_free_ char *r = NULL;
533-
size_t i;
536+
size_t i, len;
537+
int nest = 0;
534538

535539
assert(format);
536540

@@ -554,7 +558,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
554558

555559
word = e-1;
556560
state = VARIABLE;
557-
561+
nest++;
558562
} else if (*e == '$') {
559563
k = strnappend(r, word, e-word);
560564
if (!k)
@@ -594,6 +598,63 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
594598
free(r);
595599
r = k;
596600

601+
word = e+1;
602+
state = WORD;
603+
} else if (*e == ':') {
604+
if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
605+
/* Treat this as unsupported syntax, i.e. do no replacement */
606+
state = WORD;
607+
else {
608+
len = e-word-2;
609+
state = TEST;
610+
}
611+
}
612+
break;
613+
614+
case TEST:
615+
if (*e == '-')
616+
state = DEFAULT_VALUE;
617+
else if (*e == '+')
618+
state = ALTERNATE_VALUE;
619+
else {
620+
state = WORD;
621+
break;
622+
}
623+
624+
test_value = e+1;
625+
break;
626+
627+
case DEFAULT_VALUE: /* fall through */
628+
case ALTERNATE_VALUE:
629+
assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
630+
631+
if (*e == '{') {
632+
nest++;
633+
break;
634+
}
635+
636+
if (*e != '}')
637+
break;
638+
639+
nest--;
640+
if (nest == 0) { // || !strchr(e+1, '}')) {
641+
const char *t;
642+
_cleanup_free_ char *v = NULL;
643+
644+
t = strv_env_get_n(env, word+2, len, flags);
645+
646+
if (t && state == ALTERNATE_VALUE)
647+
t = v = replace_env_n(test_value, e-test_value, env, flags);
648+
else if (!t && state == DEFAULT_VALUE)
649+
t = v = replace_env_n(test_value, e-test_value, env, flags);
650+
651+
k = strappend(r, t);
652+
if (!k)
653+
return NULL;
654+
655+
free(r);
656+
r = k;
657+
597658
word = e+1;
598659
state = WORD;
599660
}

src/basic/env-util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ bool env_assignment_is_valid(const char *e);
3232
enum {
3333
REPLACE_ENV_USE_ENVIRONMENT = 1u,
3434
REPLACE_ENV_ALLOW_BRACELESS = 2u,
35+
REPLACE_ENV_ALLOW_EXTENDED = 4u,
3536
};
3637

3738
char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);

src/basic/fileio.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,9 @@ static int merge_env_file_push(
784784
}
785785

786786
expanded_value = replace_env(value, *env,
787-
REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS);
787+
REPLACE_ENV_USE_ENVIRONMENT|
788+
REPLACE_ENV_ALLOW_BRACELESS|
789+
REPLACE_ENV_ALLOW_EXTENDED);
788790
if (!expanded_value)
789791
return -ENOMEM;
790792

@@ -799,7 +801,7 @@ int merge_env_file(
799801
const char *fname) {
800802

801803
/* NOTE: this function supports braceful and braceless variable expansions,
802-
* unlike other exported parsing functions.
804+
* plus "extended" substitutions, unlike other exported parsing functions.
803805
*/
804806

805807
return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL);

src/test/test-env-util.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ static void test_replace_env_argv(void) {
185185
"${FOO",
186186
"FOO$$${FOO}",
187187
"$$FOO${FOO}",
188+
"${FOO:-${BAR}}",
189+
"${QUUX:-${FOO}}",
190+
"${FOO:+${BAR}}",
191+
"${QUUX:+${BAR}}",
192+
"${FOO:+|${BAR}|}}",
193+
"${FOO:+|${BAR}{|}",
188194
NULL
189195
};
190196
_cleanup_strv_free_ char **r = NULL;
@@ -202,7 +208,13 @@ static void test_replace_env_argv(void) {
202208
assert_se(streq(r[8], "${FOO"));
203209
assert_se(streq(r[9], "FOO$BAR BAR"));
204210
assert_se(streq(r[10], "$FOOBAR BAR"));
205-
assert_se(strv_length(r) == 11);
211+
assert_se(streq(r[11], "${FOO:-waldo}"));
212+
assert_se(streq(r[12], "${QUUX:-BAR BAR}"));
213+
assert_se(streq(r[13], "${FOO:+waldo}"));
214+
assert_se(streq(r[14], "${QUUX:+waldo}"));
215+
assert_se(streq(r[15], "${FOO:+|waldo|}}"));
216+
assert_se(streq(r[16], "${FOO:+|waldo{|}"));
217+
assert_se(strv_length(r) == 17);
206218
}
207219

208220
static void test_env_clean(void) {

src/test/test-fileio.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ static void test_merge_env_file(void) {
229229
"twentytwo=2${one}\n"
230230
"xxx_minus_three=$xxx - 3\n"
231231
"xxx=0x$one$one$one\n"
232+
"yyy=${one:-fallback}\n"
233+
"zzz=${one:+replacement}\n"
234+
"zzzz=${foobar:-${nothing}}\n"
235+
"zzzzz=${nothing:+${nothing}}\n"
232236
, false);
233237
assert(r >= 0);
234238

@@ -245,7 +249,11 @@ static void test_merge_env_file(void) {
245249
assert_se(streq(a[3], "twentytwo=22"));
246250
assert_se(streq(a[4], "xxx=0x222"));
247251
assert_se(streq(a[5], "xxx_minus_three= - 3"));
248-
assert_se(a[6] == NULL);
252+
assert_se(streq(a[6], "yyy=2"));
253+
assert_se(streq(a[7], "zzz=replacement"));
254+
assert_se(streq(a[8], "zzzz="));
255+
assert_se(streq(a[9], "zzzzz="));
256+
assert_se(a[10] == NULL);
249257

250258
r = merge_env_file(&a, NULL, t);
251259
assert_se(r >= 0);
@@ -260,7 +268,11 @@ static void test_merge_env_file(void) {
260268
assert_se(streq(a[3], "twentytwo=22"));
261269
assert_se(streq(a[4], "xxx=0x222"));
262270
assert_se(streq(a[5], "xxx_minus_three=0x222 - 3"));
263-
assert_se(a[6] == NULL);
271+
assert_se(streq(a[6], "yyy=2"));
272+
assert_se(streq(a[7], "zzz=replacement"));
273+
assert_se(streq(a[8], "zzzz="));
274+
assert_se(streq(a[9], "zzzzz="));
275+
assert_se(a[10] == NULL);
264276
}
265277

266278
static void test_merge_env_file_invalid(void) {

0 commit comments

Comments
 (0)