From 2d4f2441f5c6331efbfa07d06467398f582d6616 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 03:16:00 -0700 Subject: [PATCH 1/7] json-util: add json_dispatch_job_id() dispatcher for job IDs Job IDs are uint32_t values that are always >= 1 (the manager's ID counter starts at 1 and wraps from UINT32_MAX back to 1, never assigning 0). Add a dedicated dispatch function that validates this constraint, rejecting 0 and treating null as "unset" (0). Co-developed-by: Claude Opus 4.6 --- src/libsystemd/sd-json/json-util.c | 21 ++++++++++ src/libsystemd/sd-json/json-util.h | 1 + src/libsystemd/sd-json/test-json.c | 63 ++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 40102a6998..c6727ac760 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -796,6 +796,27 @@ int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_jso return 0; } +int json_dispatch_job_id(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + uint32_t *id = ASSERT_PTR(userdata); + uint32_t k; + int r; + + if (sd_json_variant_is_null(variant)) { + *id = 0; + return 0; + } + + r = sd_json_dispatch_uint32(name, variant, flags, &k); + if (r < 0) + return r; + + if (k == 0) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid job ID.", strna(name)); + + *id = k; + return 0; +} + int json_variant_compare(sd_json_variant *a, sd_json_variant *b) { int r; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 0b5ea32f87..48a0afa289 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -132,6 +132,7 @@ int json_dispatch_ifindex(const char *name, sd_json_variant *variant, sd_json_di int json_dispatch_log_level(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_job_id(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); static inline int json_variant_unbase64_iovec(sd_json_variant *v, struct iovec *ret) { return sd_json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); diff --git a/src/libsystemd/sd-json/test-json.c b/src/libsystemd/sd-json/test-json.c index 3be4b09660..5d5765b354 100644 --- a/src/libsystemd/sd-json/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -1569,6 +1569,69 @@ TEST(access_mode) { &mm), ERANGE); } +TEST(job_id) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_parse("{\"a\": 1, \"b\": 4294967295, \"c\": null}", + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL)); + + struct { + uint32_t a, b, c; + } data = { 99, 99, 99 }; + + ASSERT_OK(sd_json_dispatch( + v, + (const sd_json_dispatch_field[]) { + { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, a), 0 }, + { "b", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, b), 0 }, + { "c", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, c), 0 }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.a, UINT32_C(1)); + ASSERT_EQ(data.b, UINT32_MAX); + ASSERT_EQ(data.c, UINT32_C(0)); + + /* Zero is not a valid job ID */ + sd_json_variant_unrefp(&v); + ASSERT_OK(sd_json_parse("{\"a\": 0}", + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL)); + + ASSERT_ERROR(sd_json_dispatch( + v, + (const sd_json_dispatch_field[]) { + { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, a), 0 }, + {}, + }, + /* flags= */ 0, + &data), EINVAL); + + /* Negative values are not valid */ + sd_json_variant_unrefp(&v); + ASSERT_OK(sd_json_parse("{\"a\": -1}", + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL)); + + ASSERT_ERROR(sd_json_dispatch( + v, + (const sd_json_dispatch_field[]) { + { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, a), 0 }, + {}, + }, + /* flags= */ 0, + &data), EINVAL); +} + static void test_json_variant_compare_one(const char *a, const char *b, int expected) { int r; From e4b5bdb9b36a260bd47fa854a6377dd6951a3829 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:13:35 -0700 Subject: [PATCH 2/7] json-util: add JSON_BUILD_PAIR_ENUM_NON_EMPTY macro Co-developed-by: Claude Opus 4.6 --- src/libsystemd/sd-json/json-util.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 48a0afa289..ecb4e2d633 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -268,6 +268,7 @@ enum { #define JSON_BUILD_PAIR_PIDREF(name, p) SD_JSON_BUILD_PAIR(name, JSON_BUILD_PIDREF(p)) #define JSON_BUILD_PAIR_DEVNUM(name, d) SD_JSON_BUILD_PAIR(name, JSON_BUILD_DEVNUM(d)) #define JSON_BUILD_PAIR_ENUM(name, s) SD_JSON_BUILD_PAIR(name, JSON_BUILD_STRING_UNDERSCORIFY(s)) +#define JSON_BUILD_PAIR_ENUM_NON_EMPTY(name, s) JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY(name, s) #define JSON_BUILD_PAIR_YES_NO(name, b) SD_JSON_BUILD_PAIR(name, SD_JSON_BUILD_STRING(yes_no(b))) #define JSON_BUILD_PAIR_CONDITION_UNSIGNED(condition, name, value) \ From 7416bdf8f1c71284805409a0b78a4d4abf1616df Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:00:07 -0700 Subject: [PATCH 3/7] shared: extend Job varlink type with Unit and ActivationDetails fields Co-developed-by: Claude Opus 4.6 --- src/core/meson.build | 1 + src/core/varlink-job.c | 49 +++++++++++++++++++++++++++++ src/core/varlink-job.h | 6 ++++ src/core/varlink-unit.c | 13 +------- src/shared/varlink-io.systemd.Job.c | 25 +++++++++------ 5 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 src/core/varlink-job.c create mode 100644 src/core/varlink-job.h diff --git a/src/core/meson.build b/src/core/meson.build index 98cf02aef8..a0ff0baf8d 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -70,6 +70,7 @@ libcore_sources = files( 'varlink-common.c', 'varlink-dynamic-user.c', 'varlink-execute.c', + 'varlink-job.c', 'varlink-kill.c', 'varlink-manager.c', 'varlink-metrics.c', diff --git a/src/core/varlink-job.c b/src/core/varlink-job.c new file mode 100644 index 0000000000..46b9a4cbd9 --- /dev/null +++ b/src/core/varlink-job.c @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "job.h" +#include "json-util.h" +#include "strv.h" +#include "unit.h" +#include "varlink-job.h" + +static int activation_details_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_strv_free_ char **pairs = NULL; + Job *j = ASSERT_PTR(userdata); + int r; + + assert(ret); + + r = activation_details_append_pair(j->activation_details, &pairs); + if (r < 0) + return log_debug_errno(r, "Failed to get activation details: %m"); + if (r == 0) { + *ret = NULL; + return 0; + } + + STRV_FOREACH_PAIR(key, value, pairs) { + r = sd_json_variant_set_field_string(&v, *key, *value); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Job *j = ASSERT_PTR(userdata); + + /* "Unit" is omitted in StartTransient streaming notifications where the caller already knows the unit. */ + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", j->unit ? j->unit->id : NULL), + 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_ENUM_NON_EMPTY("Result", job_result_to_string(j->result)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, j)); +} diff --git a/src/core/varlink-job.h b/src/core/varlink-job.h new file mode 100644 index 0000000000..cd661daca6 --- /dev/null +++ b/src/core/varlink-job.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int job_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-unit.c b/src/core/varlink-unit.c index 31f7898a8c..4bf8136fa9 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -27,6 +27,7 @@ #include "varlink-cgroup.h" #include "varlink-common.h" #include "varlink-execute.h" +#include "varlink-job.h" #include "varlink-kill.h" #include "varlink-mount.h" #include "varlink-path.h" @@ -617,18 +618,6 @@ void varlink_unit_send_change_signal(Unit *u) { 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); diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c index 8dfc403c7b..17b06a7de5 100644 --- a/src/shared/varlink-io.systemd.Job.c +++ b/src/shared/varlink-io.systemd.Job.c @@ -39,17 +39,24 @@ SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(frozen), SD_VARLINK_DEFINE_ENUM_VALUE(concurrency)); -/* Field names match the D-Bus Job properties (Id, JobType, State) */ +/* Field names match the D-Bus Job properties (Id, Unit, JobType, State) */ +#define VARLINK_DEFINE_JOB_FIELDS(direction) \ + SD_VARLINK_FIELD_COMMENT("The numeric job ID"), \ + SD_VARLINK_DEFINE_##direction(Id, SD_VARLINK_INT, 0), \ + SD_VARLINK_FIELD_COMMENT("The unit name this job operates on"), \ + SD_VARLINK_DEFINE_##direction(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("The job type"), \ + SD_VARLINK_DEFINE_##direction##_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_##direction##_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_##direction##_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Activation details describing what triggered this job, as key-value pairs"), \ + SD_VARLINK_DEFINE_##direction(ActivationDetails, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_MAP) + 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)); + VARLINK_DEFINE_JOB_FIELDS(FIELD)); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Job, From 3941dff8b4d342aff9ac7ed69a94c18e21657ca4 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:01:27 -0700 Subject: [PATCH 4/7] core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods Co-developed-by: Claude Opus 4.6 --- src/core/varlink-job.c | 203 ++++++++++++++++++++++++++++ src/core/varlink-job.h | 6 + src/core/varlink.c | 6 + src/shared/varlink-io.systemd.Job.c | 33 ++++- 4 files changed, 246 insertions(+), 2 deletions(-) diff --git a/src/core/varlink-job.c b/src/core/varlink-job.c index 46b9a4cbd9..a0b45fa24c 100644 --- a/src/core/varlink-job.c +++ b/src/core/varlink-job.c @@ -2,8 +2,12 @@ #include "sd-varlink.h" +#include "bus-polkit.h" #include "job.h" #include "json-util.h" +#include "locale-util.h" +#include "manager.h" +#include "selinux-access.h" #include "strv.h" #include "unit.h" #include "varlink-job.h" @@ -47,3 +51,202 @@ int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { JSON_BUILD_PAIR_ENUM_NON_EMPTY("Result", job_result_to_string(j->result)), JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, j)); } + +static int varlink_error_no_such_job(sd_varlink *link, const char *name) { + return sd_varlink_errorbo( + ASSERT_PTR(link), + VARLINK_ERROR_JOB_NO_SUCH_JOB, + JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name)); +} + +static int list_job_one(sd_varlink *link, Job *job) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(link); + assert(job); + + r = job_build_json(&v, /* name= */ NULL, job); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +static int list_job_one_with_selinux_access_check(sd_varlink *link, Job *job) { + int r; + + assert(link); + assert(job); + assert(job->unit); + + r = mac_selinux_unit_access_check_varlink(job->unit, link, "status"); + if (r < 0) + /* If mac_selinux_unit_access_check_varlink() returned an error, + * it means that SELinux enforce is on. It also does all the logging(). */ + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + return list_job_one(link, job); +} + +typedef struct JobLookupParameters { + uint32_t id; + const char *unit; +} JobLookupParameters; + +static int lookup_job_by_parameters( + sd_varlink *link, + Manager *manager, + JobLookupParameters *p, + Job **ret) { + + /* The function can return ret=NULL if no lookup parameters provided */ + Job *job = NULL; + + assert(link); + assert(manager); + assert(p); + assert(ret); + + if (p->id > 0) { + job = manager_get_job(manager, p->id); + if (!job) + return varlink_error_no_such_job(link, "id"); + } + + if (p->unit) { + Unit *u = manager_get_unit(manager, p->unit); + if (!u || !u->job) + return varlink_error_no_such_job(link, "unit"); + if (job && u->job != job) { + log_debug("Job lookup by parameters id=%u unit='%s' resulted in different jobs.", p->id, p->unit); + return varlink_error_no_such_job(link, /* name= */ NULL); + } + + job = u->job; + } + + *ret = job; + return !!job; +} + +int vl_method_list_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "id", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, offsetof(JobLookupParameters, id), 0 }, + { "unit", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(JobLookupParameters, unit), 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + JobLookupParameters p = {}; + Job *job; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = lookup_job_by_parameters(link, manager, &p, &job); + if (r < 0) + return r; + if (r > 0) + return list_job_one_with_selinux_access_check(link, job); + + /* List all jobs */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_JOB_NO_SUCH_JOB); + if (r < 0) + return r; + + HASHMAP_FOREACH(job, manager->jobs) { + r = mac_selinux_unit_access_check_varlink(job->unit, link, "status"); + if (r < 0) + continue; + + r = list_job_one(link, job); + if (r < 0) + return r; + } + + return 0; +} + +int vl_method_cancel_job(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "id", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, 0, SD_JSON_MANDATORY }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + uint32_t id = 0; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &id); + if (r != 0) + return r; + + Job *j = manager_get_job(manager, id); + if (!j) + return varlink_error_no_such_job(link, "id"); + + r = mac_selinux_unit_access_check_varlink(j->unit, link, "stop"); + if (r < 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "unit", j->unit ? j->unit->id : NULL, + "verb", "cancel", + "polkit.message", N_("Authentication is required to cancel job for unit '$(unit)'."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + job_finish_and_invalidate(j, JOB_CANCELED, /* recursive= */ true, /* already= */ false); + + return sd_varlink_reply(link, NULL); +} + +int vl_method_clear_all_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, "reload"); + if (r < 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "verb", "clear-jobs", + "polkit.message", N_("Authentication is required to clear all pending jobs."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + manager_clear_jobs(manager); + + return sd_varlink_reply(link, NULL); +} diff --git a/src/core/varlink-job.h b/src/core/varlink-job.h index cd661daca6..6393d4318a 100644 --- a/src/core/varlink-job.h +++ b/src/core/varlink-job.h @@ -3,4 +3,10 @@ #include "core-forward.h" +#define VARLINK_ERROR_JOB_NO_SUCH_JOB "io.systemd.Job.NoSuchJob" + int job_build_json(sd_json_variant **ret, const char *name, void *userdata); + +int vl_method_list_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_cancel_job(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_clear_all_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index 09817b6dce..c09c6ad486 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -14,11 +14,13 @@ #include "unit.h" #include "varlink.h" #include "varlink-dynamic-user.h" +#include "varlink-io.systemd.Job.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Manager.h" #include "varlink-io.systemd.Unit.h" #include "varlink-io.systemd.UserDatabase.h" #include "varlink-io.systemd.service.h" +#include "varlink-job.h" #include "varlink-manager.h" #include "varlink-metrics.h" #include "varlink-serialize.h" @@ -398,6 +400,7 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_add_interface_many( s, + &vl_interface_io_systemd_Job, &vl_interface_io_systemd_Manager, &vl_interface_io_systemd_Unit, &vl_interface_io_systemd_service); @@ -406,6 +409,9 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_bind_method_many( s, + "io.systemd.Job.List", vl_method_list_jobs, + "io.systemd.Job.Cancel", vl_method_cancel_job, + "io.systemd.Job.ClearAll", vl_method_clear_all_jobs, "io.systemd.Manager.Describe", vl_method_describe_manager, "io.systemd.Manager.Reexecute", vl_method_reexecute_manager, "io.systemd.Manager.Reload", vl_method_reload_manager, diff --git a/src/shared/varlink-io.systemd.Job.c b/src/shared/varlink-io.systemd.Job.c index 17b06a7de5..b31e476511 100644 --- a/src/shared/varlink-io.systemd.Job.c +++ b/src/shared/varlink-io.systemd.Job.c @@ -58,10 +58,37 @@ SD_VARLINK_DEFINE_STRUCT_TYPE( Job, VARLINK_DEFINE_JOB_FIELDS(FIELD)); +static SD_VARLINK_DEFINE_ERROR( + NoSuchJob, + SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + List, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, filter by job ID"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If non-null, filter by unit name"), + SD_VARLINK_DEFINE_INPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_JOB_FIELDS(OUTPUT)); + +static SD_VARLINK_DEFINE_METHOD( + Cancel, + SD_VARLINK_FIELD_COMMENT("The job ID to cancel"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_INT, 0)); + +static SD_VARLINK_DEFINE_METHOD( + ClearAll); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Job, "io.systemd.Job", - SD_VARLINK_INTERFACE_COMMENT("Job-related types for the systemd service manager."), + SD_VARLINK_INTERFACE_COMMENT("Job management interface for the systemd service manager."), + SD_VARLINK_SYMBOL_COMMENT("List queued jobs"), + &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("Cancel a specific job"), + &vl_method_Cancel, + SD_VARLINK_SYMBOL_COMMENT("Cancel all pending jobs"), + &vl_method_ClearAll, SD_VARLINK_SYMBOL_COMMENT("Job type"), &vl_type_JobType, SD_VARLINK_SYMBOL_COMMENT("Job state"), @@ -69,4 +96,6 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Job result"), &vl_type_JobResult, SD_VARLINK_SYMBOL_COMMENT("A job object"), - &vl_type_Job); + &vl_type_Job, + SD_VARLINK_SYMBOL_COMMENT("The specified job does not exist"), + &vl_error_NoSuchJob); From f287d85a0ac7827f4863e3b2221f1254e3f7a55a Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:01:43 -0700 Subject: [PATCH 5/7] test: split TEST-74-AUX-UTILS.varlinkctl.sh into per-interface subtests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the monolithic varlinkctl test script into separate files per varlink interface for better organization and easier maintenance: - varlinkctl.sh: core varlinkctl tool tests (CLI, transports, socket discovery, upgrade/serve) and io.systemd.Manager - varlinkctl-network.sh: io.systemd.Network - varlinkctl-unit.sh: io.systemd.Unit (system + user manager) - varlinkctl-metrics.sh: io.systemd.Metrics No functional changes — the test content is moved as-is. Co-developed-by: Claude Opus 4.6 --- .../TEST-74-AUX-UTILS.varlinkctl-metrics.sh | 16 ++++ .../TEST-74-AUX-UTILS.varlinkctl-network.sh | 9 ++ .../TEST-74-AUX-UTILS.varlinkctl-unit.sh | 83 ++++++++++++++++ test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 95 ------------------- 4 files changed, 108 insertions(+), 95 deletions(-) create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh new file mode 100755 index 0000000000..49d3373a7c --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Metrics +varlinkctl info /run/systemd/report/io.systemd.Manager + +varlinkctl list-methods /run/systemd/report/io.systemd.Manager +varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . + +varlinkctl introspect /run/systemd/report/io.systemd.Manager +varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . + +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List '{}' +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh new file mode 100755 index 0000000000..cae3d7a66e --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Network +varlinkctl info /run/systemd/netif/io.systemd.Network +varlinkctl introspect /run/systemd/netif/io.systemd.Network io.systemd.Network +varlinkctl call /run/systemd/netif/io.systemd.Network io.systemd.Network.Describe '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh new file mode 100755 index 0000000000..2311e14a14 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Unit +varlinkctl info /run/systemd/io.systemd.Manager +varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit +varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}' +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null) +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}' +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') +set +o pipefail +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported" +set -o pipefail + +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}' +invocation_id="$(systemctl show -P InvocationID systemd-journald.service)" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}" +# test for KillContext +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill' +# test for AutomountContext/Runtime +automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "automount" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$automount_id" +# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that +# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. +automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.runtime.Automount' +# test for MountContext/Runtime +mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$mount_id" +mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' +# test for PathContext/Runtime +path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$path_id" +path_params=$(jq -cn --arg name "$path_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' +# test for ServiceContext/Runtime +service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$service_id" +service_params=$(jq -cn --arg name "$service_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service' +# test for ScopeContext/Runtime +scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$scope_id" +scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' +# test for SocketContext/Runtime +socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$socket_id" +socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket' +# test for SwapContext/Runtime (swap units may not be present on all systems) +swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +if test -n "$swap_id"; then + swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}') + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' +fi +# test for TimerContext/Runtime +timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$timer_id" +timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer' + +# test io.systemd.Unit in user manager +testuser_uid=$(id -u testuser) +systemd-run --wait --pipe --user --machine testuser@ \ + varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index bbc219decb..0836883f24 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -205,97 +205,6 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}' # This will disconnect and fail, as the manager reexec and drops connections varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reexecute '{}' ||: -# test io.systemd.Network -varlinkctl info /run/systemd/netif/io.systemd.Network -varlinkctl introspect /run/systemd/netif/io.systemd.Network io.systemd.Network -varlinkctl call /run/systemd/netif/io.systemd.Network io.systemd.Network.Describe '{}' - -# test io.systemd.Unit -varlinkctl info /run/systemd/io.systemd.Manager -varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit -varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}' -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null) -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}' -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}') -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') -set +o pipefail -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit" -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported" -set -o pipefail - -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}' -invocation_id="$(systemctl show -P InvocationID systemd-journald.service)" -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}" -# test for KillContext -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill' -# test for AutomountContext/Runtime -automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "automount" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$automount_id" -# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that -# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. -automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.runtime.Automount' -# test for MountContext/Runtime -mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$mount_id" -mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' -# test for PathContext/Runtime -path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$path_id" -path_params=$(jq -cn --arg name "$path_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' -# test for ServiceContext/Runtime -service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$service_id" -service_params=$(jq -cn --arg name "$service_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service' -# test for ScopeContext/Runtime -scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$scope_id" -scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' -# test for SocketContext/Runtime -socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$socket_id" -socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket' -# test for SwapContext/Runtime (swap units may not be present on all systems) -swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -if test -n "$swap_id"; then - swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}') - varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' - varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' -fi -# test for TimerContext/Runtime -timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) -test -n "$timer_id" -timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer' - -# test io.systemd.Metrics -varlinkctl info /run/systemd/report/io.systemd.Manager - -varlinkctl list-methods /run/systemd/report/io.systemd.Manager -varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . - -varlinkctl introspect /run/systemd/report/io.systemd.Manager -varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . - -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {} -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe {} - # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) systemd-run --wait --pipe --user --machine testuser@ \ @@ -305,10 +214,6 @@ systemd-run --wait --pipe --user --machine testuser@ \ systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Manager.Describe '{}' -# test io.systemd.Unit in user manager -systemd-run --wait --pipe --user --machine testuser@ \ - varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' - # test --upgrade (protocol upgrade) # The basic --upgrade proxy test is covered by the "varlinkctl serve" tests below (which use # serve+rev/gunzip as the server). The tests here exercise features that need the Python From e86b49473ffe3eba66b02011e2fb825248784a41 Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Mon, 18 May 2026 00:45:23 -0700 Subject: [PATCH 6/7] test: wait systemd to finish reexec in TEST-74-AUX-UTILS.varlinkctl.sh --- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 0836883f24..ec71e2641e 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -204,6 +204,14 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Describe '{}' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}' # This will disconnect and fail, as the manager reexec and drops connections varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reexecute '{}' ||: +# Wait for the manager to finish re-exec before proceeding — the user manager +# tests below use systemd-run which requires a functional PID 1. +for _ in {1..10}; do + if systemctl is-system-running 2>/dev/null | grep -qE 'running|degraded'; then + break + fi + sleep 1 +done # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) From bf867054e96daf0f8bab035c6501b1cad06e05fd Mon Sep 17 00:00:00 2001 From: Ivan Kruglov Date: Fri, 15 May 2026 07:02:15 -0700 Subject: [PATCH 7/7] test: add integration tests for io.systemd.Job varlink methods Co-developed-by: Claude Opus 4.6 --- .../units/TEST-74-AUX-UTILS.varlinkctl-job.sh | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh new file mode 100755 index 0000000000..032904b4e5 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Job +varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Job + +# List with no jobs pending — should return empty with --more +varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Job.List '{}' --graceful=io.systemd.Job.NoSuchJob + +# Without --more and no filter, must fail (streaming required) +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{}') + +# Error cases: non-existent job ID, non-existent unit +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"id": 999999}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"unit": "non-existent.service"}') + +# Invalid inputs +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"id": 0}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"unit": ""}') + +at_exit() { + systemctl stop varlink-test-job.service 2>/dev/null || true + rm -f /run/systemd/system/varlink-test-job.service + systemctl daemon-reload +} +trap at_exit EXIT + +# Create a job by starting a slow service, then test List/Cancel +cat >/run/systemd/system/varlink-test-job.service <