diff --git a/man/systemctl.xml b/man/systemctl.xml index d4afb36f85..14405141cf 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -1495,11 +1495,26 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err show-environment - Dump the systemd manager environment block. The - environment block will be dumped in straight-forward form - suitable for sourcing into a shell script. This environment - block will be passed to all processes the manager - spawns. + Dump the systemd manager environment block. This is the environment + block that is passed to all processes the manager spawns. The environment + block will be dumped in straight-forward form suitable for sourcing into + most shells. If no special characters or whitespace is present in the variable + values, no escaping is performed, and the assignments have the form + VARIABLE=value. If whitespace or characters which have + special meaning to the shell are present, dollar-single-quote escaping is + used, and assignments have the form VARIABLE=$'value'. + This syntax is known to be supported by + bash1, + zsh1, + ksh1, + and + busybox1's + ash1, + but not + dash1 + or + fish1. + diff --git a/src/basic/escape.c b/src/basic/escape.c index 4a1ec4505e..85e4b5282e 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -441,10 +441,16 @@ char *octescape(const char *s, size_t len) { } -static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) { +static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) { assert(bad); for (; *s; s++) { + if (escape_tab_nl && IN_SET(*s, '\n', '\t')) { + *(t++) = '\\'; + *(t++) = *s == '\n' ? 'n' : 't'; + continue; + } + if (*s == '\\' || strchr(bad, *s)) *(t++) = '\\'; @@ -461,20 +467,21 @@ char *shell_escape(const char *s, const char *bad) { if (!r) return NULL; - t = strcpy_backslash_escaped(r, s, bad); + t = strcpy_backslash_escaped(r, s, bad, false); *t = 0; return r; } -char *shell_maybe_quote(const char *s) { +char* shell_maybe_quote(const char *s, EscapeStyle style) { const char *p; char *r, *t; assert(s); - /* Encloses a string in double quotes if necessary to make it - * OK as shell string. */ + /* Encloses a string in quotes if necessary to make it OK as a shell + * string. Note that we treat benign UTF-8 characters as needing + * escaping too, but that should be OK. */ for (p = s; *p; p++) if (*p <= ' ' || @@ -485,17 +492,30 @@ char *shell_maybe_quote(const char *s) { if (!*p) return strdup(s); - r = new(char, 1+strlen(s)*2+1+1); + r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1); if (!r) return NULL; t = r; - *(t++) = '"'; + if (style == ESCAPE_BACKSLASH) + *(t++) = '"'; + else if (style == ESCAPE_POSIX) { + *(t++) = '$'; + *(t++) = '\''; + } else + assert_not_reached("Bad EscapeStyle"); + t = mempcpy(t, s, p - s); - t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE); + if (style == ESCAPE_BACKSLASH) + t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false); + else + t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true); - *(t++)= '"'; + if (style == ESCAPE_BACKSLASH) + *(t++) = '"'; + else + *(t++) = '\''; *t = 0; return r; diff --git a/src/basic/escape.h b/src/basic/escape.h index deaa4def28..6f5cc60bc8 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -31,13 +31,30 @@ /* What characters are special in the shell? */ /* must be escaped outside and inside double-quotes */ #define SHELL_NEED_ESCAPE "\"\\`$" -/* can be escaped or double-quoted */ -#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;" + +/* Those that can be escaped or double-quoted. + * + * Stricly speaking, ! does not need to be escaped, except in interactive + * mode, but let's be extra nice to the user and quote ! in case this + * output is ever used in interactive mode. */ +#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;!" + +/* Note that we assume control characters would need to be escaped too in + * addition to the "special" characters listed here, if they appear in the + * string. Current users disallow control characters. Also '"' shall not + * be escaped. + */ +#define SHELL_NEED_ESCAPE_POSIX "\\\'" typedef enum UnescapeFlags { UNESCAPE_RELAX = 1, } UnescapeFlags; +typedef enum EscapeStyle { + ESCAPE_BACKSLASH = 1, + ESCAPE_POSIX = 2, +} EscapeStyle; + char *cescape(const char *s); char *cescape_length(const char *s, size_t n); size_t cescape_char(char c, char *buf); @@ -51,4 +68,4 @@ char *xescape(const char *s, const char *bad); char *octescape(const char *s, size_t len); char *shell_escape(const char *s, const char *bad); -char *shell_maybe_quote(const char *s); +char* shell_maybe_quote(const char *s, EscapeStyle style); diff --git a/src/core/job.c b/src/core/job.c index 899c011db4..d197ad0da7 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -741,7 +741,7 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) { if (t == JOB_START && result == JOB_FAILED) { _cleanup_free_ char *quoted; - quoted = shell_maybe_quote(u->id); + quoted = shell_maybe_quote(u->id, ESCAPE_BACKSLASH); manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); } } diff --git a/src/environment-d-generator/environment-d-generator.c b/src/environment-d-generator/environment-d-generator.c index 2d4c4235e4..9c72502373 100644 --- a/src/environment-d-generator/environment-d-generator.c +++ b/src/environment-d-generator/environment-d-generator.c @@ -78,7 +78,7 @@ static int load_and_print(void) { t = strchr(*i, '='); assert(t); - q = shell_maybe_quote(t + 1); + q = shell_maybe_quote(t + 1, ESCAPE_BACKSLASH); if (!q) return log_oom(); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index aae69f6da5..7b0bc8255c 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -860,7 +860,7 @@ static void log_job_error_with_service_result(const char* service, const char *r assert(service); - service_shell_quoted = shell_maybe_quote(service); + service_shell_quoted = shell_maybe_quote(service, ESCAPE_BACKSLASH); if (extra_args) { _cleanup_free_ char *t; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 9ad74b0695..a61d46d7fc 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -47,6 +47,7 @@ #include "dropin.h" #include "efivars.h" #include "env-util.h" +#include "escape.h" #include "exit-status.h" #include "fd-util.h" #include "fileio.h" @@ -5573,6 +5574,24 @@ static int reset_failed(int argc, char *argv[], void *userdata) { return r; } +static int print_variable(const char *s) { + const char *sep; + _cleanup_free_ char *esc = NULL; + + sep = strchr(s, '='); + if (!sep) { + log_error("Invalid environment block"); + return -EUCLEAN; + } + + esc = shell_maybe_quote(sep + 1, ESCAPE_POSIX); + if (!esc) + return log_oom(); + + printf("%.*s=%s\n", (int)(sep-s), s, esc); + return 0; +} + static int show_environment(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -5602,8 +5621,11 @@ static int show_environment(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) - puts(text); + while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) { + r = print_variable(text); + if (r < 0) + return r; + } if (r < 0) return bus_log_parse_error(r); diff --git a/src/test/test-escape.c b/src/test/test-escape.c index 6cbb8443fe..181d7db5b4 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -86,25 +86,52 @@ static void test_shell_escape(void) { test_shell_escape_one("foo:bar,baz", ",:", "foo\\:bar\\,baz"); } -static void test_shell_maybe_quote_one(const char *s, const char *expected) { - _cleanup_free_ char *r; +static void test_shell_maybe_quote_one(const char *s, + EscapeStyle style, + const char *expected) { + _cleanup_free_ char *ret = NULL; - assert_se(r = shell_maybe_quote(s)); - assert_se(streq(r, expected)); + assert_se(ret = shell_maybe_quote(s, style)); + log_debug("[%s] → [%s] (%s)", s, ret, expected); + assert_se(streq(ret, expected)); } static void test_shell_maybe_quote(void) { - test_shell_maybe_quote_one("", ""); - test_shell_maybe_quote_one("\\", "\"\\\\\""); - test_shell_maybe_quote_one("\"", "\"\\\"\""); - test_shell_maybe_quote_one("foobar", "foobar"); - test_shell_maybe_quote_one("foo bar", "\"foo bar\""); - test_shell_maybe_quote_one("foo \"bar\" waldo", "\"foo \\\"bar\\\" waldo\""); - test_shell_maybe_quote_one("foo$bar", "\"foo\\$bar\""); + test_shell_maybe_quote_one("", ESCAPE_BACKSLASH, ""); + test_shell_maybe_quote_one("", ESCAPE_POSIX, ""); + test_shell_maybe_quote_one("\\", ESCAPE_BACKSLASH, "\"\\\\\""); + test_shell_maybe_quote_one("\\", ESCAPE_POSIX, "$'\\\\'"); + test_shell_maybe_quote_one("\"", ESCAPE_BACKSLASH, "\"\\\"\""); + test_shell_maybe_quote_one("\"", ESCAPE_POSIX, "$'\"'"); + test_shell_maybe_quote_one("foobar", ESCAPE_BACKSLASH, "foobar"); + test_shell_maybe_quote_one("foobar", ESCAPE_POSIX, "foobar"); + test_shell_maybe_quote_one("foo bar", ESCAPE_BACKSLASH, "\"foo bar\""); + test_shell_maybe_quote_one("foo bar", ESCAPE_POSIX, "$'foo bar'"); + test_shell_maybe_quote_one("foo\tbar", ESCAPE_BACKSLASH, "\"foo\tbar\""); + test_shell_maybe_quote_one("foo\tbar", ESCAPE_POSIX, "$'foo\\tbar'"); + test_shell_maybe_quote_one("foo\nbar", ESCAPE_BACKSLASH, "\"foo\nbar\""); + test_shell_maybe_quote_one("foo\nbar", ESCAPE_POSIX, "$'foo\\nbar'"); + test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_BACKSLASH, "\"foo \\\"bar\\\" waldo\""); + test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_POSIX, "$'foo \"bar\" waldo'"); + test_shell_maybe_quote_one("foo$bar", ESCAPE_BACKSLASH, "\"foo\\$bar\""); + test_shell_maybe_quote_one("foo$bar", ESCAPE_POSIX, "$'foo$bar'"); + + /* Note that current users disallow control characters, so this "test" + * is here merely to establish current behaviour. If control characters + * were allowed, they should be quoted, i.e. \001 should become \\001. */ + test_shell_maybe_quote_one("a\nb\001", ESCAPE_BACKSLASH, "\"a\nb\001\""); + test_shell_maybe_quote_one("a\nb\001", ESCAPE_POSIX, "$'a\\nb\001'"); + + test_shell_maybe_quote_one("foo!bar", ESCAPE_BACKSLASH, "\"foo!bar\""); + test_shell_maybe_quote_one("foo!bar", ESCAPE_POSIX, "$'foo!bar'"); } int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + test_cescape(); test_cunescape(); test_shell_escape();