core: add io.systemd.Unit.StartTransient() to the varlink API (#41583)

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 structured json object "UnitContext" with a "Service" field
inside.
   It is also only implementing a minimal set of what can be done with a
   service.
2. No aux units (for now)
3. When called with --more the varlink socket can notify about
   state changes depending on the notify{Job,Unit}Changes parameter

This aligns to the json objects/format from
https://github.com/systemd/systemd/pull/39391
and to show how the format can be shared it adds a new
(minimal) `ServiceContext` that is now part of
`io.systemd.Unit.List()`.
This commit is contained in:
Michael Vogt
2026-04-29 08:20:56 +02:00
committed by GitHub
18 changed files with 827 additions and 44 deletions
+1 -22
View File
@@ -1028,7 +1028,6 @@ static int transient_unit_from_message(
Unit **ret_unit,
sd_bus_error *reterr_error) {
UnitType t;
Unit *u;
int r;
@@ -1036,27 +1035,7 @@ static int transient_unit_from_message(
assert(message);
assert(name);
t = unit_name_to_type(name);
if (t < 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid unit name or type: %s", name);
if (!unit_vtable[t]->can_transient)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Unit type %s does not support transient units.",
unit_type_to_string(t));
r = manager_load_unit(m, name, NULL, reterr_error, &u);
if (r < 0)
return r;
if (!unit_is_pristine(u))
return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS,
"Unit %s was already loaded or has a fragment file.", name);
/* OK, the unit failed to load and is unreferenced, now let's
* fill in the transient data instead */
r = unit_make_transient(u);
r = manager_setup_transient_unit(m, name, &u, reterr_error);
if (r < 0)
return r;
+7 -2
View File
@@ -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);
@@ -165,7 +168,7 @@ void job_uninstall(Job *j) {
assert(j);
assert(j->installed);
job_set_state(j, JOB_WAITING);
job_set_state(j, JOB_FINISHED);
pj = j->type == JOB_NOP ? &j->unit->nop_job : &j->unit->job;
assert(*pj == j);
@@ -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;
+5
View File
@@ -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);
+3
View File
@@ -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)
+38
View File
@@ -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);
@@ -4893,6 +4895,42 @@ int unit_make_transient(Unit *u) {
return 0;
}
int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error) {
Unit *u;
int r;
assert(m);
assert(name);
assert(ret);
UnitType t = unit_name_to_type(name);
if (t < 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid unit name or type: %s", name);
if (!unit_vtable[t]->can_transient)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Unit type %s does not support transient units.",
unit_type_to_string(t));
r = manager_load_unit(m, name, /* path= */ NULL, reterr_error, &u);
if (r < 0)
return r;
if (!unit_is_pristine(u))
return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS,
"Unit %s was already loaded or has a fragment file.", name);
/* OK, the unit failed to load and is unreferenced, now let's
* fill in the transient data instead */
r = unit_make_transient(u);
if (r < 0)
return r;
*ret = u;
return 0;
}
static bool ignore_leftover_process(const char *comm) {
return comm && comm[0] == '('; /* Most likely our own helper process (PAM?), ignore */
}
+5
View File
@@ -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 */
@@ -973,6 +977,7 @@ int unit_write_settingf(Unit *u, UnitWriteFlags flags, const char *name, const c
int unit_kill_context(Unit *u, KillOperation k);
int unit_make_transient(Unit *u);
int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error);
int unit_add_mounts_for(Unit *u, const char *path, UnitDependencyMask mask, UnitMountDependencyType type);
+10
View File
@@ -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 = {};
+1
View File
@@ -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);
+1
View File
@@ -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;
+436
View File
@@ -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,395 @@ 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);
/* 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(j->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;
+10
View File
@@ -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);
+24 -4
View File
@@ -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");
+1
View File
@@ -223,6 +223,7 @@ shared_sources = files(
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Import.c',
'varlink-io.systemd.InstanceMetadata.c',
'varlink-io.systemd.Job.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.JournalAccess.c',
'varlink-io.systemd.Login.c',
+65
View File
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.Job.h"
/* Keep in sync with job_type_table[] in src/core/job.c */
SD_VARLINK_DEFINE_ENUM_TYPE(
JobType,
SD_VARLINK_DEFINE_ENUM_VALUE(start),
SD_VARLINK_DEFINE_ENUM_VALUE(verify_active),
SD_VARLINK_DEFINE_ENUM_VALUE(stop),
SD_VARLINK_DEFINE_ENUM_VALUE(reload),
SD_VARLINK_DEFINE_ENUM_VALUE(reload_or_start),
SD_VARLINK_DEFINE_ENUM_VALUE(restart),
SD_VARLINK_DEFINE_ENUM_VALUE(try_restart),
SD_VARLINK_DEFINE_ENUM_VALUE(try_reload),
SD_VARLINK_DEFINE_ENUM_VALUE(nop));
/* Keep in sync with job_state_table[] in src/core/job.c */
SD_VARLINK_DEFINE_ENUM_TYPE(
JobState,
SD_VARLINK_DEFINE_ENUM_VALUE(waiting),
SD_VARLINK_DEFINE_ENUM_VALUE(running),
SD_VARLINK_DEFINE_ENUM_VALUE(finished));
/* Keep in sync with job_result_table[] in src/core/job.c */
SD_VARLINK_DEFINE_ENUM_TYPE(
JobResult,
SD_VARLINK_DEFINE_ENUM_VALUE(done),
SD_VARLINK_DEFINE_ENUM_VALUE(canceled),
SD_VARLINK_DEFINE_ENUM_VALUE(timeout),
SD_VARLINK_DEFINE_ENUM_VALUE(failed),
SD_VARLINK_DEFINE_ENUM_VALUE(dependency),
SD_VARLINK_DEFINE_ENUM_VALUE(skipped),
SD_VARLINK_DEFINE_ENUM_VALUE(invalid),
SD_VARLINK_DEFINE_ENUM_VALUE(assert),
SD_VARLINK_DEFINE_ENUM_VALUE(unsupported),
SD_VARLINK_DEFINE_ENUM_VALUE(collected),
SD_VARLINK_DEFINE_ENUM_VALUE(once),
SD_VARLINK_DEFINE_ENUM_VALUE(frozen),
SD_VARLINK_DEFINE_ENUM_VALUE(concurrency));
/* Field names match the D-Bus Job properties (Id, JobType, State) */
SD_VARLINK_DEFINE_STRUCT_TYPE(
Job,
SD_VARLINK_FIELD_COMMENT("The numeric job ID"),
SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_INT, 0),
SD_VARLINK_FIELD_COMMENT("The job type"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(JobType, JobType, 0),
SD_VARLINK_FIELD_COMMENT("Current job state. '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("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(
io_systemd_Job,
"io.systemd.Job",
SD_VARLINK_INTERFACE_COMMENT("Job-related types for the systemd service manager."),
SD_VARLINK_SYMBOL_COMMENT("Job type"),
&vl_type_JobType,
SD_VARLINK_SYMBOL_COMMENT("Job state"),
&vl_type_JobState,
SD_VARLINK_SYMBOL_COMMENT("Job result"),
&vl_type_JobResult,
SD_VARLINK_SYMBOL_COMMENT("A job object"),
&vl_type_Job);
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-varlink-idl.h"
extern const sd_varlink_symbol vl_type_JobType;
extern const sd_varlink_symbol vl_type_JobState;
extern const sd_varlink_symbol vl_type_JobResult;
extern const sd_varlink_symbol vl_type_Job;
extern const sd_varlink_interface vl_interface_io_systemd_Job;
+91 -16
View File
@@ -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);
+2
View File
@@ -25,6 +25,7 @@
#include "varlink-io.systemd.Hostname.h"
#include "varlink-io.systemd.Import.h"
#include "varlink-io.systemd.InstanceMetadata.h"
#include "varlink-io.systemd.Job.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.JournalAccess.h"
#include "varlink-io.systemd.Login.h"
@@ -192,6 +193,7 @@ TEST(parse_format) {
&vl_interface_io_systemd_Hostname,
&vl_interface_io_systemd_Import,
&vl_interface_io_systemd_InstanceMetadata,
&vl_interface_io_systemd_Job,
&vl_interface_io_systemd_Journal,
&vl_interface_io_systemd_JournalAccess,
&vl_interface_io_systemd_Login,
+117
View File
@@ -519,6 +519,123 @@ systemctl show -P Markers "$UNIT_NAME" | grep needs-stop
(! systemctl show -P Markers "$UNIT_NAME" | grep needs-reload)
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties "{\"runtime\": true, \"name\": \"$UNIT_NAME\", \"properties\": {\"Markers\": []}}"
# Test io.systemd.Unit.StartTransient
MANAGER_SOCKET="/run/systemd/io.systemd.Manager"
TRANSIENT_UNITS=()
defer_transient_cleanup() {
TRANSIENT_UNITS+=("$1")
}
transient_cleanup() {
for u in "${TRANSIENT_UNITS[@]}"; do
systemctl stop "$u" 2>/dev/null || true
systemctl reset-failed "$u" 2>/dev/null || true
done
}
trap transient_cleanup EXIT
# Basic oneshot transient service
defer_transient_cleanup varlink-transient-test.service
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}')
echo "$result" | grep '"context"' >/dev/null
echo "$result" | grep '"runtime"' >/dev/null
# Wait for completion
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done'
systemctl show -P Result varlink-transient-test.service | grep success >/dev/null
# With explicit mode
defer_transient_cleanup varlink-transient-test2.service
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}')
echo "$result" | grep '"context"' >/dev/null
# Streaming with notifyJobChanges: should get intermediate state updates and a final result
defer_transient_cleanup varlink-transient-test3.service
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}')
printf '%s' "$result" | jq --seq -e 'select(.job.State == "waiting")' >/dev/null
printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null
# Fire-and-forget: --more without notify flags should return immediately with context+runtime
defer_transient_cleanup varlink-transient-fireforget.service
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}')
printf '%s' "$result" | jq --seq -e 'select(.context)' >/dev/null
printf '%s' "$result" | jq --seq -e 'select(.runtime)' >/dev/null
# Streaming with notifyUnitChanges: should get unit state change notifications
defer_transient_cleanup varlink-transient-unitnotify.service
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}')
printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null
# Streaming with both notifyJobChanges and notifyUnitChanges
defer_transient_cleanup varlink-transient-both.service
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}')
printf '%s' "$result" | jq --seq -e 'select(.job.State)' >/dev/null
printf '%s' "$result" | jq --seq -e 'select(.runtime.ActiveState)' >/dev/null
printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null
# prepare for the error case below: create a long-running service, then try to create it again while it's active
defer_transient_cleanup varlink-transient-exists.service
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}'
timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done'
# Multiple ExecStart commands (oneshot allows multiple)
defer_transient_cleanup varlink-transient-multi.service
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}')
printf '%s' "$result" | jq --seq -e 'select(.job.Result == "done")' >/dev/null
# Transient service with Description and RemainAfterExit
defer_transient_cleanup varlink-transient-desc.service
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
echo "$result" | jq -e '.context.Description == "Test description property"'
echo "$result" | jq -e '.context.Service.Type == "oneshot"'
echo "$result" | jq -e '.context.Service.RemainAfterExit == true'
echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"'
echo "$result" | jq -e '.runtime'
# Transient service with explicit arguments
defer_transient_cleanup varlink-transient-args.service
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}')
echo "$result" | jq -e '.context'
echo "$result" | jq -e '.runtime'
echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"'
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]'
timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done'
# Verify that omitting arguments defaults argv[0] to the path
defer_transient_cleanup varlink-transient-noargs.service
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]'
timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done'
# Error cases: verify specific varlink error types
set +o pipefail
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists"
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
defer_transient_cleanup varlink-transient-bad.service
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' |& grep "io.systemd.Unit.BadUnitSetting"
# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties()
defer_transient_cleanup varlink-transient-badpath.service
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' |& grep "io.systemd.Unit.BadUnitSetting"
set -o pipefail
transient_cleanup
trap - EXIT
# --dry-run with destructive verbs
# kexec is skipped intentionally, as it requires a bit more involved setup
VERBS=(