#include "sd-bus.h"
#include "sd-messages.h"
+#include "sd-varlink.h"
#include "alloc-util.h"
#include "ansi-color.h"
#include "string-util.h"
#include "strv.h"
#include "unit.h"
+#include "varlink-unit.h"
#include "virt.h"
Job* job_new_raw(Unit *unit) {
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);
/* 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;
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;
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);
#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"
assert(u->in_dbus_queue);
bus_unit_send_change_signal(u);
+ varlink_unit_send_change_signal(u);
n++;
if (budget != UINT_MAX)
assert(j->in_dbus_queue);
bus_job_send_change_signal(j);
+ varlink_job_send_change_signal(j);
n++;
if (budget != UINT_MAX)
#include "sd-bus.h"
#include "sd-id128.h"
#include "sd-messages.h"
+#include "sd-varlink.h"
#include "all-units.h"
#include "alloc-util.h"
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);
/* 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 */
/* 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"
{ 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))
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 = {};
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);
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;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "sd-bus.h"
#include "sd-json.h"
#include "bitfield.h"
#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"
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);
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(
JobMode mode,
bool reload_if_possible,
uint32_t *ret_job_id,
+ Job **ret_job,
sd_bus_error *reterr_bus_error) {
int r;
if (ret_job_id)
*ret_job_id = j->id;
+ if (ret_job)
+ *ret_job = j;
return 0;
}
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;
#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);
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);
#include "constants.h"
#include "errno-util.h"
+#include "job.h"
#include "json-util.h"
#include "manager.h"
#include "metrics.h"
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) {
"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)
"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");
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(
/* 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(
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,
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"),
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="),
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"),
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"),
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."),
&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,
/* 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"),
&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);