From 1b9400f2eea3f6484a7b02255d9c74b4757b5392 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Sun, 18 May 2025 19:54:51 +0200 Subject: [PATCH] core/transaction: add job mode "lenient" as an even weaker version of _FAIL --- man/systemctl.xml | 11 +++++++---- src/basic/unit-def.c | 1 + src/basic/unit-def.h | 1 + src/core/transaction.c | 29 +++++++++++++++++++++-------- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/man/systemctl.xml b/man/systemctl.xml index 037410cd219..08cfb5421b8 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2197,6 +2197,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err When queuing a new job, this option controls how to deal with already queued jobs. It takes one of fail, + lenient, replace, replace-irreversibly, isolate, @@ -2209,10 +2210,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err isolate command is used which implies the isolate job mode. - If fail is specified and a requested - operation conflicts with a pending job (more specifically: - causes an already pending start job to be reversed into a stop - job or vice versa), cause the operation to fail. + If fail is specified and a requested operation on weak dependencies + conflicts with a pending job (more specifically: causes an already pending start job to be reversed + into a stop job or vice versa), cause the operation to fail. + + If lenient is specified and a requested operation conflicts with any + active/activating unit, cause the operation to fail. If replace (the default) is specified, any conflicting pending job will be replaced, as diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 89c7f937908..45911fb190c 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -341,6 +341,7 @@ DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); static const char* const job_mode_table[_JOB_MODE_MAX] = { [JOB_FAIL] = "fail", + [JOB_LENIENT] = "lenient", [JOB_REPLACE] = "replace", [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", [JOB_ISOLATE] = "isolate", diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index a8779769e31..f82d072ed50 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -282,6 +282,7 @@ typedef enum NotifyAccess { typedef enum JobMode { JOB_FAIL, /* Fail if a conflicting job is already queued */ + JOB_LENIENT, /* Fail if any conflicting unit is active (even weaker than JOB_FAIL) */ JOB_REPLACE, /* Replace an existing conflicting job */ JOB_REPLACE_IRREVERSIBLY, /* Like JOB_REPLACE + produce irreversible jobs */ JOB_ISOLATE, /* Start a unit, and stop all others */ diff --git a/src/core/transaction.c b/src/core/transaction.c index 1fe3c950e24..1e5b88af4d9 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -565,7 +565,7 @@ static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_erro assert(!j->transaction_prev); assert(!j->transaction_next); - if (j->unit->job && (mode == JOB_FAIL || j->unit->job->irreversible) && + if (j->unit->job && (IN_SET(mode, JOB_FAIL, JOB_LENIENT) || j->unit->job->irreversible) && job_type_is_conflicting(j->unit->job->type, j->type)) return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, "Transaction for %s/%s is destructive (%s has '%s' job queued, but '%s' is included in transaction).", @@ -576,7 +576,7 @@ static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_erro return 0; } -static void transaction_minimize_impact(Transaction *tr) { +static int transaction_minimize_impact(Transaction *tr, JobMode mode, sd_bus_error *e) { Job *head; assert(tr); @@ -584,13 +584,16 @@ static void transaction_minimize_impact(Transaction *tr) { /* Drops all unnecessary jobs that reverse already active jobs * or that stop a running service. */ + if (!IN_SET(mode, JOB_FAIL, JOB_LENIENT)) + return 0; + rescan: HASHMAP_FOREACH(head, tr->jobs) { LIST_FOREACH(transaction, j, head) { bool stops_running_service, changes_existing_job; /* If it matters, we shouldn't drop it */ - if (j->matters_to_anchor) + if (j->matters_to_anchor && mode != JOB_LENIENT) continue; /* Would this stop a running service? @@ -607,6 +610,13 @@ rescan: if (!stops_running_service && !changes_existing_job) continue; + if (j->matters_to_anchor) { + assert(mode == JOB_LENIENT); + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, + "%s/%s would stop a running unit or change existing job, bailing", + j->unit->id, job_type_to_string(j->type)); + } + if (stops_running_service) log_unit_debug(j->unit, "%s/%s would stop a running service.", @@ -626,6 +636,8 @@ rescan: goto rescan; } } + + return 0; } static int transaction_apply( @@ -734,11 +746,12 @@ int transaction_activate( /* First step: figure out which jobs matter */ transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++); - /* Second step: Try not to stop any running services if - * we don't have to. Don't try to reverse running - * jobs if we don't have to. */ - if (mode == JOB_FAIL) - transaction_minimize_impact(tr); + /* Second step: Try not to stop any running services if we don't have to. Don't try to reverse + * running jobs if we don't have to. */ + r = transaction_minimize_impact(tr, mode, e); + if (r < 0) + return r; /* Note that we don't log here, because for JOB_LENIENT conflicts are very much expected + and shouldn't appear to be fatal for the unit. Only inform the caller via bus error. */ /* Third step: Drop redundant jobs */ transaction_drop_redundant(tr); -- 2.47.3