]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods
authorIvan Kruglov <mail@ikruglov.com>
Fri, 15 May 2026 14:01:27 +0000 (07:01 -0700)
committerIvan Kruglov <mail@ikruglov.com>
Tue, 19 May 2026 09:05:55 +0000 (11:05 +0200)
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
src/core/varlink-job.c
src/core/varlink-job.h
src/core/varlink.c
src/shared/varlink-io.systemd.Job.c

index 46b9a4cbd928f9496d8d11e11d675016eead16ca..a0b45fa24ceb125018406c094cdf9a82814aba6d 100644 (file)
@@ -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);
+}
index cd661daca62ac08c51090fe02c6e9b874cd33df3..6393d4318af7b52acc6026a2fb16795780e72e99 100644 (file)
@@ -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);
index 09817b6dce2c46861a87c47d386636bafeba7842..c09c6ad486f2538d6fabbe64f9107de65a65e42c 100644 (file)
 #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,
index 17b06a7de5865281e118d2a823fcf690a5612f65..b31e476511bb05e8f05bbd9e95fe51ba414935c6 100644 (file)
@@ -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);