From: Michael Vogt Date: Tue, 14 Apr 2026 15:04:15 +0000 (+0200) Subject: core: add io.systemd.Unit.StartTransient() to the varlink API X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=913b81735e740c9d700647754c466bc8b75943d7;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/core/job.c b/src/core/job.c index b4578a946c6..44652d5f3e2 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 a8eca99505d..45dccdaaaec 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 56ee66e5bd2..17908d4db86 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 de0276a813a..0edb7e25aaa 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 54794e8be7a..d79bb7a98a5 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 bdef4d5a947..6f876db85b0 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 fc919dcf36c..aa9f26145c1 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 96287dc32f0..0bef5cbe984 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 789b21947c8..7111baf7391 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 8f58d7ce103..9460245ec0a 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 7bfc3789a39..09817b6dce2 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 af15f8f45bb..8dfc403c7be 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 422aeb6952b..2b1f0f2b105 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);