From: Ivan Kruglov Date: Fri, 15 May 2026 14:01:27 +0000 (-0700) Subject: core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods X-Git-Tag: v261-rc1~80^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3941dff8b4d342aff9ac7ed69a94c18e21657ca4;p=thirdparty%2Fsystemd.git core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods Co-developed-by: Claude Opus 4.6 --- diff --git a/src/core/varlink-job.c b/src/core/varlink-job.c index 46b9a4cbd92..a0b45fa24ce 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 cd661daca62..6393d4318af 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 09817b6dce2..c09c6ad486f 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 17b06a7de58..b31e476511b 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);