core: introduce io.systemd.Job interface with List, Cancel, and ClearAll methods

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ivan Kruglov
2026-05-19 11:05:55 +02:00
parent 7416bdf8f1
commit 3941dff8b4
4 changed files with 246 additions and 2 deletions
+203
View 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);
}
+6
View 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);
+6
View File
@@ -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,
+31 -2
View 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);