diff --git a/man/systemd-run.xml b/man/systemd-run.xml
index 00f1586a5b..0e67911fb0 100644
--- a/man/systemd-run.xml
+++ b/man/systemd-run.xml
@@ -72,7 +72,7 @@
processes of the command are managed by the service manager similarly to normal services, and will show
up in the output of systemctl list-units. Execution in this case is synchronous, and
will return only when the command finishes. This mode is enabled via the switch
- (see below).
+ (see below).
If a command is run with path, socket, or timer options such as (see below),
a transient path, socket, or timer unit is created alongside the service unit for the specified command. Only the
@@ -82,15 +82,16 @@
.path, .socket, or .timer unit that triggers the
specified unit.
- By default, services created with systemd-run default to the type,
- see the description of Type= in
+ By default, services created with systemd-run default to the
+ type, see the description of Type= in
systemd.service5 for
- details. Note that when this type is used the service manager (and thus the systemd-run command)
- considers service start-up successful as soon as the fork() for the main service process
- succeeded, i.e. before the execve() is invoked, and thus even if the specified command cannot
- be started. Consider using the service type (i.e. ) to
- ensure that systemd-run returns successfully only if the specified command line has been
- successfully started.
+ details. Note that when this type is used, the service manager (and thus the
+ systemd-run command) considers service start-up successful as soon as the
+ fork() for the main service process succeeded, i.e. before the
+ execve() is invoked, and thus even if the specified command cannot be started.
+ Consider using the service type (i.e. ) to
+ ensure that systemd-run returns successfully only if the specified command line has
+ been successfully started.
@@ -411,10 +412,8 @@
- All command line arguments after the first non-option
- argument become part of the command line of the launched
- process. If a command is run as service unit, the first argument
- needs to be an absolute program path.
+ All command line arguments after the first non-option argument become part of the command line of
+ the launched process.
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 8de092ee4e..37d0afb0cb 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -1591,7 +1591,7 @@ int bus_set_transient_exec_command(
if (!exec_chars)
return -ENOMEM;
- a = unit_concat_strv(c->argv, UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS);
+ a = unit_concat_strv(c->argv, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX);
if (!a)
return -ENOMEM;
@@ -1601,7 +1601,8 @@ int bus_set_transient_exec_command(
_cleanup_free_ char *t = NULL;
const char *p;
- p = unit_escape_setting(c->path, UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS, &t);
+ p = unit_escape_setting(c->path,
+ UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX, &t);
if (!p)
return -ENOMEM;
diff --git a/src/core/unit.c b/src/core/unit.c
index 5f1c6109b0..05cee225ed 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -4254,51 +4254,62 @@ static const char* unit_drop_in_dir(Unit *u, UnitWriteFlags flags) {
}
char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf) {
- char *ret = NULL;
+ assert(!FLAGS_SET(flags, UNIT_ESCAPE_EXEC_SYNTAX | UNIT_ESCAPE_C));
+
+ _cleanup_free_ char *t = NULL;
if (!s)
return NULL;
- /* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the allocated
- * return buffer pointer is also written to *buf, except if no escaping was necessary, in which case *buf is
- * set to NULL, and the input pointer is returned as-is. This means the return value always contains a properly
- * escaped version, but *buf when passed only contains a pointer if an allocation was necessary. If *buf is
- * not specified, then the return value always needs to be freed. Callers can use this to optimize memory
- * allocations. */
+ /* Escapes the input string as requested. Returns the escaped string. If 'buf' is specified then the
+ * allocated return buffer pointer is also written to *buf, except if no escaping was necessary, in
+ * which case *buf is set to NULL, and the input pointer is returned as-is. This means the return
+ * value always contains a properly escaped version, but *buf when passed only contains a pointer if
+ * an allocation was necessary. If *buf is not specified, then the return value always needs to be
+ * freed. Callers can use this to optimize memory allocations. */
if (flags & UNIT_ESCAPE_SPECIFIERS) {
- ret = specifier_escape(s);
- if (!ret)
+ t = specifier_escape(s);
+ if (!t)
return NULL;
- s = ret;
+ s = t;
}
- if (flags & UNIT_ESCAPE_C) {
- char *a;
+ /* We either do c-escaping or shell-escaping, to additionally escape characters that we parse for
+ * ExecStart= and friend, i.e. '$' and ';' and quotes. */
- a = cescape(s);
- free(ret);
- if (!a)
+ if (flags & UNIT_ESCAPE_EXEC_SYNTAX) {
+ char *t2 = shell_escape(s, "$;'\"");
+ if (!t2)
return NULL;
+ free_and_replace(t, t2);
- ret = a;
+ s = t;
+
+ } else if (flags & UNIT_ESCAPE_C) {
+ char *t2 = cescape(s);
+ if (!t2)
+ return NULL;
+ free_and_replace(t, t2);
+
+ s = t;
}
if (buf) {
- *buf = ret;
- return ret ?: (char*) s;
+ *buf = TAKE_PTR(t);
+ return (char*) s;
}
- return ret ?: strdup(s);
+ return TAKE_PTR(t) ?: strdup(s);
}
char* unit_concat_strv(char **l, UnitWriteFlags flags) {
_cleanup_free_ char *result = NULL;
size_t n = 0;
- /* Takes a list of strings, escapes them, and concatenates them. This may be used to format command lines in a
- * way suitable for ExecStart= stanzas */
+ /* Takes a list of strings, escapes them, and concatenates them. This may be used to format command
+ * lines in a way suitable for ExecStart= stanzas. */
STRV_FOREACH(i, l) {
_cleanup_free_ char *buf = NULL;
diff --git a/src/core/unit.h b/src/core/unit.h
index fc8edaade5..01cef89525 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -531,19 +531,22 @@ typedef struct UnitStatusMessageFormats {
/* Flags used when writing drop-in files or transient unit files */
typedef enum UnitWriteFlags {
/* Write a runtime unit file or drop-in (i.e. one below /run) */
- UNIT_RUNTIME = 1 << 0,
+ UNIT_RUNTIME = 1 << 0,
/* Write a persistent drop-in (i.e. one below /etc) */
- UNIT_PERSISTENT = 1 << 1,
+ UNIT_PERSISTENT = 1 << 1,
/* Place this item in the per-unit-type private section, instead of [Unit] */
- UNIT_PRIVATE = 1 << 2,
+ UNIT_PRIVATE = 1 << 2,
/* Apply specifier escaping before writing */
- UNIT_ESCAPE_SPECIFIERS = 1 << 3,
+ UNIT_ESCAPE_SPECIFIERS = 1 << 3,
+
+ /* Escape elements of ExecStart= syntax before writing */
+ UNIT_ESCAPE_EXEC_SYNTAX = 1 << 4,
/* Apply C escaping before writing */
- UNIT_ESCAPE_C = 1 << 4,
+ UNIT_ESCAPE_C = 1 << 5,
} UnitWriteFlags;
/* Returns true if neither persistent, nor runtime storage is requested, i.e. this is a check invocation only */