summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd.service.xml93
-rw-r--r--src/core/load-fragment.c124
-rw-r--r--src/test/test-unit-file.c156
3 files changed, 269 insertions, 104 deletions
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index da9079cbb8..0b68aa0890 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -377,11 +377,10 @@
<para>For each of the specified
commands, the first argument must be
- an absolute and literal path to an
- executable. Optionally, if the
- absolute file name is prefixed with
- <literal>@</literal>, the second token
- will be passed as
+ an absolute path to an executable.
+ Optionally, if this file name is
+ prefixed with <literal>@</literal>,
+ the second token will be passed as
<literal>argv[0]</literal> to the
executed process, followed by the
further arguments specified. If the
@@ -1148,13 +1147,15 @@
<para>Each command line is split on whitespace, with
the first item being the command to execute, and the
- subsequent items being the arguments. Double quotes
+ subsequent items being the arguments. Double quotes
("...") and single quotes ('...') may be used, in
which case everything until the next matching quote
- becomes part of the same argument. Quotes themselves
- are removed after parsing. In addition, a trailing
- backslash (<literal>\</literal>) may be used to merge
- lines. </para>
+ becomes part of the same argument. C-style escapes are
+ also supported, see table below. Quotes themselves are
+ removed after parsing and escape sequences
+ substituted. In addition, a trailing backslash
+ (<literal>\</literal>) may be used to merge lines.
+ </para>
<para>This syntax is intended to be very similar to
shell syntax, but only the meta-characters and
@@ -1168,6 +1169,10 @@
<emphasis>other elements of shell syntax are not
supported</emphasis>.</para>
+ <para>The command to execute must an absolute path
+ name. It may contain spaces, but control characters
+ are not allowed.</para>
+
<para>The command line accepts <literal>%</literal>
specifiers as described in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
@@ -1253,6 +1258,74 @@ ExecStart=/bin/echo $ONE $TWO $THREE</programlisting>
<literal>&gt;/dev/null</literal>,
<literal>&amp;</literal>, <literal>;</literal>, and
<literal>/bin/ls</literal>.</para>
+
+ <table>
+ <title>C escapes supported in command lines and environment variables</title>
+ <tgroup cols='2'>
+ <colspec colname='escape' />
+ <colspec colname='meaning' />
+ <thead>
+ <row>
+ <entry>Literal</entry>
+ <entry>Actual value</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>\a</literal></entry>
+ <entry>bell</entry>
+ </row>
+ <row>
+ <entry><literal>\b</literal></entry>
+ <entry>backspace</entry>
+ </row>
+ <row>
+ <entry><literal>\f</literal></entry>
+ <entry>form feed</entry>
+ </row>
+ <row>
+ <entry><literal>\n</literal></entry>
+ <entry>newline</entry>
+ </row>
+ <row>
+ <entry><literal>\r</literal></entry>
+ <entry>carriage return</entry>
+ </row>
+ <row>
+ <entry><literal>\t</literal></entry>
+ <entry>tab</entry>
+ </row>
+ <row>
+ <entry><literal>\v</literal></entry>
+ <entry>vertical tab</entry>
+ </row>
+ <row>
+ <entry><literal>\\</literal></entry>
+ <entry>backslash</entry>
+ </row>
+ <row>
+ <entry><literal>\"</literal></entry>
+ <entry>double quotation mark</entry>
+ </row>
+ <row>
+ <entry><literal>\'</literal></entry>
+ <entry>single quotation mark</entry>
+ </row>
+ <row>
+ <entry><literal>\s</literal></entry>
+ <entry>space</entry>
+ </row>
+ <row>
+ <entry><literal>\x<replaceable>xx</replaceable></literal></entry>
+ <entry>character number <replaceable>xx</replaceable> in hexadecimal encoding</entry>
+ </row>
+ <row>
+ <entry><literal>\<replaceable>nnn</replaceable></literal></entry>
+ <entry>character number <replaceable>nnn</replaceable> in octal encoding</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
</refsect1>
<refsect1>
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 7430036f48..e9659ca344 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -547,9 +547,9 @@ int config_parse_exec(const char *unit,
* overriding of argv[0]. */
for (;;) {
int i;
- const char *word, *state;
+ const char *word, *state, *reason;
size_t l;
- bool honour_argv0 = false, ignore = false;
+ bool separate_argv0 = false, ignore = false;
path = NULL;
nce = NULL;
@@ -560,28 +560,23 @@ int config_parse_exec(const char *unit,
if (rvalue[0] == 0)
break;
- for (i = 0; i < 2; i++) {
- if (rvalue[0] == '-' && !ignore) {
- ignore = true;
- rvalue ++;
- }
-
- if (rvalue[0] == '@' && !honour_argv0) {
- honour_argv0 = true;
- rvalue ++;
- }
- }
-
- if (*rvalue != '/') {
- log_syntax(unit, LOG_ERR, filename, line, EINVAL,
- "Executable path is not absolute, ignoring: %s", rvalue);
- return 0;
- }
-
k = 0;
FOREACH_WORD_QUOTED(word, l, rvalue, state) {
- if (strneq(word, ";", MAX(l, 1U)))
- goto found;
+ if (k == 0) {
+ for (i = 0; i < 2; i++) {
+ if (*word == '-' && !ignore) {
+ ignore = true;
+ word ++;
+ }
+
+ if (*word == '@' && !separate_argv0) {
+ separate_argv0 = true;
+ word ++;
+ }
+ }
+ } else
+ if (strneq(word, ";", MAX(l, 1U)))
+ goto found;
k++;
}
@@ -592,60 +587,69 @@ int config_parse_exec(const char *unit,
}
found:
- n = new(char*, k + !honour_argv0);
+ n = new(char*, k + !separate_argv0);
if (!n)
return log_oom();
k = 0;
FOREACH_WORD_QUOTED(word, l, rvalue, state) {
- if (strneq(word, ";", MAX(l, 1U)))
- break;
- else if (strneq(word, "\\;", MAX(l, 1U))) {
- word ++;
- l --;
- }
+ char *c;
+ unsigned skip;
- if (honour_argv0 && word == rvalue) {
- assert(!path);
+ if (separate_argv0 ? path == NULL : k == 0) {
+ /* first word, very special */
+ skip = separate_argv0 + ignore;
- path = strndup(word, l);
- if (!path) {
- r = log_oom();
- goto fail;
- }
+ /* skip special chars in the beginning */
+ assert(skip < l);
- if (!utf8_is_valid(path)) {
- log_invalid_utf8(unit, LOG_ERR, filename, line, EINVAL, rvalue);
- r = 0;
- goto fail;
- }
+ } else if (strneq(word, ";", MAX(l, 1U)))
+ /* new commandline */
+ break;
- } else {
- char *c;
+ else
+ skip = strneq(word, "\\;", MAX(l, 1U));
- c = n[k++] = cunescape_length(word, l);
- if (!c) {
- r = log_oom();
- goto fail;
- }
+ c = cunescape_length(word + skip, l - skip);
+ if (!c) {
+ r = log_oom();
+ goto fail;
+ }
- if (!utf8_is_valid(c)) {
- log_invalid_utf8(unit, LOG_ERR, filename, line, EINVAL, rvalue);
- r = 0;
- goto fail;
- }
+ if (!utf8_is_valid(c)) {
+ log_invalid_utf8(unit, LOG_ERR, filename, line, EINVAL, rvalue);
+ r = 0;
+ goto fail;
}
+
+ /* where to stuff this? */
+ if (separate_argv0 && path == NULL)
+ path = c;
+ else
+ n[k++] = c;
}
n[k] = NULL;
- if (!n[0]) {
- log_syntax(unit, LOG_ERR, filename, line, EINVAL,
- "Invalid command line, ignoring: %s", rvalue);
- r = 0;
- goto fail;
- }
+ log_debug("path: %s", path ?: n[0]);
+
+ if (!n[0])
+ reason = "Empty executable name or zeroeth argument";
+ else if (!string_is_safe(path ?: n[0]))
+ reason = "Executable path contains special characters";
+ else if (!path_is_absolute(path ?: n[0]))
+ reason = "Executable path is not absolute";
+ else if (endswith(path ?: n[0], "/"))
+ reason = "Executable path specifies a directory";
+ else
+ goto ok;
+
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "%s, ignoring: %s", reason, rvalue);
+ r = 0;
+ goto fail;
+ok:
if (!path) {
path = strdup(n[0]);
if (!path) {
@@ -654,8 +658,6 @@ int config_parse_exec(const char *unit,
}
}
- assert(path_is_absolute(path));
-
nce = new0(ExecCommand, 1);
if (!nce) {
r = log_oom();
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
index 08da2ba4eb..6a146a702f 100644
--- a/src/test/test-unit-file.c
+++ b/src/test/test-unit-file.c
@@ -74,31 +74,34 @@ static void check_execcommand(ExecCommand *c,
const char* argv2,
bool ignore) {
assert_se(c);
- log_info("%s %s %s %s",
+ log_info("expect: \"%s\" [\"%s\" \"%s\" \"%s\"]",
+ path, argv0 ?: path, argv1, argv2);
+ log_info("actual: \"%s\" [\"%s\" \"%s\" \"%s\"]",
c->path, c->argv[0], c->argv[1], c->argv[2]);
assert_se(streq(c->path, path));
- assert_se(streq(c->argv[0], argv0));
- assert_se(streq(c->argv[1], argv1));
+ assert_se(streq(c->argv[0], argv0 ?: path));
+ assert_se(streq_ptr(c->argv[1], argv1));
assert_se(streq_ptr(c->argv[2], argv2));
assert_se(c->ignore == ignore);
}
static void test_config_parse_exec(void) {
- /* int config_parse_exec( */
- /* const char *filename, */
- /* unsigned line, */
- /* const char *section, */
- /* unsigned section_line, */
- /* const char *lvalue, */
- /* int ltype, */
- /* const char *rvalue, */
- /* void *data, */
- /* void *userdata) */
+ /* int config_parse_exec(
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) */
int r;
ExecCommand *c = NULL, *c1;
+ const char *ccc;
- /* basic test */
+ log_info("/* basic test */");
r = config_parse_exec(NULL, "fake", 1, "section", 1,
"LValue", 0, "/RValue r1",
&c, NULL);
@@ -106,52 +109,60 @@ static void test_config_parse_exec(void) {
check_execcommand(c, "/RValue", "/RValue", "r1", NULL, false);
r = config_parse_exec(NULL, "fake", 2, "section", 1,
- "LValue", 0, "/RValue///slashes/// r1",
+ "LValue", 0, "/RValue///slashes r1///",
&c, NULL);
- /* test slashes */
+
+ log_info("/* test slashes */");
assert_se(r >= 0);
c1 = c->command_next;
- check_execcommand(c1, "/RValue/slashes", "/RValue///slashes///", "r1", NULL, false);
+ check_execcommand(c1, "/RValue/slashes", "/RValue///slashes", "r1///", NULL, false);
- /* honour_argv0 */
+ log_info("/* trailing slash */");
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/RValue/ argv0 r1",
+ &c, NULL);
+ assert_se(r == 0);
+ assert_se(c1->command_next == NULL);
+
+ log_info("/* honour_argv0 */");
r = config_parse_exec(NULL, "fake", 3, "section", 1,
- "LValue", 0, "@/RValue///slashes2/// argv0 r1",
+ "LValue", 0, "@/RValue///slashes2 ///argv0 r1",
&c, NULL);
assert_se(r >= 0);
c1 = c1->command_next;
- check_execcommand(c1, "/RValue/slashes2", "argv0", "r1", NULL, false);
+ check_execcommand(c1, "/RValue/slashes2", "///argv0", "r1", NULL, false);
- /* ignore && honour_argv0 */
+ log_info("/* ignore && honour_argv0 */");
r = config_parse_exec(NULL, "fake", 4, "section", 1,
- "LValue", 0, "-@/RValue///slashes3/// argv0a r1",
+ "LValue", 0, "-@/RValue///slashes3 argv0a r1",
&c, NULL);
assert_se(r >= 0);
c1 = c1->command_next;
check_execcommand(c1, "/RValue/slashes3", "argv0a", "r1", NULL, true);
- /* ignore && honour_argv0 */
+ log_info("/* ignore && honour_argv0 */");
r = config_parse_exec(NULL, "fake", 4, "section", 1,
- "LValue", 0, "@-/RValue///slashes4/// argv0b r1",
+ "LValue", 0, "@-/RValue///slashes4 argv0b r1",
&c, NULL);
assert_se(r >= 0);
c1 = c1->command_next;
check_execcommand(c1, "/RValue/slashes4", "argv0b", "r1", NULL, true);
- /* ignore && ignore */
+ log_info("/* ignore && ignore */");
r = config_parse_exec(NULL, "fake", 4, "section", 1,
"LValue", 0, "--/RValue argv0 r1",
&c, NULL);
assert_se(r == 0);
assert_se(c1->command_next == NULL);
- /* ignore && ignore */
+ log_info("/* ignore && ignore (2) */");
r = config_parse_exec(NULL, "fake", 4, "section", 1,
"LValue", 0, "-@-/RValue argv0 r1",
&c, NULL);
assert_se(r == 0);
assert_se(c1->command_next == NULL);
- /* semicolon */
+ log_info("/* semicolon */");
r = config_parse_exec(NULL, "fake", 5, "section", 1,
"LValue", 0,
"-@/RValue argv0 r1 ; "
@@ -162,9 +173,9 @@ static void test_config_parse_exec(void) {
check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true);
c1 = c1->command_next;
- check_execcommand(c1, "/goo/goo", "/goo/goo", "boo", NULL, false);
+ check_execcommand(c1, "/goo/goo", NULL, "boo", NULL, false);
- /* trailing semicolon */
+ log_info("/* trailing semicolon */");
r = config_parse_exec(NULL, "fake", 5, "section", 1,
"LValue", 0,
"-@/RValue argv0 r1 ; ",
@@ -175,16 +186,16 @@ static void test_config_parse_exec(void) {
assert_se(c1->command_next == NULL);
- /* escaped semicolon */
+ log_info("/* escaped semicolon */");
r = config_parse_exec(NULL, "fake", 5, "section", 1,
"LValue", 0,
"/bin/find \\;",
&c, NULL);
assert_se(r >= 0);
c1 = c1->command_next;
- check_execcommand(c1, "/bin/find", "/bin/find", ";", NULL, false);
+ check_execcommand(c1, "/bin/find", NULL, ";", NULL, false);
- /* escaped semicolon with following arg */
+ log_info("/* escaped semicolon with following arg */");
r = config_parse_exec(NULL, "fake", 5, "section", 1,
"LValue", 0,
"/sbin/find \\; x",
@@ -192,7 +203,86 @@ static void test_config_parse_exec(void) {
assert_se(r >= 0);
c1 = c1->command_next;
check_execcommand(c1,
- "/sbin/find", "/sbin/find", ";", "x", false);
+ "/sbin/find", NULL, ";", "x", false);
+
+ log_info("/* spaces in the filename */");
+ r = config_parse_exec(NULL, "fake", 5, "section", 1,
+ "LValue", 0,
+ "\"/PATH WITH SPACES/daemon\" -1 -2",
+ &c, NULL);
+ assert_se(r >= 0);
+ c1 = c1->command_next;
+ check_execcommand(c1,
+ "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false);
+
+ log_info("/* spaces in the filename, no args */");
+ r = config_parse_exec(NULL, "fake", 5, "section", 1,
+ "LValue", 0,
+ "\"/PATH WITH SPACES/daemon -1 -2\"",
+ &c, NULL);
+ assert_se(r >= 0);
+ c1 = c1->command_next;
+ check_execcommand(c1,
+ "/PATH WITH SPACES/daemon -1 -2", NULL, NULL, NULL, false);
+
+ log_info("/* spaces in the filename, everything quoted */");
+ r = config_parse_exec(NULL, "fake", 5, "section", 1,
+ "LValue", 0,
+ "\"/PATH WITH SPACES/daemon\" \"-1\" '-2'",
+ &c, NULL);
+ assert_se(r >= 0);
+ c1 = c1->command_next;
+ check_execcommand(c1,
+ "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false);
+
+ log_info("/* escaped spaces in the filename */");
+ r = config_parse_exec(NULL, "fake", 5, "section", 1,
+ "LValue", 0,
+ "\"/PATH\\sWITH\\sSPACES/daemon\" '-1 -2'",
+ &c, NULL);
+ assert_se(r >= 0);
+ c1 = c1->command_next;
+ check_execcommand(c1,
+ "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false);
+
+ log_info("/* escaped spaces in the filename (2) */");
+ r = config_parse_exec(NULL, "fake", 5, "section", 1,
+ "LValue", 0,
+ "\"/PATH\\x20WITH\\x20SPACES/daemon\" \"-1 -2\"",
+ &c, NULL);
+ assert_se(r >= 0);
+ c1 = c1->command_next;
+ check_execcommand(c1,
+ "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false);
+
+ for (ccc = "abfnrtv\\\'\"x"; *ccc; ccc++) {
+ /* \\x is an incomplete hexadecimal sequence, invalid because of the slash */
+ char path[] = "/path\\X";
+ path[sizeof(path) - 2] = *ccc;
+
+ log_info("/* invalid character: \\%c */", *ccc);
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, path,
+ &c, NULL);
+ assert_se(r == 0);
+ assert_se(c1->command_next == NULL);
+ }
+
+ log_info("/* valid character: \\s */");
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/path\\s",
+ &c, NULL);
+ assert_se(r >= 0);
+ c1 = c1->command_next;
+ check_execcommand(c1, "/path ", NULL, NULL, NULL, false);
+
+ log_info("/* trailing backslash: \\ */");
+ /* backslash is invalid */
+ r = config_parse_exec(NULL, "fake", 4, "section", 1,
+ "LValue", 0, "/path\\",
+ &c, NULL);
+ assert_se(r == 0);
+ assert_se(c1->command_next == NULL);
exec_command_free_list(c);
}