From fe902fa496abb4583c5befaf671a2402b650cd14 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Fri, 10 Feb 2017 21:44:21 -0500 Subject: core/manager: move environment serialization out to basic/env-util.c This protocol is generally useful, we might just as well reuse it for the env. generators. The implementation is changed a bit: instead of making a new strv and freeing the old one, just mutate the original. This is much faster with larger arrays, while in fact atomicity is preserved, since we only either insert the new entry or not, without being in inconsistent state. v2: - fix confusion with return value --- src/basic/env-util.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 96da38d45e..4645ec781e 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -26,6 +26,7 @@ #include "alloc-util.h" #include "env-util.h" +#include "escape.h" #include "extract-word.h" #include "macro.h" #include "parse-util.h" @@ -644,3 +645,36 @@ int getenv_bool(const char *p) { return parse_boolean(e); } + +int serialize_environment(FILE *f, char **environment) { + char **e; + + STRV_FOREACH(e, environment) { + _cleanup_free_ char *ce; + + ce = cescape(*e); + if (!ce) + return -ENOMEM; + + fprintf(f, "env=%s\n", *e); + } + + /* caller should call ferror() */ + + return 0; +} + +int deserialize_environment(char ***environment, const char *line) { + char *uce = NULL; + int r; + + assert(line); + assert(environment); + + assert(startswith(line, "env=")); + r = cunescape(line + 4, UNESCAPE_RELAX, &uce); + if (r < 0) + return r; + + return strv_env_replace(environment, uce); +} -- cgit v1.2.3-54-g00ecf From 99003e01b6f7171f884ee6da663326aa1aa44e2b Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Fri, 10 Feb 2017 23:08:53 -0500 Subject: env-util,fileio: immediately replace variables in load_env_file_push() strv_env_replace was calling env_match(), which in effect allowed multiple values for the same key to be inserted into the environment block. That's pointless, because APIs to access variables only return a single value (the latest entry), so it's better to keep the block clean, i.e. with just a single entry for each key. Add a new helper function that simply tests if the part before '=' is equal in two strings and use that in strv_env_replace. In load_env_file_push, use strv_env_replace to immediately replace the previous assignment with a matching name. Afaict, none of the callers are materially affected by this change, but it seems like some pointless work was being done, if the same value was set multiple times. We'd go through parsing and assigning the value for each entry. With this change, we handle just the last one. --- src/basic/env-util.c | 30 ++++++++++++++++++++++++++---- src/basic/fileio.c | 9 ++++++--- src/test/test-fileio.c | 2 ++ 3 files changed, 34 insertions(+), 7 deletions(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 4645ec781e..05b90e499e 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -274,6 +274,19 @@ _pure_ static bool env_match(const char *t, const char *pattern) { return false; } +static bool env_entry_has_name(const char *entry, const char *name) { + const char *t; + + assert(entry); + assert(name); + + t = startswith(entry, name); + if (!t) + return false; + + return *t == '='; +} + char **strv_env_delete(char **x, unsigned n_lists, ...) { size_t n, i = 0; char **k, **r; @@ -387,18 +400,24 @@ char **strv_env_unset_many(char **l, ...) { int strv_env_replace(char ***l, char *p) { char **f; + const char *t, *name; assert(p); /* Replace first occurrence of the env var or add a new one in the * string list. Drop other occurences. Edits in-place. Does not copy p. + * p must be a valid key=value assignment. */ + t = strchr(p, '='); + assert(t); + + name = strndupa(p, t - p); + for (f = *l; f && *f; f++) - if (env_match(*f, p)) { - free(*f); - *f = p; - strv_env_unset(f + 1, p); + if (env_entry_has_name(*f, name)) { + free_and_replace(*f, p); + strv_env_unset(f + 1, *f); return 0; } @@ -676,5 +695,8 @@ int deserialize_environment(char ***environment, const char *line) { if (r < 0) return r; + if (!env_assignment_is_valid(uce)) + return -EINVAL; + return strv_env_replace(environment, uce); } diff --git a/src/basic/fileio.c b/src/basic/fileio.c index fb41431ec9..a1e4978125 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -30,6 +30,7 @@ #include "alloc-util.h" #include "ctype.h" +#include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -678,13 +679,15 @@ static int load_env_file_push( if (r < 0) return r; - p = strjoin(key, "=", strempty(value)); + p = strjoin(key, "=", value); if (!p) return -ENOMEM; - r = strv_consume(m, p); - if (r < 0) + r = strv_env_replace(m, p); + if (r < 0) { + free(p); return r; + } if (n_pushed) (*n_pushed)++; diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 56316904a3..a38bb874a9 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -71,6 +71,8 @@ static void test_parse_env_file(void) { "seven=\"sevenval\" #nocomment\n" "eight=eightval #nocomment\n" "export nine=nineval\n" + "ten=ignored\n" + "ten=ignored\n" "ten=", f); fflush(f); -- cgit v1.2.3-54-g00ecf From c8cebc36b02f6386faccce7d84b444155f3765ba Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Sat, 18 Feb 2017 16:23:03 -0500 Subject: basic/env-util: drop _pure_ from static function --- src/basic/env-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 05b90e499e..a28707fb49 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -248,7 +248,7 @@ fail: return NULL; } -_pure_ static bool env_match(const char *t, const char *pattern) { +static bool env_match(const char *t, const char *pattern) { assert(t); assert(pattern); -- cgit v1.2.3-54-g00ecf From 6162512cde54244127da547917d0e6d5d0a378c3 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 3 Aug 2016 14:35:50 -0400 Subject: basic: fix strv_env_get_n for unclean arrays If an environment array has duplicates, strv_env_get_n returns the results for the first match. This is wrong, because later entries in the environment are supposed to replace earlier entries. --- src/basic/env-util.c | 2 +- src/test/test-env-util.c | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index a28707fb49..f9208d1475 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -462,7 +462,7 @@ char *strv_env_get_n(char **l, const char *name, size_t k) { if (k <= 0) return NULL; - STRV_FOREACH(i, l) + STRV_FOREACH_BACKWARDS(i, l) if (strneq(*i, name, k) && (*i)[k] == '=') return *i + k + 1; diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index 35bb62906e..e004c518fb 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -45,6 +45,16 @@ static void test_strv_env_delete(void) { assert_se(strv_length(d) == 2); } +static void test_strv_env_get(void) { + char **l; + + l = STRV_MAKE("ONE_OR_TWO=1", "THREE=3", "ONE_OR_TWO=2", "FOUR=4"); + + assert_se(streq(strv_env_get(l, "ONE_OR_TWO"), "2")); + assert_se(streq(strv_env_get(l, "THREE"), "3")); + assert_se(streq(strv_env_get(l, "FOUR"), "4")); +} + static void test_strv_env_unset(void) { _cleanup_strv_free_ char **l = NULL; @@ -211,6 +221,7 @@ static void test_env_assignment_is_valid(void) { int main(int argc, char *argv[]) { test_strv_env_delete(); + test_strv_env_get(); test_strv_env_unset(); test_strv_env_set(); test_strv_env_merge(); -- cgit v1.2.3-54-g00ecf From d8ad241f54b8c4ac76aafd960d89b47b0ed87fb6 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 9 Aug 2016 10:39:15 -0400 Subject: basic: drop unnecessary strempty() call in replace_env strempty() converts a NULL value to empty string, so that it can be passed on to functions that don't support NULL. replace_env calls strempty before passing its value on to strappend. strappend supports NULL just fine, though, so this commit drops the strempty call. --- src/basic/env-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index f9208d1475..86ac07e1b6 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -563,7 +563,7 @@ char *replace_env(const char *format, char **env) { if (*e == '}') { const char *t; - t = strempty(strv_env_get_n(env, word+2, e-word-2)); + t = strv_env_get_n(env, word+2, e-word-2); k = strappend(r, t); if (!k) -- cgit v1.2.3-54-g00ecf From 37f3ffca273e5238794019caede7b7cd33a5de3a Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 4 Aug 2016 12:00:00 -0400 Subject: basic: add new merge_env_file function merge_env_file is a new function, that's like load_env_file, but takes a pre-existing environment as an input argument. New environment entries are merged. Variable expansion is performed. Falling back to the process environment is supported (when a flag is set). Alternatively this could be implemented as passing an additional fallback environment array, but later on we're adding another flag to allow braceless expansion, and the two flags can be combined in one arg, so there's less stuff to pass around. --- src/basic/env-util.c | 17 +++++++++++----- src/basic/env-util.h | 8 ++++++-- src/basic/fileio.c | 28 ++++++++++++++++++++++++++ src/basic/fileio.h | 2 ++ src/test/test-env-util.c | 31 +++++++++++++++++++++++++++++ src/test/test-fileio.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 7 deletions(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 86ac07e1b6..99a130008b 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -454,7 +454,7 @@ fail: return NULL; } -char *strv_env_get_n(char **l, const char *name, size_t k) { +char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) { char **i; assert(name); @@ -467,13 +467,20 @@ char *strv_env_get_n(char **l, const char *name, size_t k) { (*i)[k] == '=') return *i + k + 1; + if (flags & REPLACE_ENV_USE_ENVIRONMENT) { + const char *t; + + t = strndupa(name, k); + return getenv(t); + }; + return NULL; } char *strv_env_get(char **l, const char *name) { assert(name); - return strv_env_get_n(l, name, strlen(name)); + return strv_env_get_n(l, name, strlen(name), 0); } char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { @@ -512,7 +519,7 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha return e; } -char *replace_env(const char *format, char **env) { +char *replace_env(const char *format, char **env, unsigned flags) { enum { WORD, CURLY, @@ -563,7 +570,7 @@ char *replace_env(const char *format, char **env) { if (*e == '}') { const char *t; - t = strv_env_get_n(env, word+2, e-word-2); + t = strv_env_get_n(env, word+2, e-word-2, flags); k = strappend(r, t); if (!k) @@ -643,7 +650,7 @@ char **replace_env_argv(char **argv, char **env) { } /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env); + ret[k] = replace_env(*i, env, 0); if (!ret[k]) { strv_free(ret); return NULL; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 90df5b1cd9..4e83dcb43a 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -29,7 +29,11 @@ bool env_name_is_valid(const char *e); bool env_value_is_valid(const char *e); bool env_assignment_is_valid(const char *e); -char *replace_env(const char *format, char **env); +enum { + REPLACE_ENV_USE_ENVIRONMENT = 1u, +}; + +char *replace_env(const char *format, char **env, unsigned flags); char **replace_env_argv(char **argv, char **env); bool strv_env_is_valid(char **e); @@ -47,7 +51,7 @@ char **strv_env_unset(char **l, const char *p); /* In place ... */ char **strv_env_unset_many(char **l, ...) _sentinel_; int strv_env_replace(char ***l, char *p); /* In place ... */ -char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; +char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_; char *strv_env_get(char **x, const char *n) _pure_; int getenv_bool(const char *p); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index a1e4978125..49dd52bfd9 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -762,6 +762,34 @@ int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ** return 0; } +static int merge_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + char ***env = userdata; + char *expanded_value; + + assert(env); + + expanded_value = replace_env(value, *env, REPLACE_ENV_USE_ENVIRONMENT); + if (!expanded_value) + return -ENOMEM; + + free_and_replace(value, expanded_value); + + return load_env_file_push(filename, line, key, value, env, n_pushed); +} + +int merge_env_file( + char ***env, + FILE *f, + const char *fname) { + + return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL); +} + static void write_env_var(FILE *f, const char *v) { const char *p; diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 64852b15a8..e547614cc4 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -48,6 +48,8 @@ int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); +int merge_env_file(char ***env, FILE *f, const char *fname); + int write_env_file(const char *fname, char **l); int executable_is_script(const char *path, char **interpreter); diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index e004c518fb..f44cb3d57b 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -112,6 +112,36 @@ static void test_strv_env_merge(void) { assert_se(strv_length(r) == 5); } +static void test_env_strv_get_n(void) { + const char *_env[] = { + "FOO=NO NO NO", + "FOO=BAR BAR", + "BAR=waldo", + "PATH=unset", + NULL + }; + char **env = (char**) _env; + + assert_se(streq(strv_env_get_n(env, "FOO__", 3, 0), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO__", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO", 3, 0), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + + assert_se(streq(strv_env_get_n(env, "PATH__", 4, 0), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH", 4, 0), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + + env[3] = NULL; /* kill our $PATH */ + + assert_se(!strv_env_get_n(env, "PATH__", 4, 0)); + assert_se(!strv_env_get_n(env, "PATH", 4, 0)); + assert_se(streq(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), + getenv("PATH"))); + assert_se(streq(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), + getenv("PATH"))); +} + static void test_replace_env_arg(void) { const char *env[] = { "FOO=BAR BAR", @@ -225,6 +255,7 @@ int main(int argc, char *argv[]) { test_strv_env_unset(); test_strv_env_set(); test_strv_env_merge(); + test_env_strv_get_n(); test_replace_env_arg(); test_env_clean(); test_env_name_is_valid(); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index a38bb874a9..84f394a713 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -206,6 +206,56 @@ static void test_parse_multiline_env_file(void) { unlink(p); } +static void test_merge_env_file(void) { + char t[] = "/tmp/test-fileio-XXXXXX"; + int fd, r; + FILE *f; + _cleanup_strv_free_ char **a = NULL; + char **i; + + fd = mkostemp_safe(t); + assert_se(fd >= 0); + + log_info("/* %s (%s) */", __func__, t); + + f = fdopen(fd, "w"); + assert_se(f); + + r = write_string_stream(f, + "one=1 \n" + "twelve=${one}2\n" + "twentyone=2${one}\n" + "one=2\n" + "twentytwo=2${one}\n", false); + assert(r >= 0); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + strv_sort(a); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(streq(a[0], "one=2")); + assert_se(streq(a[1], "twelve=12")); + assert_se(streq(a[2], "twentyone=21")); + assert_se(streq(a[3], "twentytwo=22")); + assert_se(a[4] == NULL); + + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + strv_sort(a); + + STRV_FOREACH(i, a) + log_info("Got2: <%s>", *i); + + assert_se(streq(a[0], "one=2")); + assert_se(streq(a[1], "twelve=12")); + assert_se(streq(a[2], "twentyone=21")); + assert_se(streq(a[3], "twentytwo=22")); + assert_se(a[4] == NULL); +} static void test_executable_is_script(void) { char t[] = "/tmp/test-executable-XXXXXX"; @@ -557,11 +607,13 @@ static void test_tempfn(void) { } int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); log_parse_environment(); log_open(); test_parse_env_file(); test_parse_multiline_env_file(); + test_merge_env_file(); test_executable_is_script(); test_status_field(); test_capeff(); -- cgit v1.2.3-54-g00ecf From cb4499d0056a7c974d7d3695cc355c7e77edc938 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Sat, 11 Feb 2017 13:22:13 -0500 Subject: basic/env-util: use _cleanup_ in replace_env() --- src/basic/env-util.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 99a130008b..8774a81531 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -527,7 +527,8 @@ char *replace_env(const char *format, char **env, unsigned flags) { } state = WORD; const char *e, *word = format; - char *r = NULL, *k; + char *k; + _cleanup_free_ char *r = NULL; assert(format); @@ -544,7 +545,7 @@ char *replace_env(const char *format, char **env, unsigned flags) { if (*e == '{') { k = strnappend(r, word, e-word-1); if (!k) - goto fail; + return NULL; free(r); r = k; @@ -555,7 +556,7 @@ char *replace_env(const char *format, char **env, unsigned flags) { } else if (*e == '$') { k = strnappend(r, word, e-word); if (!k) - goto fail; + return NULL; free(r); r = k; @@ -574,7 +575,7 @@ char *replace_env(const char *format, char **env, unsigned flags) { k = strappend(r, t); if (!k) - goto fail; + return NULL; free(r); r = k; @@ -586,15 +587,7 @@ char *replace_env(const char *format, char **env, unsigned flags) { } } - k = strnappend(r, word, e-word); - if (!k) - goto fail; - - free(r); - return k; - -fail: - return mfree(r); + return strnappend(r, word, e-word); } char **replace_env_argv(char **argv, char **env) { -- cgit v1.2.3-54-g00ecf From ccad1fd07ce4eb40a2fcf81cfb55d9b41fdcac48 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Sat, 11 Feb 2017 14:05:10 -0500 Subject: Allow braceless variables to be expanded (Only in environment.d files.) We have only basic compatibility with shell syntax, but specifying variables without using braces is probably more common, and I think a lot of people would be surprised if this didn't work. --- man/environment.d.xml | 4 ++-- src/basic/env-util.c | 45 +++++++++++++++++++++++++++++++++++++++++++-- src/basic/env-util.h | 1 + src/basic/fileio.c | 7 ++++++- src/test/test-env-util.c | 31 +++++++++++++++++++++++++++++-- src/test/test-fileio.c | 14 ++++++++++---- 6 files changed, 91 insertions(+), 11 deletions(-) (limited to 'src/basic/env-util.c') diff --git a/man/environment.d.xml b/man/environment.d.xml index 4f3e03825a..2302992fa5 100644 --- a/man/environment.d.xml +++ b/man/environment.d.xml @@ -78,7 +78,7 @@ KEY=VALUE environment variable assignments, separated by newlines. The right hand side of these assignments may reference previously defined environment variables, using the ${OTHER_KEY} - format. No other elements of shell syntax are supported. + and $OTHER_KEY format. No other elements of shell syntax are supported. @@ -91,7 +91,7 @@ FOO_DEBUG=force-software-gl,log-verbose - PATH=/opt/foo/bin:${PATH} + PATH=/opt/foo/bin:$PATH LD_LIBRARY_PATH=/opt/foo/lib XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS} diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 8774a81531..f370854673 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -523,7 +523,8 @@ char *replace_env(const char *format, char **env, unsigned flags) { enum { WORD, CURLY, - VARIABLE + VARIABLE, + VARIABLE_RAW, } state = WORD; const char *e, *word = format; @@ -563,6 +564,18 @@ char *replace_env(const char *format, char **env, unsigned flags) { word = e+1; state = WORD; + + } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) { + k = strnappend(r, word, e-word-1); + if (!k) + return NULL; + + free(r); + r = k; + + word = e-1; + state = VARIABLE_RAW; + } else state = WORD; break; @@ -584,10 +597,38 @@ char *replace_env(const char *format, char **env, unsigned flags) { state = WORD; } break; + + case VARIABLE_RAW: + assert(flags & REPLACE_ENV_ALLOW_BRACELESS); + + if (!strchr(VALID_CHARS_ENV_NAME, *e)) { + const char *t; + + t = strv_env_get_n(env, word+1, e-word-1, flags); + + k = strappend(r, t); + if (!k) + return NULL; + + free(r); + r = k; + + word = e--; + state = WORD; + } + break; } } - return strnappend(r, word, e-word); + if (state == VARIABLE_RAW) { + const char *t; + + assert(flags & REPLACE_ENV_ALLOW_BRACELESS); + + t = strv_env_get_n(env, word+1, e-word-1, flags); + return strappend(r, t); + } else + return strnappend(r, word, e-word); } char **replace_env_argv(char **argv, char **env) { diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 4e83dcb43a..03bbc6af00 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -31,6 +31,7 @@ bool env_assignment_is_valid(const char *e); enum { REPLACE_ENV_USE_ENVIRONMENT = 1u, + REPLACE_ENV_ALLOW_BRACELESS = 2u, }; char *replace_env(const char *format, char **env, unsigned flags); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 49dd52bfd9..3c2dab1855 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -773,7 +773,8 @@ static int merge_env_file_push( assert(env); - expanded_value = replace_env(value, *env, REPLACE_ENV_USE_ENVIRONMENT); + expanded_value = replace_env(value, *env, + REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS); if (!expanded_value) return -ENOMEM; @@ -787,6 +788,10 @@ int merge_env_file( FILE *f, const char *fname) { + /* NOTE: this function supports braceful and braceless variable expansions, + * 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 f44cb3d57b..77a5219d82 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -142,7 +142,32 @@ static void test_env_strv_get_n(void) { getenv("PATH"))); } -static void test_replace_env_arg(void) { +static void test_replace_env(bool braceless) { + const char *env[] = { + "FOO=BAR BAR", + "BAR=waldo", + NULL + }; + _cleanup_free_ char *t = NULL, *s = NULL, *q = NULL, *r = NULL, *p = NULL; + unsigned flags = REPLACE_ENV_ALLOW_BRACELESS*braceless; + + t = replace_env("FOO=$FOO=${FOO}", (char**) env, flags); + assert_se(streq(t, braceless ? "FOO=BAR BAR=BAR BAR" : "FOO=$FOO=BAR BAR")); + + s = replace_env("BAR=$BAR=${BAR}", (char**) env, flags); + assert_se(streq(s, braceless ? "BAR=waldo=waldo" : "BAR=$BAR=waldo")); + + q = replace_env("BARBAR=$BARBAR=${BARBAR}", (char**) env, flags); + assert_se(streq(q, braceless ? "BARBAR==" : "BARBAR=$BARBAR=")); + + q = replace_env("BAR=$BAR$BAR${BAR}${BAR}", (char**) env, flags); + assert_se(streq(q, braceless ? "BAR=waldowaldowaldowaldo" : "BAR=$BAR$BARwaldowaldo")); + + p = replace_env("${BAR}$BAR$BAR", (char**) env, flags); + assert_se(streq(p, braceless ? "waldowaldowaldo" : "waldo$BAR$BAR")); +} + +static void test_replace_env_argv(void) { const char *env[] = { "FOO=BAR BAR", "BAR=waldo", @@ -256,7 +281,9 @@ int main(int argc, char *argv[]) { test_strv_env_set(); test_strv_env_merge(); test_env_strv_get_n(); - test_replace_env_arg(); + test_replace_env(false); + test_replace_env(true); + test_replace_env_argv(); test_env_clean(); test_env_name_is_valid(); test_env_value_is_valid(); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 84f394a713..c204cbae22 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -226,7 +226,10 @@ static void test_merge_env_file(void) { "twelve=${one}2\n" "twentyone=2${one}\n" "one=2\n" - "twentytwo=2${one}\n", false); + "twentytwo=2${one}\n" + "xxx_minus_three=$xxx - 3\n" + "xxx=0x$one$one$one\n" + , false); assert(r >= 0); r = merge_env_file(&a, NULL, t); @@ -240,8 +243,9 @@ static void test_merge_env_file(void) { assert_se(streq(a[1], "twelve=12")); assert_se(streq(a[2], "twentyone=21")); assert_se(streq(a[3], "twentytwo=22")); - assert_se(a[4] == NULL); - + assert_se(streq(a[4], "xxx=0x222")); + assert_se(streq(a[5], "xxx_minus_three= - 3")); + assert_se(a[6] == NULL); r = merge_env_file(&a, NULL, t); assert_se(r >= 0); @@ -254,7 +258,9 @@ static void test_merge_env_file(void) { assert_se(streq(a[1], "twelve=12")); assert_se(streq(a[2], "twentyone=21")); assert_se(streq(a[3], "twentytwo=22")); - assert_se(a[4] == NULL); + assert_se(streq(a[4], "xxx=0x222")); + assert_se(streq(a[5], "xxx_minus_three=0x222 - 3")); + assert_se(a[6] == NULL); } static void test_executable_is_script(void) { -- cgit v1.2.3-54-g00ecf From 4bed076c5f79ce26451ea3d73950d895f630f9a7 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 9 Aug 2016 10:20:22 -0400 Subject: basic: add replace_env_n function It's like replace_env, but lets you pass in a substring. --- src/basic/env-util.c | 6 ++++-- src/basic/env-util.h | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src/basic/env-util.c') diff --git a/src/basic/env-util.c b/src/basic/env-util.c index f370854673..1b955ff1d5 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -519,7 +519,7 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha return e; } -char *replace_env(const char *format, char **env, unsigned flags) { +char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { enum { WORD, CURLY, @@ -530,10 +530,11 @@ char *replace_env(const char *format, char **env, unsigned flags) { const char *e, *word = format; char *k; _cleanup_free_ char *r = NULL; + size_t i; assert(format); - for (e = format; *e; e ++) { + for (e = format, i = 0; *e && i < n; e ++, i ++) { switch (state) { @@ -614,6 +615,7 @@ char *replace_env(const char *format, char **env, unsigned flags) { r = k; word = e--; + i--; state = WORD; } break; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 03bbc6af00..43a1371f5e 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -34,9 +34,13 @@ enum { REPLACE_ENV_ALLOW_BRACELESS = 2u, }; -char *replace_env(const char *format, char **env, unsigned flags); +char *replace_env_n(const char *format, size_t n, char **env, unsigned flags); char **replace_env_argv(char **argv, char **env); +static inline char *replace_env(const char *format, char **env, unsigned flags) { + return replace_env_n(format, strlen(format), env, flags); +} + 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); -- cgit v1.2.3-54-g00ecf From b82f58bfe396b395bce3452bc0ba2f972fb01ab8 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Tue, 9 Aug 2016 10:20:22 -0400 Subject: 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.] --- man/environment.d.xml | 16 +++++++++--- src/basic/env-util.c | 67 +++++++++++++++++++++++++++++++++++++++++++++--- src/basic/env-util.h | 1 + src/basic/fileio.c | 6 +++-- src/test/test-env-util.c | 14 +++++++++- src/test/test-fileio.c | 16 ++++++++++-- 6 files changed, 108 insertions(+), 12 deletions(-) (limited to 'src/basic/env-util.c') 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 @@ KEY=VALUE environment variable assignments, separated by newlines. The right hand side of these assignments may reference previously defined environment variables, using the ${OTHER_KEY} - and $OTHER_KEY format. No other elements of shell syntax are supported. - + and $OTHER_KEY format. It is also possible to use + + ${FOO:-DEFAULT_VALUE} + to expand in the same way as ${FOO} unless the + expansion would be empty, in which case it expands to DEFAULT_VALUE, + and use + ${FOO:+ALTERNATE_VALUE} + to expand to ALTERNATE_VALUE as long as + ${FOO} would have expanded to a non-empty value. + No other elements of shell syntax are supported. EachKEY must be a valid variable name. Empty lines and lines beginning with the comment character # are ignored. @@ -95,8 +103,8 @@ 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/} 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) @@ -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; } 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) { -- cgit v1.2.3-54-g00ecf