]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add support for Environment in io.systemd.Unit.StartTransient
authorMichael Vogt <michael@amutable.com>
Wed, 29 Apr 2026 13:45:10 +0000 (15:45 +0200)
committerMichael Vogt <michael@amutable.com>
Wed, 13 May 2026 17:28:35 +0000 (19:28 +0200)
This commit adds support to set `Environment` in the
`io.systemd.Unit.StartTransient` varlink call.

The behavior is similar to D-Bus, i.e. a `null` or `[]` clears the
environment. This is not needed for StartTransient() as there the env
always starts empty but it seems a good property to have if this is
reused.

src/core/varlink-unit.c
src/shared/varlink-io.systemd.Unit.c
test/units/TEST-26-SYSTEMCTL.sh

index cb67dfa70fd96d383acc1df09aa17c77aa285b92..10fbc1d7bcf4f4883a778ec5e4195a372bab3b13 100644 (file)
@@ -712,8 +712,15 @@ typedef struct TransientExecContextParameters {
         bool present;
         bool working_directory_set;
         TransientWorkingDirectory working_directory;
+        bool environment_set;
+        char **environment;
 } TransientExecContextParameters;
 
+static void transient_exec_context_parameters_done(TransientExecContextParameters *p) {
+        assert(p);
+        strv_free(p->environment);
+}
+
 typedef struct TransientServiceParameters {
         bool present;
         ServiceType type;
@@ -772,6 +779,7 @@ typedef struct StartTransientContextParameters {
 
 static void start_transient_context_parameters_done(StartTransientContextParameters *p) {
         assert(p);
+        transient_exec_context_parameters_done(&p->exec);
         transient_service_parameters_done(&p->service);
 }
 
@@ -813,10 +821,17 @@ static int dispatch_transient_working_directory(const char *name, sd_json_varian
         return sd_json_dispatch(variant, dispatch, flags, &p->working_directory);
 }
 
+static int dispatch_transient_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+        TransientExecContextParameters *p = ASSERT_PTR(userdata);
+        p->environment_set = true;
+        return sd_json_dispatch_strv(name, variant, flags, &p->environment);
+}
+
 static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
         /* Key names compatible with D-Bus property names */
         static const sd_json_dispatch_field exec_dispatch[] = {
-                { "WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0 },
+                { "WorkingDirectory", SD_JSON_VARIANT_OBJECT,        dispatch_transient_working_directory, 0, 0 },
+                { "Environment",      _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment,       0, 0 },
                 {}
         };
 
@@ -925,6 +940,16 @@ static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, Tran
                                     c->working_directory_home ? "~" : strempty(c->working_directory));
         }
 
+        if (p->environment_set) {
+                r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE);
+                if (r == -E2BIG)
+                        return log_debug_errno(r, "Too many environment assignments.");
+                if (r == -EINVAL)
+                        return log_debug_errno(r, "Invalid Environment list.");
+                if (r < 0)
+                        return r;
+        }
+
         return 0;
 }
 
index dada1d58139d868adc016d85797fdc0996b41b94..ae0f78db9c2e8d450dff3d78cf201119b7cca639 100644 (file)
@@ -1184,7 +1184,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
 
 /* UnitContext is used both as input to StartTransient (subset settable at creation time: ID,
- * Description, Service, and the Exec subset {WorkingDirectory}) and as output from
+ * Description, Service, and the Exec subset {WorkingDirectory, Environment}) and as output from
  * List/StartTransient (full unit configuration). Fields that are not settable at creation time are
  * rejected with PropertyNotSupported when supplied as input. */
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
index ec3f88b3a9260051e7aefbfc02f0e7c3c4b1ff8b..6f615eb1c3ab640c78ff34d0dd616fc749be300a 100755 (executable)
@@ -621,13 +621,17 @@ result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
 echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]'
 timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done'
 
-# Exec.WorkingDirectory
+# Exec.WorkingDirectory and Exec.Environment
 defer_transient_cleanup varlink-transient-exec.service
 result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
-    '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
+    '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
 echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"'
+echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null'
+echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null'
 timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done'
 systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null
+systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null
+systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null
 
 # WorkingDirectory with missingOK=true (path does not exist but unit still starts)
 defer_transient_cleanup varlink-transient-wd-missing.service
@@ -659,6 +663,10 @@ varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
 defer_transient_cleanup varlink-transient-bad-wd.service
 varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
     '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
+# Malformed environment entry (not KEY=VALUE)
+defer_transient_cleanup varlink-transient-bad-env.service
+varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
+    '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
 # Exec on a unit type without an exec context (.target) is rejected
 varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
     '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"