]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
run: add --expand-environment=no to disable server-side envvar expansion
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Sun, 2 Apr 2023 21:17:58 +0000 (23:17 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 24 Apr 2023 08:02:30 +0000 (10:02 +0200)
This uses StartExecEx to get the equivalent of ExecStart=:. StartExecEx was
added in b3d593673c5b8b0b7d781fd26ab2062ca6e7dbdb, so this will not work with
older systemds.

A hint is emitted if we get an error indicating lack of support. PID1 returns
SD_BUS_ERROR_PROPERTY_READ_ONLY, but I'm checking for
SD_BUS_ERROR_UNKNOWN_PROPERTY too for safety.

man/systemd-run.xml
src/run/run.c

index cd9e50d5b8940238529faeee0b61901483ffc588..2ad68d8884c2dc4f68b5ddbc1d507ff1814712e1 100644 (file)
     Consider using the <option>exec</option> service type (i.e. <option>--property=Type=exec</option>) to
     ensure that <command>systemd-run</command> returns successfully only if the specified command line has
     been successfully started.</para>
+
+    <para>After <command>systemd-run</command> passes the command to the service manager, the manager
+    performs variable expansion. This means that dollar characters (<literal>$</literal>) which should not be
+    expanded need to be escaped as <literal>$$</literal>. Expansion can also be disabled using
+    <varname>--expand-environment=no</varname>.</para>
   </refsect1>
 
   <refsect1>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--expand-environment=<replaceable>BOOL</replaceable></option></term>
+
+        <listitem><para>Expand environment variables in command arguments. If enabled (the default), the
+        service manager that spawns the actual command will expand variables specified as
+        <literal>${<replaceable>VARIABLE</replaceable>}</literal> in the same way as in commands specied via
+        <varname>ExecStart=</varname> in units. Note that this is similar to, but not the same as variable
+        expansion in
+        <citerefentry project='man-pages'><refentrytitle>bash</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        and other shells.</para>
+
+        <para>See
+        <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for a description of variable expansion. Disabling variable expansion is useful if the specified
+        command includes or may include a <literal>$</literal> sign.</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>-r</option></term>
         <term><option>--remain-after-exit</option></term>
@@ -533,7 +556,8 @@ There is a screen on:
 
       <programlisting>$ systemd-run --user --wait true
 $ systemd-run --user --wait -p SuccessExitStatus=11 bash -c 'exit 11'
-$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 bash -c 'kill -SIGUSR1 $$$$'</programlisting>
+$ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no \
+      bash -c 'kill -SIGUSR1 $$'</programlisting>
 
       <para>Those three invocations will succeed, i.e. terminate with an exit code of 0.</para>
     </example>
index 409212cbfad7851d23142223336b02d260e11765..3ad866aaa6911e30f47d2abdfaffb6b60b184f2d 100644 (file)
@@ -45,6 +45,7 @@ static const char *arg_unit = NULL;
 static const char *arg_description = NULL;
 static const char *arg_slice = NULL;
 static bool arg_slice_inherit = false;
+static bool arg_expand_environment = true;
 static bool arg_send_sighup = false;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 static const char *arg_host = NULL;
@@ -102,6 +103,8 @@ static int help(void) {
                "     --description=TEXT           Description for unit\n"
                "     --slice=SLICE                Run in the specified slice\n"
                "     --slice-inherit              Inherit the slice\n"
+               "     --expand-environment=BOOL    Control server-side expansion of environment\n"
+               "                                  variables\n"
                "     --no-block                   Do not wait until operation finished\n"
                "  -r --remain-after-exit          Leave service around until explicitly stopped\n"
                "     --wait                       Wait until service stopped again\n"
@@ -168,6 +171,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_DESCRIPTION,
                 ARG_SLICE,
                 ARG_SLICE_INHERIT,
+                ARG_EXPAND_ENVIRONMENT,
                 ARG_SEND_SIGHUP,
                 ARG_SERVICE_TYPE,
                 ARG_EXEC_USER,
@@ -192,47 +196,48 @@ static int parse_argv(int argc, char *argv[]) {
         };
 
         static const struct option options[] = {
-                { "help",              no_argument,       NULL, 'h'                   },
-                { "version",           no_argument,       NULL, ARG_VERSION           },
-                { "user",              no_argument,       NULL, ARG_USER              },
-                { "system",            no_argument,       NULL, ARG_SYSTEM            },
-                { "scope",             no_argument,       NULL, ARG_SCOPE             },
-                { "unit",              required_argument, NULL, 'u'                   },
-                { "description",       required_argument, NULL, ARG_DESCRIPTION       },
-                { "slice",             required_argument, NULL, ARG_SLICE             },
-                { "slice-inherit",     no_argument,       NULL, ARG_SLICE_INHERIT     },
-                { "remain-after-exit", no_argument,       NULL, 'r'                   },
-                { "send-sighup",       no_argument,       NULL, ARG_SEND_SIGHUP       },
-                { "host",              required_argument, NULL, 'H'                   },
-                { "machine",           required_argument, NULL, 'M'                   },
-                { "service-type",      required_argument, NULL, ARG_SERVICE_TYPE      },
-                { "wait",              no_argument,       NULL, ARG_WAIT              },
-                { "uid",               required_argument, NULL, ARG_EXEC_USER         },
-                { "gid",               required_argument, NULL, ARG_EXEC_GROUP        },
-                { "nice",              required_argument, NULL, ARG_NICE              },
-                { "setenv",            required_argument, NULL, 'E'                   },
-                { "property",          required_argument, NULL, 'p'                   },
-                { "tty",               no_argument,       NULL, 't'                   }, /* deprecated alias */
-                { "pty",               no_argument,       NULL, 't'                   },
-                { "pipe",              no_argument,       NULL, 'P'                   },
-                { "quiet",             no_argument,       NULL, 'q'                   },
-                { "on-active",         required_argument, NULL, ARG_ON_ACTIVE         },
-                { "on-boot",           required_argument, NULL, ARG_ON_BOOT           },
-                { "on-startup",        required_argument, NULL, ARG_ON_STARTUP        },
-                { "on-unit-active",    required_argument, NULL, ARG_ON_UNIT_ACTIVE    },
-                { "on-unit-inactive",  required_argument, NULL, ARG_ON_UNIT_INACTIVE  },
-                { "on-calendar",       required_argument, NULL, ARG_ON_CALENDAR       },
-                { "on-timezone-change",no_argument,       NULL, ARG_ON_TIMEZONE_CHANGE},
-                { "on-clock-change",   no_argument,       NULL, ARG_ON_CLOCK_CHANGE   },
-                { "timer-property",    required_argument, NULL, ARG_TIMER_PROPERTY    },
-                { "path-property",     required_argument, NULL, ARG_PATH_PROPERTY     },
-                { "socket-property",   required_argument, NULL, ARG_SOCKET_PROPERTY   },
-                { "no-block",          no_argument,       NULL, ARG_NO_BLOCK          },
-                { "no-ask-password",   no_argument,       NULL, ARG_NO_ASK_PASSWORD   },
-                { "collect",           no_argument,       NULL, 'G'                   },
-                { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
-                { "same-dir",          no_argument,       NULL, 'd'                   },
-                { "shell",             no_argument,       NULL, 'S'                   },
+                { "help",               no_argument,       NULL, 'h'                    },
+                { "version",            no_argument,       NULL, ARG_VERSION            },
+                { "user",               no_argument,       NULL, ARG_USER               },
+                { "system",             no_argument,       NULL, ARG_SYSTEM             },
+                { "scope",              no_argument,       NULL, ARG_SCOPE              },
+                { "unit",               required_argument, NULL, 'u'                    },
+                { "description",        required_argument, NULL, ARG_DESCRIPTION        },
+                { "slice",              required_argument, NULL, ARG_SLICE              },
+                { "slice-inherit",      no_argument,       NULL, ARG_SLICE_INHERIT      },
+                { "remain-after-exit",  no_argument,       NULL, 'r'                    },
+                { "expand-environment", required_argument, NULL, ARG_EXPAND_ENVIRONMENT },
+                { "send-sighup",        no_argument,       NULL, ARG_SEND_SIGHUP        },
+                { "host",               required_argument, NULL, 'H'                    },
+                { "machine",            required_argument, NULL, 'M'                    },
+                { "service-type",       required_argument, NULL, ARG_SERVICE_TYPE       },
+                { "wait",               no_argument,       NULL, ARG_WAIT               },
+                { "uid",                required_argument, NULL, ARG_EXEC_USER          },
+                { "gid",                required_argument, NULL, ARG_EXEC_GROUP         },
+                { "nice",               required_argument, NULL, ARG_NICE               },
+                { "setenv",             required_argument, NULL, 'E'                    },
+                { "property",           required_argument, NULL, 'p'                    },
+                { "tty",                no_argument,       NULL, 't'                    }, /* deprecated alias */
+                { "pty",                no_argument,       NULL, 't'                    },
+                { "pipe",               no_argument,       NULL, 'P'                    },
+                { "quiet",              no_argument,       NULL, 'q'                    },
+                { "on-active",          required_argument, NULL, ARG_ON_ACTIVE          },
+                { "on-boot",            required_argument, NULL, ARG_ON_BOOT            },
+                { "on-startup",         required_argument, NULL, ARG_ON_STARTUP         },
+                { "on-unit-active",     required_argument, NULL, ARG_ON_UNIT_ACTIVE     },
+                { "on-unit-inactive",   required_argument, NULL, ARG_ON_UNIT_INACTIVE   },
+                { "on-calendar",        required_argument, NULL, ARG_ON_CALENDAR        },
+                { "on-timezone-change", no_argument,       NULL, ARG_ON_TIMEZONE_CHANGE },
+                { "on-clock-change",    no_argument,       NULL, ARG_ON_CLOCK_CHANGE    },
+                { "timer-property",     required_argument, NULL, ARG_TIMER_PROPERTY     },
+                { "path-property",      required_argument, NULL, ARG_PATH_PROPERTY      },
+                { "socket-property",    required_argument, NULL, ARG_SOCKET_PROPERTY    },
+                { "no-block",           no_argument,       NULL, ARG_NO_BLOCK           },
+                { "no-ask-password",    no_argument,       NULL, ARG_NO_ASK_PASSWORD    },
+                { "collect",            no_argument,       NULL, 'G'                    },
+                { "working-directory",  required_argument, NULL, ARG_WORKING_DIRECTORY  },
+                { "same-dir",           no_argument,       NULL, 'd'                    },
+                { "shell",              no_argument,       NULL, 'S'                    },
                 {},
         };
 
@@ -284,6 +289,12 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_slice_inherit = true;
                         break;
 
+                case ARG_EXPAND_ENVIRONMENT:
+                        r = parse_boolean_argument("--expand-environment=", optarg, &arg_expand_environment);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case ARG_SEND_SIGHUP:
                         arg_send_sighup = true;
                         break;
@@ -716,6 +727,11 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
         bool send_term = false;
         int r;
 
+        /* We disable environment expansion on the server side via ExecStartEx=:.
+         * ExecStartEx was added relatively recently (v243), and some bugs were fixed only later.
+         * So use that feature only if required. It will fail with older systemds. */
+        bool use_ex_prop = !arg_expand_environment;
+
         assert(m);
 
         r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property);
@@ -847,19 +863,23 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_append(m, "s", "ExecStart");
+                r = sd_bus_message_append(m, "s",
+                                          use_ex_prop ? "ExecStartEx" : "ExecStart");
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_open_container(m, 'v', "a(sasb)");
+                r = sd_bus_message_open_container(m, 'v',
+                                                  use_ex_prop ? "a(sasas)" : "a(sasb)");
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_open_container(m, 'a', "(sasb)");
+                r = sd_bus_message_open_container(m, 'a',
+                                                  use_ex_prop ? "(sasas)" : "(sasb)");
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_open_container(m, 'r', "sasb");
+                r = sd_bus_message_open_container(m, 'r',
+                                                  use_ex_prop ? "sasas" : "sasb");
                 if (r < 0)
                         return bus_log_create_error(r);
 
@@ -871,7 +891,12 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_append(m, "b", false);
+                if (use_ex_prop)
+                        r = sd_bus_message_append_strv(
+                                        m,
+                                        STRV_MAKE(arg_expand_environment ? NULL : "no-env-expand"));
+                else
+                        r = sd_bus_message_append(m, "b", false);
                 if (r < 0)
                         return bus_log_create_error(r);
 
@@ -1157,6 +1182,29 @@ static int make_transient_service_unit(
         return 0;
 }
 
+static int bus_call_with_hint(
+                sd_bus *bus,
+                sd_bus_message *message,
+                const char *name,
+                sd_bus_message **reply) {
+
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        r = sd_bus_call(bus, message, 0, &error, reply);
+        if (r < 0) {
+                log_error_errno(r, "Failed to start transient %s unit: %s", name, bus_error_message(&error, r));
+
+                if (!arg_expand_environment &&
+                    sd_bus_error_has_names(&error,
+                                           SD_BUS_ERROR_UNKNOWN_PROPERTY,
+                                           SD_BUS_ERROR_PROPERTY_READ_ONLY))
+                        log_notice_errno(r, "Hint: --expand-environment=no is not supported by old systemd");
+        }
+
+        return r;
+}
+
 static int start_transient_service(sd_bus *bus) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -1244,9 +1292,9 @@ static int start_transient_service(sd_bus *bus) {
 
         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = sd_bus_call(bus, m, 0, &error, &reply);
+        r = bus_call_with_hint(bus, m, "service", &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r));
+                return r;
 
         if (w) {
                 const char *object;
@@ -1659,7 +1707,6 @@ static int make_transient_trigger_unit(
 }
 
 static int start_transient_trigger(sd_bus *bus, const char *suffix) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
         _cleanup_free_ char *trigger = NULL, *service = NULL;
@@ -1727,9 +1774,9 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
 
         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = sd_bus_call(bus, m, 0, &error, &reply);
+        r = bus_call_with_hint(bus, m, suffix + 1, &reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to start transient %s unit: %s", suffix + 1, bus_error_message(&error, r));
+                return r;
 
         r = sd_bus_message_read(reply, "o", &object);
         if (r < 0)