From 2cdb473690a4414b0bcafbb3878e468fce6e89e2 Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Tue, 12 Jul 2022 16:21:39 +0100 Subject: [PATCH] custom-env: Add helper to parse combined env/arg strings Users like desktop-shell want to parse a provided string containing a combination of environment and arg, e.g.: ENV=stuff /path/to/thing --good Add support to custom-env for parsing this, with tests, so we can delete the custom implementation inside desktop-shell. Signed-off-by: Daniel Stone --- shared/process-util.c | 81 +++++++++++++++++++++++++++++++++++++++++ shared/process-util.h | 3 ++ tests/custom-env-test.c | 52 ++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/shared/process-util.c b/shared/process-util.c index 7cb92f6c0..e36c64709 100644 --- a/shared/process-util.c +++ b/shared/process-util.c @@ -25,6 +25,9 @@ #include "config.h" +#include +#include +#include #include #include #include @@ -154,6 +157,84 @@ custom_env_set_env_var(struct custom_env *env, const char *name, const char *val assert(*ep); } +/** + * Add information from a parsed exec string to a custom_env + * + * An 'exec string' is a string in the format: + * ENVFOO=bar ENVBAR=baz /path/to/exec --arg anotherarg + * + * This function will parse such a string and add the specified environment + * variables (in the format KEY=value) up until it sees a non-environment + * string, after which point every entry will be interpreted as a new + * argument. + * + * Entries are space-separated; there is no support for quoting. + */ +void +custom_env_add_from_exec_string(struct custom_env *env, const char *exec_str) +{ + char *dup_path = strdup(exec_str); + char *start = dup_path; + + assert(dup_path); + + /* Build the environment array (if any) by handling any number of + * equal-separated key=value at the start of the string, split by + * spaces; uses "foo=bar baz=quux meh argh" as the example, where + * "foo=bar" and "baz=quux" should go into the environment, and + * "meh" should be executed with "argh" as its first argument */ + while (*start) { + char *k = NULL, *v = NULL; + char *p; + + /* Leaves us with "foo\0bar baz=quux meh argh", with k pointing + * to "foo" and v pointing to "bar baz=quux meh argh" */ + for (p = start; *p && !isspace(*p); p++) { + if (*p == '=') { + *p++ = '\0'; + k = start; + v = p; + break; + } + } + + if (!v) + break; + + /* Walk to the next space or NUL, filling any trailing spaces + * with NUL, to give us "foo\0bar\0\0baz=quux meh argh". + * k will point to "foo", v will point to "bar", and + * start will point to "baz=quux meh argh". */ + while (*p && !isspace(*p)) + p++; + while (*p && isspace(*p)) + *p++ = '\0'; + start = p; + + custom_env_set_env_var(env, k, v); + } + + /* Now build the argv array by splitting on spaces */ + while (*start) { + char *p; + bool valid = false; + + for (p = start; *p && !isspace(*p); p++) + valid = true; + + if (!valid) + break; + + while (*p && isspace(*p)) + *p++ = '\0'; + + custom_env_add_arg(env, start); + start = p; + } + + free(dup_path); +} + char *const * custom_env_get_envp(struct custom_env *env) { diff --git a/shared/process-util.h b/shared/process-util.h index 081ed3314..05543f6f6 100644 --- a/shared/process-util.h +++ b/shared/process-util.h @@ -84,6 +84,9 @@ custom_env_set_env_var(struct custom_env *env, const char *name, const char *val void custom_env_add_arg(struct custom_env *env, const char *arg); +void +custom_env_add_from_exec_string(struct custom_env *env, const char *exec_str); + char *const * custom_env_get_envp(struct custom_env *env); diff --git a/tests/custom-env-test.c b/tests/custom-env-test.c index 44e3f0f7a..3a5161907 100644 --- a/tests/custom-env-test.c +++ b/tests/custom-env-test.c @@ -104,3 +104,55 @@ TEST(basic_env_arg) assert(env.arg_finalized); custom_env_fini(&env); } + +struct test_str { + const char *exec_str; + char * const *envp; + char * const *argp; +}; + +static struct test_str str_tests[] = { + { + .exec_str = "ENV1=1 ENV2=owt two-arghs", + .envp = (char * const []) { "ENV1=1", "ENV2=owt", "ENV3=three", NULL }, + .argp = (char * const []) { "two-arghs", NULL }, + }, + { + .exec_str = "ENV2=owt one-argh", + .envp = (char * const []) { "ENV1=one", "ENV2=owt", "ENV3=three", NULL }, + .argp = (char * const []) { "one-argh", NULL }, + }, + { + .exec_str = "FOO=bar one-argh-again", + .envp = (char * const []) { "ENV1=one", "ENV2=two", "ENV3=three", "FOO=bar", NULL }, + .argp = (char * const []) { "one-argh-again", NULL }, + }, + { + .exec_str = "ENV1=number=7 one-argh-eq", + .envp = (char * const []) { "ENV1=number=7", "ENV2=two", "ENV3=three", NULL }, + .argp = (char * const []) { "one-argh-eq", NULL }, + }, + { + .exec_str = "no-arg-h", + .envp = DEFAULT_ENVP, + .argp = (char * const []) { "no-arg-h", NULL }, + }, + { + .exec_str = "argh-w-arg argequals=thing plainarg ", + .envp = DEFAULT_ENVP, + .argp = (char * const []) { "argh-w-arg", "argequals=thing", "plainarg", NULL }, + }, +}; + +TEST_P(env_parse_string, str_tests) +{ + struct custom_env env; + struct test_str *test = data; + + testlog("checking exec_str '%s'\n", test->exec_str); + custom_env_init_from_environ(&env); + custom_env_add_from_exec_string(&env, test->exec_str); + ASSERT_STR_ARRAY_MATCH("envp", custom_env_get_envp(&env), test->envp); + ASSERT_STR_ARRAY_MATCH("argp", custom_env_get_argp(&env), test->argp); + custom_env_fini(&env); +}