]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/transaction: add job mode "lenient" as an even weaker version of _FAIL
authorMike Yuan <me@yhndnzj.com>
Sun, 18 May 2025 17:54:51 +0000 (19:54 +0200)
committerMike Yuan <me@yhndnzj.com>
Mon, 30 Jun 2025 11:10:42 +0000 (13:10 +0200)
man/systemctl.xml
src/basic/unit-def.c
src/basic/unit-def.h
src/core/transaction.c

index 037410cd219f7aceed038687ef69422160d2023a..08cfb5421b8d0de1644cf1c97d0ccb05687a52a9 100644 (file)
@@ -2197,6 +2197,7 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
         <listitem>
           <para>When queuing a new job, this option controls how to deal with
           already queued jobs. It takes one of <literal>fail</literal>,
+          <literal>lenient</literal>,
           <literal>replace</literal>,
           <literal>replace-irreversibly</literal>,
           <literal>isolate</literal>,
@@ -2209,10 +2210,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
           <command>isolate</command> command is used which implies the
           <literal>isolate</literal> job mode.</para>
 
-          <para>If <literal>fail</literal> 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.</para>
+          <para>If <literal>fail</literal> 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.</para>
+
+          <para>If <literal>lenient</literal> is specified and a requested operation conflicts with any
+          active/activating unit, cause the operation to fail.</para>
 
           <para>If <literal>replace</literal> (the default) is
           specified, any conflicting pending job will be replaced, as
index 89c7f937908f76011364ff5de8047bcfea3ff569..45911fb190c0ded252f8ca22d2aab41be854bc0e 100644 (file)
@@ -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",
index a8779769e31df730a3321c3601f1cbb6d9b949ca..f82d072ed50ddf6c361a8121389cbf9b6c674187 100644 (file)
@@ -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 */
index 1fe3c950e249251e4520962b287c8f9bc356ba66..1e5b88af4d9c4b72ad80803f5603f4368395d68b 100644 (file)
@@ -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);