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);