From 9c39db197b6b5bc0e3ed1706eb32fbf34ee4d246 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 21 Apr 2026 09:10:39 +0200 Subject: [PATCH 1/5] varlink: add new io.systemd.Job interface This commit creates a new varlink-io.systemd.Job.c file and puts the job related varlink types into this file. Those will be used by the upcoming io.systemd.Unit.StartTransient. Note that the property names follow the D-Bus naming to make a future "systemctl show" transition from D-Bus -> varlink easier. Thanks to @ikruglov for suggesting this preparation commit and to Lennart for suggesting the D-Bus compatibility considerations. --- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Job.c | 65 +++++++++++++++++++++++++++++ src/shared/varlink-io.systemd.Job.h | 10 +++++ src/test/test-varlink-idl.c | 2 + 4 files changed, 78 insertions(+) create mode 100644 src/shared/varlink-io.systemd.Job.c create mode 100644 src/shared/varlink-io.systemd.Job.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 84acaf698b..c28fe040b6 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -223,6 +223,7 @@ shared_sources = files( 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', 'varlink-io.systemd.InstanceMetadata.c', + 'varlink-io.systemd.Job.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.JournalAccess.c', 'varlink-io.systemd.Login.c', diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c new file mode 100644 index 0000000000..af15f8f45b --- /dev/null +++ b/src/shared/varlink-io.systemd.Job.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Job.h" + +/* Keep in sync with job_type_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobType, + SD_VARLINK_DEFINE_ENUM_VALUE(start), + SD_VARLINK_DEFINE_ENUM_VALUE(verify_active), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + SD_VARLINK_DEFINE_ENUM_VALUE(reload), + SD_VARLINK_DEFINE_ENUM_VALUE(reload_or_start), + SD_VARLINK_DEFINE_ENUM_VALUE(restart), + SD_VARLINK_DEFINE_ENUM_VALUE(try_restart), + SD_VARLINK_DEFINE_ENUM_VALUE(try_reload), + SD_VARLINK_DEFINE_ENUM_VALUE(nop)); + +/* Keep in sync with job_state_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobState, + SD_VARLINK_DEFINE_ENUM_VALUE(waiting), + SD_VARLINK_DEFINE_ENUM_VALUE(running), + SD_VARLINK_DEFINE_ENUM_VALUE(finished)); + +/* Keep in sync with job_result_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobResult, + SD_VARLINK_DEFINE_ENUM_VALUE(done), + SD_VARLINK_DEFINE_ENUM_VALUE(canceled), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(failed), + SD_VARLINK_DEFINE_ENUM_VALUE(dependency), + SD_VARLINK_DEFINE_ENUM_VALUE(skipped), + SD_VARLINK_DEFINE_ENUM_VALUE(invalid), + SD_VARLINK_DEFINE_ENUM_VALUE(assert), + SD_VARLINK_DEFINE_ENUM_VALUE(unsupported), + SD_VARLINK_DEFINE_ENUM_VALUE(collected), + SD_VARLINK_DEFINE_ENUM_VALUE(once), + SD_VARLINK_DEFINE_ENUM_VALUE(frozen), + SD_VARLINK_DEFINE_ENUM_VALUE(concurrency)); + +/* Field names match the D-Bus Job properties (Id, JobType, State) */ +SD_VARLINK_DEFINE_STRUCT_TYPE( + Job, + SD_VARLINK_FIELD_COMMENT("The numeric job ID"), + SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The job type"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobType, JobType, 0), + SD_VARLINK_FIELD_COMMENT("Current job state, set in intermediate streaming notifications"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Final job result, set in the final streaming reply"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Job, + "io.systemd.Job", + SD_VARLINK_INTERFACE_COMMENT("Job-related types for the systemd service manager."), + SD_VARLINK_SYMBOL_COMMENT("Job type"), + &vl_type_JobType, + SD_VARLINK_SYMBOL_COMMENT("Job state"), + &vl_type_JobState, + SD_VARLINK_SYMBOL_COMMENT("Job result"), + &vl_type_JobResult, + SD_VARLINK_SYMBOL_COMMENT("A job object"), + &vl_type_Job); diff --git a/src/shared/varlink-io.systemd.Job.h b/src/shared/varlink-io.systemd.Job.h new file mode 100644 index 0000000000..029d56aa21 --- /dev/null +++ b/src/shared/varlink-io.systemd.Job.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_symbol vl_type_JobType; +extern const sd_varlink_symbol vl_type_JobState; +extern const sd_varlink_symbol vl_type_JobResult; +extern const sd_varlink_symbol vl_type_Job; +extern const sd_varlink_interface vl_interface_io_systemd_Job; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index d0f3b91447..a645d4d9d3 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -25,6 +25,7 @@ #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.Import.h" #include "varlink-io.systemd.InstanceMetadata.h" +#include "varlink-io.systemd.Job.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.JournalAccess.h" #include "varlink-io.systemd.Login.h" @@ -192,6 +193,7 @@ TEST(parse_format) { &vl_interface_io_systemd_Hostname, &vl_interface_io_systemd_Import, &vl_interface_io_systemd_InstanceMetadata, + &vl_interface_io_systemd_Job, &vl_interface_io_systemd_Journal, &vl_interface_io_systemd_JournalAccess, &vl_interface_io_systemd_Login, From 8edd9b2b66aacbf4d8bd061b76ba734fd39046b4 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 14 Apr 2026 16:24:07 +0200 Subject: [PATCH 2/5] core: extract manager_setup_transient_unit() helper Extract the transient unit setup logic from transient_unit_from_message() in dbus-manager.c into a shared helper. This prepares for reuse by the varlink StartTransient implementation. --- src/core/dbus-manager.c | 23 +---------------------- src/core/unit.c | 36 ++++++++++++++++++++++++++++++++++++ src/core/unit.h | 1 + 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 1bc73e7b43..f48d04a4cb 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1028,7 +1028,6 @@ static int transient_unit_from_message( Unit **ret_unit, sd_bus_error *reterr_error) { - UnitType t; Unit *u; int r; @@ -1036,27 +1035,7 @@ static int transient_unit_from_message( assert(message); assert(name); - t = unit_name_to_type(name); - if (t < 0) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, - "Invalid unit name or type: %s", name); - - if (!unit_vtable[t]->can_transient) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, - "Unit type %s does not support transient units.", - unit_type_to_string(t)); - - r = manager_load_unit(m, name, NULL, reterr_error, &u); - if (r < 0) - return r; - - if (!unit_is_pristine(u)) - return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS, - "Unit %s was already loaded or has a fragment file.", name); - - /* OK, the unit failed to load and is unreferenced, now let's - * fill in the transient data instead */ - r = unit_make_transient(u); + r = manager_setup_transient_unit(m, name, &u, reterr_error); if (r < 0) return r; diff --git a/src/core/unit.c b/src/core/unit.c index 3404d7f8d3..de0276a813 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -4893,6 +4893,42 @@ int unit_make_transient(Unit *u) { return 0; } +int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error) { + Unit *u; + int r; + + assert(m); + assert(name); + assert(ret); + + UnitType t = unit_name_to_type(name); + if (t < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid unit name or type: %s", name); + + if (!unit_vtable[t]->can_transient) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Unit type %s does not support transient units.", + unit_type_to_string(t)); + + r = manager_load_unit(m, name, /* path= */ NULL, reterr_error, &u); + if (r < 0) + return r; + + if (!unit_is_pristine(u)) + return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS, + "Unit %s was already loaded or has a fragment file.", name); + + /* OK, the unit failed to load and is unreferenced, now let's + * fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return r; + + *ret = u; + return 0; +} + static bool ignore_leftover_process(const char *comm) { return comm && comm[0] == '('; /* Most likely our own helper process (PAM?), ignore */ } diff --git a/src/core/unit.h b/src/core/unit.h index e503c2e344..54794e8be7 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -973,6 +973,7 @@ int unit_write_settingf(Unit *u, UnitWriteFlags flags, const char *name, const c int unit_kill_context(Unit *u, KillOperation k); int unit_make_transient(Unit *u); +int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error); int unit_add_mounts_for(Unit *u, const char *path, UnitDependencyMask mask, UnitMountDependencyType type); From 913b81735e740c9d700647754c466bc8b75943d7 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 14 Apr 2026 17:04:15 +0200 Subject: [PATCH 3/5] core: add io.systemd.Unit.StartTransient() to the varlink API This commit adds a simple version of io.systemd.Unit.StartTransient for varlink. It is similar to the dbus version, but there is a key difference: 1. Instead of building the unit from key/value properties it takes a json object in the "service" parameter. It is also only implementing a minimal set of what can be done with a service (for now) 2. No aux units (for now) 3. When called with --more the varlink socket can notify about unit job and state changes controlled via a bool on the varlink call inputs: notify{Job,Unit}Changes We use the new io.systemd.Job interface when outputing the io.systemd.Unit.StartTransient result as it makes the output nice and mirrors the input. Note that the property names follow the D-Bus naming to make a future "systemctl show" transition from D-Bus -> varlink easier. Because UnitContext is now also used for the inputs we need to make a bunch of fields `SD_VARLINK_NULLABLE` so that the input is even accepted. This does not affect the output, it is still fully populated, just the schema. The ID of UnitContext is still required. Thanks to ikruglov and Lennart for their excellent feedback on this. --- src/core/job.c | 7 +- src/core/job.h | 5 + src/core/manager.c | 3 + src/core/unit.c | 2 + src/core/unit.h | 4 + src/core/varlink-common.c | 10 + src/core/varlink-common.h | 1 + src/core/varlink-manager.c | 1 + src/core/varlink-unit.c | 442 +++++++++++++++++++++++++++ src/core/varlink-unit.h | 10 + src/core/varlink.c | 28 +- src/shared/varlink-io.systemd.Job.c | 4 +- src/shared/varlink-io.systemd.Unit.c | 107 ++++++- 13 files changed, 601 insertions(+), 23 deletions(-) diff --git a/src/core/job.c b/src/core/job.c index b4578a946c..44652d5f3e 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -2,6 +2,7 @@ #include "sd-bus.h" #include "sd-messages.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "ansi-color.h" @@ -24,6 +25,7 @@ #include "string-util.h" #include "strv.h" #include "unit.h" +#include "varlink-unit.h" #include "virt.h" Job* job_new_raw(Unit *unit) { @@ -124,6 +126,7 @@ Job* job_free(Job *j) { job_unlink(j); sd_bus_track_unref(j->bus_track); + sd_varlink_unref(j->varlink); strv_free(j->deserialized_clients); activation_details_unref(j->activation_details); @@ -173,8 +176,10 @@ void job_uninstall(Job *j) { /* Detach from next 'bigger' objects */ /* daemon-reload should be transparent to job observers */ - if (!MANAGER_IS_RELOADING(j->manager)) + if (!MANAGER_IS_RELOADING(j->manager)) { bus_job_send_removed_signal(j); + varlink_job_send_removed_signal(j); + } *pj = NULL; diff --git a/src/core/job.h b/src/core/job.h index a8eca99505..45dccdaaae 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -125,6 +125,9 @@ typedef struct Job { sd_bus_track *bus_track; char **deserialized_clients; + /* If non-NULL, a varlink connection streaming updates. */ + sd_varlink *varlink; + /* If the job had a specific trigger that needs to be advertised (eg: a path unit), store it. */ ActivationDetails *activation_details; @@ -142,6 +145,8 @@ typedef struct Job { bool ref_by_private_bus:1; bool in_gc_queue:1; + + bool varlink_notify_job_changes:1; } Job; Job* job_new(Unit *unit, JobType type); diff --git a/src/core/manager.c b/src/core/manager.c index 56ee66e5bd..17908d4db8 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -93,6 +93,7 @@ #include "umask-util.h" #include "unit-name.h" #include "user-util.h" +#include "varlink-unit.h" #include "varlink.h" #include "virt.h" #include "watchdog.h" @@ -2636,6 +2637,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { assert(u->in_dbus_queue); bus_unit_send_change_signal(u); + varlink_unit_send_change_signal(u); n++; if (budget != UINT_MAX) @@ -2646,6 +2648,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { assert(j->in_dbus_queue); bus_job_send_change_signal(j); + varlink_job_send_change_signal(j); n++; if (budget != UINT_MAX) diff --git a/src/core/unit.c b/src/core/unit.c index de0276a813..0edb7e25aa 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -7,6 +7,7 @@ #include "sd-bus.h" #include "sd-id128.h" #include "sd-messages.h" +#include "sd-varlink.h" #include "all-units.h" #include "alloc-util.h" @@ -791,6 +792,7 @@ Unit* unit_free(Unit *u) { u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); u->bus_track = sd_bus_track_unref(u->bus_track); + u->varlink_unit_change = sd_varlink_unref(u->varlink_unit_change); u->deserialized_refs = strv_free(u->deserialized_refs); u->pending_freezer_invocation = sd_bus_message_unref(u->pending_freezer_invocation); diff --git a/src/core/unit.h b/src/core/unit.h index 54794e8be7..d79bb7a98a 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -289,6 +289,10 @@ typedef struct Unit { /* References to this unit from clients */ sd_bus_track *bus_track; + + /* If non-NULL, a varlink connection streaming unit state change notifications */ + sd_varlink *varlink_unit_change; + char **deserialized_refs; /* References to this */ diff --git a/src/core/varlink-common.c b/src/core/varlink-common.c index bdef4d5a94..6f876db85b 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-bus.h" +#include "sd-varlink.h" #include "bus-common-errors.h" #include "cpu-set-util.h" @@ -18,6 +19,8 @@ const char* varlink_error_id_from_bus_error(const sd_bus_error *e) { { BUS_ERROR_NO_SUCH_UNIT, VARLINK_ERROR_UNIT_NO_SUCH_UNIT }, { BUS_ERROR_ONLY_BY_DEPENDENCY, VARLINK_ERROR_UNIT_ONLY_BY_DEPENDENCY }, { BUS_ERROR_SHUTTING_DOWN, VARLINK_ERROR_UNIT_DBUS_SHUTTING_DOWN }, + { BUS_ERROR_UNIT_EXISTS, VARLINK_ERROR_UNIT_UNIT_EXISTS }, + { BUS_ERROR_BAD_UNIT_SETTING, VARLINK_ERROR_UNIT_BAD_SETTING }, }; if (!sd_bus_error_is_set(e)) @@ -30,6 +33,13 @@ const char* varlink_error_id_from_bus_error(const sd_bus_error *e) { return NULL; } +int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e) { + const char *error_id = varlink_error_id_from_bus_error(e); + if (error_id) + return sd_varlink_error(link, error_id, NULL); + return sd_varlink_error_errno(link, r); +} + int rlimit_build_json(sd_json_variant **ret, const char *name, void *userdata) { const struct rlimit *rl = userdata; struct rlimit buf = {}; diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index fc919dcf36..aa9f26145c 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -8,3 +8,4 @@ int rlimit_table_build_json(sd_json_variant **ret, const char *name, void *userd int cpuset_build_json(sd_json_variant **ret, const char *name, void *userdata); const char* varlink_error_id_from_bus_error(const sd_bus_error *e); int exec_command_build_json(sd_json_variant **ret, const char *name, void *userdata); +int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e); diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 96287dc32f..0bef5cbe98 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -381,6 +381,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par JOB_FAIL, /* reload_if_possible= */ !BIT_SET(u->markers, UNIT_MARKER_NEEDS_RESTART), &job_id, + /* ret_job= */ NULL, &bus_error); if (ERRNO_IS_NEG_RESOURCE(r)) return r; diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 789b21947c..7111baf739 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-bus.h" #include "sd-json.h" #include "bitfield.h" @@ -10,16 +11,21 @@ #include "execute.h" #include "format-util.h" #include "install.h" +#include "job.h" #include "json-util.h" +#include "locale-util.h" #include "manager.h" #include "path-util.h" #include "pidref.h" #include "selinux-access.h" +#include "service.h" #include "set.h" #include "strv.h" +#include "unit-name.h" #include "unit.h" #include "varlink-automount.h" #include "varlink-cgroup.h" +#include "varlink-common.h" #include "varlink-execute.h" #include "varlink-kill.h" #include "varlink-mount.h" @@ -108,6 +114,43 @@ static int unit_conditions_build_json(sd_json_variant **ret, const char *name, v return 0; } +static int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ExecCommand *list = userdata; + int r; + + assert(ret); + + LIST_FOREACH(command, c, list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = exec_command_build_json(&entry, /* name= */ NULL, c); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&v, entry); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +/* TODO: This covers only a small subset of a service object's properties. Extend to make more available to + * consumers like Unit.StartTransient */ +static int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Service *s = ASSERT_PTR(SERVICE(u)); + assert(ret); + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_ENUM("Type", service_type_to_string(s->type)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStart", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START]), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit)); +} + static int unit_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { Unit *u = ASSERT_PTR(userdata); @@ -119,6 +162,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void static const sd_json_build_callback_t unit_type_callbacks[_UNIT_TYPE_MAX] = { [UNIT_AUTOMOUNT] = automount_context_build_json, [UNIT_MOUNT] = mount_context_build_json, + [UNIT_SERVICE] = service_context_build_json, }; return sd_json_buildo( @@ -548,6 +592,7 @@ int varlink_unit_queue_job_one( JobMode mode, bool reload_if_possible, uint32_t *ret_job_id, + Job **ret_job, sd_bus_error *reterr_bus_error) { int r; @@ -568,6 +613,8 @@ int varlink_unit_queue_job_one( if (ret_job_id) *ret_job_id = j->id; + if (ret_job) + *ret_job = j; return 0; } @@ -579,6 +626,401 @@ int varlink_error_no_such_unit(sd_varlink *v, const char *name) { JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name)); } +void varlink_unit_send_change_signal(Unit *u) { + assert(u); + + if (!u->varlink_unit_change) + return; + + (void) sd_varlink_notifybo( + u->varlink_unit_change, + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u)); +} + +static int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Job *j = ASSERT_PTR(userdata); + + /* We cannot just "blindly" use the j->state as it will get reset to WAITING in + * core/job.c:job_uninstall() before varlink_job_send_removed_signal(). This happens from + * unit.c:unit_free(). It is probably something that should be fixed but its a subtle part of + * systemd so for now we just deal with it here. */ + JobState state = j->result >= 0 ? JOB_FINISHED : j->state; + + /* Note that "Result" is suppressed until the job reaches JOB_FINISHED. */ + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), + JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), + JSON_BUILD_PAIR_ENUM("State", job_state_to_string(state)), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("Result", job_result_to_string(j->result))); +} + +void varlink_job_send_change_signal(Job *j) { + assert(j); + + if (!j->varlink || !j->varlink_notify_job_changes) + return; + + (void) sd_varlink_notifybo( + j->varlink, + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); +} + +void varlink_job_send_removed_signal(Job *j) { + assert(j); + + if (!j->varlink) + return; + + /* Send the final reply, which completes the method call */ + (void) sd_varlink_replybo( + j->varlink, + SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, j->unit), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, j->unit), + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + j->varlink = sd_varlink_unref(j->varlink); + j->unit->varlink_unit_change = sd_varlink_unref(j->unit->varlink_unit_change); +} + +typedef struct TransientExecCommandItem { + const char *path; + char **arguments; +} TransientExecCommandItem; + +static void transient_exec_command_item_done(TransientExecCommandItem *i) { + assert(i); + strv_free(i->arguments); +} + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_service_type, ServiceType, service_type_from_string); +static JSON_DISPATCH_ENUM_DEFINE(dispatch_job_mode, JobMode, job_mode_from_string); + +typedef struct TransientServiceParameters { + ServiceType type; + TransientExecCommandItem *exec_start; + size_t n_exec_start; + int remain_after_exit; +} TransientServiceParameters; + +static void transient_service_parameters_done(TransientServiceParameters *p) { + assert(p); + FOREACH_ARRAY(i, p->exec_start, p->n_exec_start) + transient_exec_command_item_done(i); + free(p->exec_start); +} + +static int dispatch_transient_exec_command(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field exec_command_dispatch[] = { + { "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecCommandItem, path), SD_JSON_MANDATORY }, + { "arguments", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecCommandItem, arguments), 0 }, + {} + }; + + TransientServiceParameters *p = ASSERT_PTR(userdata); + size_t n; + int r; + + if (!sd_json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Expected JSON array for ExecStart."); + + n = sd_json_variant_elements(variant); + if (n == 0) + return 0; + + p->exec_start = new0(TransientExecCommandItem, n); + if (!p->exec_start) + return -ENOMEM; + p->n_exec_start = n; + + for (size_t i = 0; i < n; i++) { + sd_json_variant *element = sd_json_variant_by_index(variant, i); + + r = sd_json_dispatch(element, exec_command_dispatch, /* flags= */ 0, &p->exec_start[i]); + if (r < 0) + return r; + } + return 0; +} + +typedef struct StartTransientContextParameters { + const char *id; + const char *description; + TransientServiceParameters service; +} StartTransientContextParameters; + +static void start_transient_context_parameters_done(StartTransientContextParameters *p) { + assert(p); + transient_service_parameters_done(&p->service); +} + +typedef struct StartTransientParameters { + StartTransientContextParameters context; + JobMode mode; + int notify_job_changes; + int notify_unit_changes; + const char *unsupported_property; /* For error reporting on unknown context fields */ +} StartTransientParameters; + +static void start_transient_parameters_done(StartTransientParameters *p) { + assert(p); + start_transient_context_parameters_done(&p->context); +} + +static int dispatch_transient_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field service_dispatch[] = { + { "Type", SD_JSON_VARIANT_STRING, dispatch_service_type, offsetof(TransientServiceParameters, type), 0 }, + { "ExecStart", SD_JSON_VARIANT_ARRAY, dispatch_transient_exec_command, 0, 0 }, + { "RemainAfterExit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(TransientServiceParameters, remain_after_exit), 0 }, + {} + }; + + StartTransientContextParameters *p = ASSERT_PTR(userdata); + return sd_json_dispatch(variant, service_dispatch, /* flags= */ 0, &p->service); +} + +static int dispatch_transient_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field context_dispatch[] = { + { "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY }, + { "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 }, + { "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 }, + {} + }; + + StartTransientParameters *p = ASSERT_PTR(userdata); + const char *bad_field = NULL; + int r; + + /* Don't propagate the caller's flags (in particular SD_JSON_MANDATORY from the outer 'context' + * field) into the nested dispatch, otherwise every inner field becomes mandatory. */ + r = sd_json_dispatch_full(variant, context_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->context, &bad_field); + if (r == -EADDRNOTAVAIL && !isempty(bad_field)) + /* A UnitContext field that exists in the schema but is not settable at creation time: stash + * the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. */ + p->unsupported_property = bad_field; + return r; +} + +static int transient_service_apply_properties(Unit *u, TransientServiceParameters *sp) { + int r; + + Service *s = ASSERT_PTR(SERVICE(u)); + assert(sp); + + if (sp->type >= 0) { + s->type = sp->type; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Type", "Type=%s", service_type_to_string(sp->type)); + } + + if (sp->remain_after_exit >= 0) { + s->remain_after_exit = sp->remain_after_exit; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "RemainAfterExit", "RemainAfterExit=%s", yes_no(sp->remain_after_exit)); + } + + FOREACH_ARRAY(item, sp->exec_start, sp->n_exec_start) { + _cleanup_(exec_command_freep) ExecCommand *c = NULL; + _cleanup_strv_free_ char **argv = NULL; + + if (!filename_or_absolute_path_is_valid(item->path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", item->path); + + if (!strv_isempty(item->arguments)) { + argv = strv_copy(item->arguments); + if (!argv) + return -ENOMEM; + } + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + r = path_simplify_alloc(item->path, &c->path); + if (r < 0) + return r; + + /* If no arguments were provided, default argv[0] to the executable path. + * Otherwise the caller is expected to include argv[0] in the arguments array. */ + if (strv_isempty(argv)) { + r = strv_extend(&argv, c->path); + if (r < 0) + return r; + } + + c->argv = TAKE_PTR(argv); + + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], TAKE_PTR(c)); + } + + /* Write ExecStart= lines to the transient file */ + if (sp->n_exec_start > 0) { + UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX_ENV; + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + _cleanup_free_ char *a = NULL; + + a = unit_concat_strv(c->argv, esc_flags); + if (!a) + return -ENOMEM; + + /* streq() instead path_equal() as argv[0] can be arbitrary and may not be a path */ + if (streq(c->path, c->argv[0])) + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=%s", a); + else { + _cleanup_free_ char *t = NULL; + const char *p; + + p = unit_escape_setting(c->path, esc_flags, &t); + if (!p) + return -ENOMEM; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=@%s %s", p, a); + } + } + } + + return 0; +} + +int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "context", SD_JSON_VARIANT_OBJECT, dispatch_transient_context, 0, SD_JSON_MANDATORY }, + { "mode", SD_JSON_VARIANT_STRING, dispatch_job_mode, offsetof(StartTransientParameters, mode), 0 }, + { "notifyJobChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_job_changes), 0 }, + { "notifyUnitChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_unit_changes), 0 }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error bus_error = SD_BUS_ERROR_NULL; + _cleanup_(start_transient_parameters_done) StartTransientParameters p = { + .mode = JOB_REPLACE, + .notify_job_changes = -1, + .notify_unit_changes = -1, + .context.service.type = _SERVICE_TYPE_INVALID, + .context.service.remain_after_exit = -1, + }; + Manager *manager = ASSERT_PTR(userdata); + const char *bad_field = NULL; + Unit *u; + int r; + + assert(link); + assert(parameters); + + r = mac_selinux_access_check_varlink(link, "start"); + if (r < 0) + return r; + + r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, /* flags= */ 0, &p, &bad_field); + if (r < 0) { + /* An unknown field in 'context' maps to PropertyNotSupported (the field is defined in the + * UnitContext schema but cannot be set at creation time). Anything else is a bad parameter. */ + if (streq_ptr(bad_field, "context") && r == -EADDRNOTAVAIL && p.unsupported_property) + return sd_varlink_errorbo( + link, + "io.systemd.Unit.PropertyNotSupported", + SD_JSON_BUILD_PAIR_STRING("property", p.unsupported_property)); + if (bad_field) + return sd_varlink_error_invalid_parameter_name(link, bad_field); + return r; + } + + /* Pre-check unit type early and return targeted varlink error as manager_setup_transient_unit() the + * too generic SD_BUS_ERROR_INVALID_ARGS. */ + UnitType t = unit_name_to_type(p.context.id); + if (t < 0) + return sd_varlink_error_invalid_parameter_name(link, "context"); + if (!unit_vtable[t]->can_transient) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "unit", p.context.id, + "verb", "start", + "polkit.message", N_("Authentication is required to start transient unit '$(unit)'."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + r = manager_setup_transient_unit(manager, p.context.id, &u, &bus_error); + if (r < 0) + return varlink_reply_bus_error(link, r, &bus_error); + + /* Apply unit-level properties from context */ + if (p.context.description) { + r = unit_set_description(u, p.context.description); + if (r < 0) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + unit_write_settingf(u, UNIT_RUNTIME|UNIT_ESCAPE_SPECIFIERS, "Description", "Description=%s", p.context.description); + } + + /* Apply service-specific properties from context.Service */ + if (p.context.service.type >= 0 || p.context.service.n_exec_start > 0 || p.context.service.remain_after_exit >= 0) { + if (t != UNIT_SERVICE) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + r = transient_service_apply_properties(u, &p.context.service); + if (r < 0) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + } + + unit_add_to_load_queue(u); + manager_dispatch_load_queue(manager); + + if (u->load_state == UNIT_BAD_SETTING) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_BAD_SETTING, NULL); + if (!UNIT_IS_LOAD_COMPLETE(u->load_state)) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT, NULL); + + Job *j; + r = varlink_unit_queue_job_one( + u, + JOB_START, + p.mode, + /* reload_if_possible= */ false, + /* ret_job_id= */ NULL, + &j, + &bus_error); + if (r < 0) + return varlink_reply_bus_error(link, r, &bus_error); + + bool notify_job = p.notify_job_changes > 0; + bool notify_unit = p.notify_unit_changes > 0; + + /* Non-streaming, or fire-and-forget (no notification flags set): return full unit context + * and runtime, plus the job object so the caller can correlate with later state. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE) || (!notify_job && !notify_unit)) + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + /* Streaming: always attach to the job for the final reply, and optionally to the unit for state + * change notifications. j->varlink owns the stream lifetime, u->varlink_unit_change is just a flag + * to also send unit state notifications along the way. */ + assert(!j->varlink); + j->varlink = sd_varlink_ref(link); + j->varlink_notify_job_changes = notify_job; + if (notify_unit) { + assert(!u->varlink_unit_change); + u->varlink_unit_change = sd_varlink_ref(link); + } + + /* Send initial job state notification if requested. Unit state change notifications are not sent + * here; they will arrive via varlink_unit_send_change_signal() when the unit actually transitions, + * matching D-Bus PropertiesChanged behavior. */ + if (notify_job) + return sd_varlink_notifybo( + link, + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + return 0; +} + typedef struct UnitSetPropertiesParameters { const char *unsupported_property; /* For error reporting */ const char *name; diff --git a/src/core/varlink-unit.h b/src/core/varlink-unit.h index 8f58d7ce10..9460245ec0 100644 --- a/src/core/varlink-unit.h +++ b/src/core/varlink-unit.h @@ -6,6 +6,9 @@ #define VARLINK_ERROR_UNIT_NO_SUCH_UNIT "io.systemd.Unit.NoSuchUnit" #define VARLINK_ERROR_UNIT_ONLY_BY_DEPENDENCY "io.systemd.Unit.OnlyByDependency" #define VARLINK_ERROR_UNIT_DBUS_SHUTTING_DOWN "io.systemd.Unit.DBusShuttingDown" +#define VARLINK_ERROR_UNIT_UNIT_EXISTS "io.systemd.Unit.UnitExists" +#define VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED "io.systemd.Unit.UnitTypeNotSupported" +#define VARLINK_ERROR_UNIT_BAD_SETTING "io.systemd.Unit.BadUnitSetting" int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); @@ -15,8 +18,15 @@ int varlink_unit_queue_job_one( JobMode mode, bool reload_if_possible, uint32_t *ret_job_id, + Job **ret_job, sd_bus_error *reterr_bus_error); int vl_method_set_unit_properties(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +void varlink_unit_send_change_signal(Unit *u); +void varlink_job_send_change_signal(Job *j); +void varlink_job_send_removed_signal(Job *j); + int varlink_error_no_such_unit(sd_varlink *v, const char *name); diff --git a/src/core/varlink.c b/src/core/varlink.c index 7bfc3789a3..09817b6dce 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -4,6 +4,7 @@ #include "constants.h" #include "errno-util.h" +#include "job.h" #include "json-util.h" #include "manager.h" #include "metrics.h" @@ -356,6 +357,24 @@ static void vl_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata if (link == m->managed_oom_varlink) m->managed_oom_varlink = sd_varlink_unref(link); + + /* Drop any job varlink references for the disconnecting client. + * A varlink link can stream at most one job, so stop after the first match. */ + Job *j; + HASHMAP_FOREACH(j, m->jobs) + if (j->varlink == link) { + j->varlink = sd_varlink_unref(j->varlink); + break; + } + + /* Also drop any unit-change varlink reference streaming to this link. + * A varlink link attaches to at most one unit, so stop after the first match. */ + Unit *u; + HASHMAP_FOREACH(u, m->units) + if (u->varlink_unit_change == link) { + u->varlink_unit_change = sd_varlink_unref(u->varlink_unit_change); + break; + } } int manager_setup_varlink_server(Manager *m) { @@ -398,6 +417,7 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.Manager.SoftReboot", vl_method_soft_reboot, "io.systemd.Unit.List", vl_method_list_units, "io.systemd.Unit.SetProperties", vl_method_set_unit_properties, + "io.systemd.Unit.StartTransient", vl_method_start_transient_unit, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) @@ -419,12 +439,12 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups); if (r < 0) return log_debug_errno(r, "Failed to register varlink methods: %m"); - - r = sd_varlink_server_bind_disconnect(s, vl_disconnect); - if (r < 0) - return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); } + r = sd_varlink_server_bind_disconnect(s, vl_disconnect); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); + r = sd_varlink_server_attach_event(s, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m"); diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c index af15f8f45b..8dfc403c7b 100644 --- a/src/shared/varlink-io.systemd.Job.c +++ b/src/shared/varlink-io.systemd.Job.c @@ -46,9 +46,9 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("The job type"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobType, JobType, 0), - SD_VARLINK_FIELD_COMMENT("Current job state, set in intermediate streaming notifications"), + SD_VARLINK_FIELD_COMMENT("Current job state. 'finished' indicates the job has completed; in that case Result is also set."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Final job result, set in the final streaming reply"), + SD_VARLINK_FIELD_COMMENT("Job result. Only set once the job has reached the 'finished' state."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE)); SD_VARLINK_DEFINE_INTERFACE( diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index 422aeb6952..2b1f0f2b10 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "varlink-idl-common.h" +#include "varlink-io.systemd.Job.h" #include "varlink-io.systemd.Unit.h" SD_VARLINK_DEFINE_ENUM_TYPE( @@ -972,6 +973,30 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Remount command"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecRemount, ExecCommand, SD_VARLINK_NULLABLE)); +/* Service-specific types */ + +/* Keep in sync with service_type_table[] in src/core/service.c */ +static SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceType, + SD_VARLINK_DEFINE_ENUM_VALUE(simple), + SD_VARLINK_DEFINE_ENUM_VALUE(exec), + SD_VARLINK_DEFINE_ENUM_VALUE(forking), + SD_VARLINK_DEFINE_ENUM_VALUE(oneshot), + SD_VARLINK_DEFINE_ENUM_VALUE(dbus), + SD_VARLINK_DEFINE_ENUM_VALUE(notify), + SD_VARLINK_FIELD_COMMENT("Like notify, but also implements a reload protocol via SIGHUP."), + SD_VARLINK_DEFINE_ENUM_VALUE(notify_reload), + SD_VARLINK_DEFINE_ENUM_VALUE(idle)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ServiceContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#Type="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, ServiceType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStart="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStart, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RemainAfterExit="), + SD_VARLINK_DEFINE_FIELD(RemainAfterExit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( Condition, @@ -984,10 +1009,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The parameter passed to the condition"), 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 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( UnitContext, SD_VARLINK_FIELD_COMMENT("The unit type"), - SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The unit ID"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The aliases of this unit"), @@ -1054,25 +1082,25 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#WantsMountsFor="), SD_VARLINK_DEFINE_FIELD(WantsMountsFor, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#OnSuccessJobMode="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnSuccessJobMode, JobMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnSuccessJobMode, JobMode, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#OnSuccessJobMode="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnFailureJobMode, JobMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnFailureJobMode, JobMode, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#IgnoreOnIsolate="), - SD_VARLINK_DEFINE_FIELD(IgnoreOnIsolate, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(IgnoreOnIsolate, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StopWhenUnneeded="), - SD_VARLINK_DEFINE_FIELD(StopWhenUnneeded, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(StopWhenUnneeded, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RefuseManualStart="), - SD_VARLINK_DEFINE_FIELD(RefuseManualStart, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RefuseManualStart, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RefuseManualStart="), - SD_VARLINK_DEFINE_FIELD(RefuseManualStop, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RefuseManualStop, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#AllowIsolate="), - SD_VARLINK_DEFINE_FIELD(AllowIsolate, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(AllowIsolate, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#DefaultDependencies="), - SD_VARLINK_DEFINE_FIELD(DefaultDependencies, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DefaultDependencies, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#SurviveFinalKillSignal="), - SD_VARLINK_DEFINE_FIELD(SurviveFinalKillSignal, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(SurviveFinalKillSignal, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#CollectMode="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(CollectMode, CollectMode, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CollectMode, CollectMode, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureAction="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(FailureAction, EmergencyAction, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#FailureAction="), @@ -1119,11 +1147,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The unit file preset for this unit"), SD_VARLINK_DEFINE_FIELD(UnitFilePreset, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether this unit is transient"), - SD_VARLINK_DEFINE_FIELD(Transient, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(Transient, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether this unit is perpetual"), - SD_VARLINK_DEFINE_FIELD(Perpetual, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(Perpetual, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("When true, logs about this unit will be at debug level regardless of other log level settings"), - SD_VARLINK_DEFINE_FIELD(DebugInvocation, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DebugInvocation, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), /* Other contexts */ SD_VARLINK_FIELD_COMMENT("The cgroup context of the unit"), @@ -1132,6 +1160,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD_BY_TYPE(Exec, ExecContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The kill context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Kill, KillContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The service context of the unit (only for .service units)"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Service, ServiceContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The automount context of the unit"), SD_VARLINK_DEFINE_FIELD_BY_TYPE(Automount, AutomountContext, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The mount context of the unit"), @@ -1339,6 +1369,28 @@ static SD_VARLINK_DEFINE_ERROR( PropertyNotSupported, SD_VARLINK_DEFINE_FIELD(property, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_ERROR(UnitExists); +static SD_VARLINK_DEFINE_ERROR(UnitTypeNotSupported); +static SD_VARLINK_DEFINE_ERROR(BadUnitSetting); + +static SD_VARLINK_DEFINE_METHOD_FULL( + StartTransient, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Unit context. Must include ID (the unit name). Only the subset of fields settable at creation time is accepted; supplying any other field returns PropertyNotSupported."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(context, UnitContext, 0), + SD_VARLINK_FIELD_COMMENT("Job mode. Defaults to replace."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(mode, JobMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true and 'more' is set, stream job state change notifications. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(notifyJobChanges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true and 'more' is set, stream unit runtime notifications on state changes. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(notifyUnitChanges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unit context. Set in the final reply."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, UnitContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unit runtime state. Set in the final reply and in intermediate streaming notifications when notifyUnitChanges is true."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, UnitRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The job that was enqueued. Always set in the final streaming reply; also included in intermediate streaming notifications when notifyJobChanges is true."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(job, Job, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_METHOD( SetProperties, SD_VARLINK_FIELD_COMMENT("The name of the unit to operate on."), @@ -1355,6 +1407,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_List, SD_VARLINK_SYMBOL_COMMENT("Set unit properties"), &vl_method_SetProperties, + SD_VARLINK_SYMBOL_COMMENT("Create a transient unit and start it"), + &vl_method_StartTransient, &vl_type_RateLimit, SD_VARLINK_SYMBOL_COMMENT("An object to represent a unit's conditions"), &vl_type_Condition, @@ -1451,7 +1505,22 @@ SD_VARLINK_DEFINE_INTERFACE( /* UnitContext enums */ &vl_type_CollectMode, &vl_type_EmergencyAction, + + /* Shared types (used by both StartTransient and Unit.List) */ + SD_VARLINK_SYMBOL_COMMENT("Service type"), + &vl_type_ServiceType, + SD_VARLINK_SYMBOL_COMMENT("Job mode"), &vl_type_JobMode, + SD_VARLINK_SYMBOL_COMMENT("Job type (defined in io.systemd.Job)"), + &vl_type_JobType, + SD_VARLINK_SYMBOL_COMMENT("Job state (defined in io.systemd.Job)"), + &vl_type_JobState, + SD_VARLINK_SYMBOL_COMMENT("Job result (defined in io.systemd.Job)"), + &vl_type_JobResult, + SD_VARLINK_SYMBOL_COMMENT("A job object (defined in io.systemd.Job)"), + &vl_type_Job, + SD_VARLINK_SYMBOL_COMMENT("Service-specific context"), + &vl_type_ServiceContext, /* Errors */ SD_VARLINK_SYMBOL_COMMENT("No matching unit found"), @@ -1460,9 +1529,15 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_error_UnitMasked, SD_VARLINK_SYMBOL_COMMENT("Unit is in a fatal error state"), &vl_error_UnitError, - SD_VARLINK_SYMBOL_COMMENT("Changing this property via SetProperties() is not supported"), + SD_VARLINK_SYMBOL_COMMENT("The named property cannot be set (via SetProperties() or at creation time via StartTransient())"), &vl_error_PropertyNotSupported, SD_VARLINK_SYMBOL_COMMENT("Job for the unit may only be enqueued by dependencies"), &vl_error_OnlyByDependency, SD_VARLINK_SYMBOL_COMMENT("A unit that requires D-Bus cannot be started as D-Bus is shutting down"), - &vl_error_DBusShuttingDown); + &vl_error_DBusShuttingDown, + SD_VARLINK_SYMBOL_COMMENT("A unit with this name already exists"), + &vl_error_UnitExists, + SD_VARLINK_SYMBOL_COMMENT("This unit type does not support transient units"), + &vl_error_UnitTypeNotSupported, + SD_VARLINK_SYMBOL_COMMENT("The unit file content contains invalid settings"), + &vl_error_BadUnitSetting); From 1cde1cc3bab595fe7b4e2befbfa08a01a172db0f Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 14 Apr 2026 17:04:31 +0200 Subject: [PATCH 4/5] test: add varlink io.systemd.Unit.StartTransient() test This commit adds a bunch of integration tests for the new varlink based io.systemd.Unit.StartTransient(). --- test/units/TEST-26-SYSTEMCTL.sh | 117 ++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/test/units/TEST-26-SYSTEMCTL.sh b/test/units/TEST-26-SYSTEMCTL.sh index 75feedd3b0..32842c1e90 100755 --- a/test/units/TEST-26-SYSTEMCTL.sh +++ b/test/units/TEST-26-SYSTEMCTL.sh @@ -519,6 +519,123 @@ systemctl show -P Markers "$UNIT_NAME" | grep needs-stop (! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload) varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}" +# Test io.systemd.Unit.StartTransient +MANAGER_SOCKET="/run/systemd/io.systemd.Manager" + +TRANSIENT_UNITS=() +defer_transient_cleanup() { + TRANSIENT_UNITS+=("$1") +} +transient_cleanup() { + for u in "${TRANSIENT_UNITS[@]}"; do + systemctl stop "$u" 2>/dev/null || true + systemctl reset-failed "$u" 2>/dev/null || true + done +} +trap transient_cleanup EXIT + +# Basic oneshot transient service +defer_transient_cleanup varlink-transient-test.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | grep '"context"' >/dev/null +echo "$result" | grep '"runtime"' >/dev/null + +# Wait for completion +timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done' +systemctl show -P Result varlink-transient-test.service | grep success >/dev/null + +# With explicit mode +defer_transient_cleanup varlink-transient-test2.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}') +echo "$result" | grep '"context"' >/dev/null + +# Streaming with notifyJobChanges: should get intermediate state updates and a final result +defer_transient_cleanup varlink-transient-test3.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.job.State == "waiting")' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null + +# Fire-and-forget: --more without notify flags should return immediately with context+runtime +defer_transient_cleanup varlink-transient-fireforget.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +printf '%s' "$result" | jq --seq -e 'select(.context)' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.runtime)' >/dev/null + +# Streaming with notifyUnitChanges: should get unit state change notifications +defer_transient_cleanup varlink-transient-unitnotify.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null + +# Streaming with both notifyJobChanges and notifyUnitChanges +defer_transient_cleanup varlink-transient-both.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.job.State)' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null +printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null + +# prepare for the error case below: create a long-running service, then try to create it again while it's active +defer_transient_cleanup varlink-transient-exists.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' +timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done' + +# Multiple ExecStart commands (oneshot allows multiple) +defer_transient_cleanup varlink-transient-multi.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null + +# Transient service with Description and RemainAfterExit +defer_transient_cleanup varlink-transient-desc.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Description == "Test description property"' +echo "$result" | jq -e '.context.Service.Type == "oneshot"' +echo "$result" | jq -e '.context.Service.RemainAfterExit == true' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"' +echo "$result" | jq -e '.runtime' + +# Transient service with explicit arguments +defer_transient_cleanup varlink-transient-args.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}') +echo "$result" | jq -e '.context' +echo "$result" | jq -e '.runtime' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"' +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done' + +# Verify that omitting arguments defaults argv[0] to the path +defer_transient_cleanup varlink-transient-noargs.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +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' + +# Error cases: verify specific varlink error types +set +o pipefail +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists" +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +defer_transient_cleanup varlink-transient-bad.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' |& grep "io.systemd.Unit.BadUnitSetting" +# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties() +defer_transient_cleanup varlink-transient-badpath.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' |& grep "io.systemd.Unit.BadUnitSetting" +set -o pipefail + +transient_cleanup +trap - EXIT + # --dry-run with destructive verbs # kexec is skipped intentionally, as it requires a bit more involved setup VERBS=( From 328fd964a91e120c4ea7edde6d1c5c9688123175 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 27 Apr 2026 13:12:30 +0200 Subject: [PATCH 5/5] core: transition job to JOB_FINISHED on uninstall When a job reaches the job_uninstall() stage we used to set the state to JOB_WAITING. However now that we have a JOB_FINISHED state [1] we should use that instead. This is more accurate so when varlink_job_send_removed_signal() is called the job is in the expected state and that is what the user will see. Note that this does not change the D-Bus API because there bus_job_send_removed_signal() doesn't send the state, it only sends the result. [1] https://github.com/systemd/systemd/pull/41811 --- src/core/job.c | 2 +- src/core/varlink-unit.c | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/core/job.c b/src/core/job.c index 44652d5f3e..2388d1de95 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -168,7 +168,7 @@ void job_uninstall(Job *j) { assert(j); assert(j->installed); - job_set_state(j, JOB_WAITING); + job_set_state(j, JOB_FINISHED); pj = j->type == JOB_NOP ? &j->unit->nop_job : &j->unit->job; assert(*pj == j); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 7111baf739..5b1dc77aae 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -640,18 +640,12 @@ void varlink_unit_send_change_signal(Unit *u) { static int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { Job *j = ASSERT_PTR(userdata); - /* We cannot just "blindly" use the j->state as it will get reset to WAITING in - * core/job.c:job_uninstall() before varlink_job_send_removed_signal(). This happens from - * unit.c:unit_free(). It is probably something that should be fixed but its a subtle part of - * systemd so for now we just deal with it here. */ - JobState state = j->result >= 0 ? JOB_FINISHED : j->state; - /* Note that "Result" is suppressed until the job reaches JOB_FINISHED. */ return sd_json_buildo( ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), - JSON_BUILD_PAIR_ENUM("State", job_state_to_string(state)), + JSON_BUILD_PAIR_ENUM("State", job_state_to_string(j->state)), JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("Result", job_result_to_string(j->result))); }