out o unit_path,
out s job_type,
out a(uosos) affected_jobs);
+ EnqueueUnitJobMany(in as units,
+ in s job_type,
+ in s job_mode,
+ in t flags,
+ out a(uosos) jobs);
KillUnit(in s name,
in s whom,
in i signal);
<!--method GetUnitByControlGroup is not documented!-->
- <!--method EnqueueUnitJob is not documented!-->
-
<!--method FreezeUnit is not documented!-->
<!--method ThawUnit is not documented!-->
<variablelist class="dbus-method" generated="True" extra-ref="EnqueueUnitJob()"/>
+ <variablelist class="dbus-method" generated="True" extra-ref="EnqueueUnitJobMany()"/>
+
<variablelist class="dbus-method" generated="True" extra-ref="KillUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="KillUnitSubgroup()"/>
the "Try" flavor is used in which case a service that is not running is not affected by the restart. The
"ReloadOrRestart" flavors attempt a reload if the unit supports it and use a restart otherwise.</para>
+ <para><function>EnqueueUnitJob()</function> is similar to the various
+ <function>StartUnit()</function>/<function>StopUnit()</function>/<function>RestartUnit()</function>
+ methods above, but provides a unified interface that operates on a single unit. The
+ <varname>job_type</varname> argument selects the job type and is one of
+ <literal>start</literal>, <literal>stop</literal>, <literal>reload</literal>,
+ <literal>restart</literal>, <literal>try-restart</literal>,
+ <literal>reload-or-restart</literal> or <literal>reload-or-try-restart</literal>. The
+ <varname>job_mode</varname> argument matches the <varname>mode</varname> argument of
+ <function>StartUnit()</function>. On reply the method returns the enqueued anchor job's
+ numeric id and object path, the affected unit's id and object path, the job type
+ actually queued (after type collapsing, e.g. <literal>reload-or-restart</literal> turns
+ into <literal>restart</literal> or <literal>reload</literal>), and an array of
+ additional jobs that were enqueued as part of the same transaction in order to satisfy
+ dependencies.</para>
+
+ <para><function>EnqueueUnitJobMany()</function> is similar to
+ <function>EnqueueUnitJob()</function>, but operates on multiple units at once. All
+ requested units are enqueued in a single transaction, which is necessary in order to
+ properly honour <varname>After=</varname>/<varname>Before=</varname> ordering between
+ units passed in the same call regardless of their order in the array. The
+ <varname>job_type</varname> argument is applied to each of the listed units, including
+ the magic values <literal>reload-or-restart</literal> and
+ <literal>reload-or-try-restart</literal> which are resolved per unit (units that
+ support reloading get a reload job, others get a restart). The <varname>flags</varname>
+ argument does not support any flags for now and must be passed as <literal>0</literal>;
+ it is reserved for future extensions. On reply the method returns an array of tuples
+ describing the anchor job that was enqueued for each requested unit: the numeric job
+ id, the job object path, the unit id, the unit object path, and the job type that was
+ actually queued. Note that the order of tuples in the reply is unspecified and may not
+ correspond to the order of the input array.</para>
+
<para><function>EnqueueMarkedJobs()</function> creates reload/restart jobs for units which have been
appropriately marked, see <varname>Markers</varname> property above. This is equivalent to calling
<function>TryRestartUnit()</function> or <function>ReloadOrTryRestartUnit()</function> for the marked
<varname>ReloadCount</varname>,
<varname>EventLoopRateLimitIntervalUSec</varname>, and
<varname>EventLoopRateLimitBurst</varname> were added in version 261.</para>
- <para><varname>KExecsCount</varname> was added in version 262.</para>
+ <para><varname>KExecsCount</varname>, and
+ <function>EnqueueUnitJobMany()</function> were added in version 262.</para>
</refsect2>
<refsect2>
<title>Unit Objects</title>
return method_generic_unit_operation(message, userdata, reterr_error, _UNIT_TYPE_INVALID, bus_unit_method_enqueue_job, GENERIC_UNIT_LOAD);
}
+static int method_enqueue_unit_job_many(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) {
+ Manager *m = ASSERT_PTR(userdata);
+ _cleanup_strv_free_ char **names = NULL;
+ _cleanup_set_free_ Set *jobs = NULL;
+ const char *jtype, *smode;
+ JobType type;
+ JobMode mode;
+ uint64_t flags;
+ bool reload_if_possible;
+ Job *j;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read_strv(message, &names);
+ if (r < 0)
+ return r;
+
+ if (strv_isempty(names))
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "No units specified.");
+
+ if (strv_length(names) > (unsigned) MANAGER_MAX_NAMES / 2)
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many unit names requested.");
+
+ r = sd_bus_message_read(message, "sst", &jtype, &smode, &flags);
+ if (r < 0)
+ return r;
+
+ if (flags != 0)
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter, must be 0.");
+
+ r = bus_unit_parse_job_type(jtype, &type, &reload_if_possible, reterr_error);
+ if (r < 0)
+ return r;
+
+ mode = job_mode_from_string(smode);
+ if (mode < 0)
+ return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode);
+
+ r = mac_selinux_access_check(message, job_type_to_access_method(type), reterr_error);
+ if (r < 0)
+ return r;
+
+ r = bus_verify_manage_units_async(m, message, reterr_error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ jobs = set_new(NULL);
+ if (!jobs)
+ return -ENOMEM;
+
+ r = manager_add_jobs(m, type, names, reload_if_possible, mode,
+ /* extra_flags= */ 0, /* affected_jobs= */ NULL, reterr_error, jobs);
+ if (r < 0)
+ return r;
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(uosos)");
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(j, jobs) {
+ _cleanup_free_ char *job_path = NULL, *unit_path = NULL;
+
+ r = bus_job_track_sender(j, message);
+ if (r < 0)
+ return r;
+
+ bus_job_send_pending_change_signal(j, true);
+
+ job_path = job_dbus_path(j);
+ if (!job_path)
+ return -ENOMEM;
+
+ unit_path = unit_dbus_path(j->unit);
+ if (!unit_path)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(uosos)",
+ j->id, job_path,
+ j->unit->id, unit_path,
+ job_type_to_string(j->type));
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_send(reply);
+}
+
static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) {
Manager *m = ASSERT_PTR(userdata);
const char *old_name;
SD_BUS_RESULT("u", job_id, "o", job_path, "s", unit_id, "o", unit_path, "s", job_type, "a(uosos)", affected_jobs),
method_enqueue_unit_job,
SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD_WITH_ARGS("EnqueueUnitJobMany",
+ SD_BUS_ARGS("as", units, "s", job_type, "s", job_mode, "t", flags),
+ SD_BUS_RESULT("a(uosos)", jobs),
+ method_enqueue_unit_job_many,
+ SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("KillUnit",
SD_BUS_ARGS("s", name, "s", whom, "i", signal),
SD_BUS_NO_RESULT,
return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, reterr_error);
}
+int bus_unit_parse_job_type(
+ const char *jtype,
+ JobType *ret_type,
+ bool *ret_reload_if_possible,
+ sd_bus_error *reterr_error) {
+
+ JobType type;
+ bool reload_if_possible = false;
+
+ assert(jtype);
+ assert(ret_type);
+ assert(ret_reload_if_possible);
+
+ /* Parses the job type string as accepted by the EnqueueUnitJob()/EnqueueUnitJobMany() bus methods. The
+ * two magic "reload-or-…" types are handled manually, the rest generically. The actual
+ * reload-vs-restart choice is unit-specific and applied per-unit later. */
+ if (streq(jtype, "reload-or-restart")) {
+ type = JOB_RESTART;
+ reload_if_possible = true;
+ } else if (streq(jtype, "reload-or-try-restart")) {
+ type = JOB_TRY_RESTART;
+ reload_if_possible = true;
+ } else {
+ type = job_type_from_string(jtype);
+ if (type < 0)
+ return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Job type %s invalid", jtype);
+ }
+
+ *ret_type = type;
+ *ret_reload_if_possible = reload_if_possible;
+ return 0;
+}
+
int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) {
BusUnitQueueFlags flags = BUS_UNIT_QUEUE_VERBOSE_REPLY;
const char *jtype, *smode;
Unit *u = ASSERT_PTR(userdata);
+ bool reload_if_possible;
JobType type;
JobMode mode;
int r;
if (r < 0)
return r;
- /* Parse the two magic reload types "reload-or-…" manually */
- if (streq(jtype, "reload-or-restart")) {
- type = JOB_RESTART;
- flags |= BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE;
- } else if (streq(jtype, "reload-or-try-restart")) {
- type = JOB_TRY_RESTART;
+ r = bus_unit_parse_job_type(jtype, &type, &reload_if_possible, reterr_error);
+ if (r < 0)
+ return r;
+ if (reload_if_possible)
flags |= BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE;
- } else {
- /* And the rest generically */
- type = job_type_from_string(jtype);
- if (type < 0)
- return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Job type %s invalid", jtype);
- }
mode = job_mode_from_string(smode);
if (mode < 0)
int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *reterr_error);
int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error);
+int bus_unit_parse_job_type(const char *jtype, JobType *ret_type, bool *ret_reload_if_possible, sd_bus_error *reterr_error);
int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error);
int bus_unit_method_kill_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error);
int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error);
return 0;
}
+int manager_add_jobs(
+ Manager *m,
+ JobType type,
+ char * const *names,
+ bool reload_if_possible,
+ JobMode mode,
+ TransactionAddFlags extra_flags,
+ Set *affected_jobs,
+ sd_bus_error *reterr_error,
+ Set *ret_jobs) {
+
+ _cleanup_(transaction_abort_and_freep) Transaction *tr = NULL;
+ Job *j;
+ int r;
+
+ assert(m);
+ assert(type >= 0 && type < _JOB_TYPE_MAX);
+ assert(!strv_isempty(names));
+ assert(mode >= 0 && mode < _JOB_MODE_MAX);
+ assert((extra_flags & ~_TRANSACTION_FLAGS_MASK_PUBLIC) == 0);
+
+ if (mode == JOB_ISOLATE && type != JOB_START)
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start.");
+
+ if (mode == JOB_TRIGGERING && type != JOB_STOP)
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
+ "--job-mode=triggering is only valid for stop.");
+
+ if (mode == JOB_RESTART_DEPENDENCIES && type != JOB_START)
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
+ "--job-mode=restart-dependencies is only valid for start.");
+
+ if (mode == JOB_ISOLATE && strv_length(names) > 1)
+ return sd_bus_error_set(reterr_error, SD_BUS_ERROR_NOT_SUPPORTED,
+ "Isolating more than one unit is not supported.");
+
+ tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY, ++m->last_transaction_id);
+ if (!tr)
+ return -ENOMEM;
+
+ LOG_CONTEXT_PUSHF("TRANSACTION_ID=%" PRIu64, tr->id);
+
+ STRV_FOREACH(name, names) {
+ Unit *u;
+ JobType t = type;
+ JobType merged_type;
+
+ r = manager_load_unit(m, *name, NULL, reterr_error, &u);
+ if (r < 0)
+ return r;
+
+ if (mode == JOB_ISOLATE && !u->allow_isolate)
+ return sd_bus_error_setf(reterr_error, BUS_ERROR_NO_ISOLATION,
+ "Operation refused, unit %s may not be isolated.", u->id);
+
+ /* Per-unit validation and reload-if-possible mangling: units that can reload turn
+ * JOB_RESTART into JOB_RELOAD_OR_START and JOB_TRY_RESTART into JOB_TRY_RELOAD; others
+ * keep the original restart type. Also rejects manual start/stop on units that refuse
+ * it, etc. */
+ r = unit_queue_job_check_and_mangle_type(u, &t, reload_if_possible, reterr_error);
+ if (r < 0)
+ return r;
+
+ merged_type = job_type_collapse(t, u);
+
+ log_unit_debug(u, "Trying to enqueue job %s/%s/%s",
+ u->id, job_type_to_string(merged_type), job_mode_to_string(mode));
+
+ r = transaction_add_job_and_dependencies(
+ tr,
+ merged_type,
+ u,
+ /* by= */ NULL,
+ TRANSACTION_MATTERS |
+ (IN_SET(mode, JOB_IGNORE_DEPENDENCIES, JOB_IGNORE_REQUIREMENTS) ? TRANSACTION_IGNORE_REQUIREMENTS : 0) |
+ (mode == JOB_IGNORE_DEPENDENCIES ? TRANSACTION_IGNORE_ORDER : 0) |
+ (mode == JOB_RESTART_DEPENDENCIES ? TRANSACTION_PROPAGATE_START_AS_RESTART : 0) |
+ extra_flags,
+ reterr_error);
+ if (r < 0)
+ return r;
+
+ if (mode == JOB_TRIGGERING) {
+ r = transaction_add_triggering_jobs(tr, u);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (mode == JOB_ISOLATE) {
+ r = transaction_add_isolate_jobs(tr, m);
+ if (r < 0)
+ return r;
+ }
+
+ r = transaction_activate(tr, m, mode, affected_jobs, reterr_error);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(j, tr->anchor_jobs)
+ log_unit_debug(j->unit,
+ "Enqueued job %s/%s as %u",
+ j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
+
+ if (ret_jobs) {
+ /* The anchor_jobs set would be destroyed anyway, so steal the contents. */
+ r = set_move(ret_jobs, tr->anchor_jobs);
+ if (r < 0) {
+ /* On failure, still clear anchor_jobs so the cleanup handler doesn't trip the
+ * empty-set assertion in transaction_free(). */
+ set_clear(tr->anchor_jobs);
+ return r;
+ }
+ } else
+ /* tr->anchor_jobs tracks pointers to jobs that are now installed in the manager; clear
+ * it so transaction_free() doesn't trip its empty-set assertion. */
+ set_clear(tr->anchor_jobs);
+
+ tr = transaction_free(tr);
+ return 0;
+}
+
int manager_add_job_full(
Manager *m,
JobType type,
if (r < 0)
return r;
+ Job *anchor = ASSERT_PTR(set_first(tr->anchor_jobs));
+ assert(set_size(tr->anchor_jobs) == 1);
+
log_unit_debug(unit,
"Enqueued job %s/%s as %u", unit->id,
- job_type_to_string(type), (unsigned) tr->anchor_job->id);
+ job_type_to_string(type), (unsigned) anchor->id);
if (ret)
- *ret = tr->anchor_job;
+ *ret = anchor;
+
+ /* anchor_jobs tracks pointers to jobs that are now installed in the manager; clear it so
+ * transaction_free() doesn't trip its empty-set assertion. */
+ set_clear(tr->anchor_jobs);
tr = transaction_free(tr);
return 0;
transaction_add_propagate_reload_jobs(
tr,
unit,
- tr->anchor_job,
+ set_first(tr->anchor_jobs),
mode == JOB_IGNORE_DEPENDENCIES ? TRANSACTION_IGNORE_ORDER : 0);
/* Only activate the transaction if it contains jobs other than NOP anchor.
if (r < 0)
return r;
+ /* tr->anchor_jobs tracks pointers to jobs that are now installed in the manager, clear it */
+ set_clear(tr->anchor_jobs);
+
tr = transaction_free(tr);
return 0;
}
#include "log.h"
#include "path-lookup.h"
#include "show-status.h"
+#include "transaction.h"
#include "unit.h"
struct libmnt_monitor;
int manager_load_startable_unit_or_warn(Manager *m, const char *name, const char *path, Unit **ret);
int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u);
+int manager_add_jobs(
+ Manager *m,
+ JobType type,
+ char * const *names,
+ bool reload_if_possible,
+ JobMode mode,
+ TransactionAddFlags extra_flags,
+ Set *affected_jobs,
+ sd_bus_error *reterr_error,
+ Set *ret_jobs);
+
int manager_add_job_full(
Manager *m,
JobType type,
static bool job_matters_to_anchor(Job *job);
static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies);
+static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, unsigned generation);
static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) {
assert(tr);
/* Deletes one job from the transaction. */
transaction_unlink_job(tr, j, delete_dependencies);
+ (void) set_remove(tr->anchor_jobs, j);
job_free(j);
}
assert(hashmap_isempty(tr->jobs));
}
-static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) {
+static void transaction_find_jobs_that_matter_to_anchor_one(Job *j, unsigned generation) {
assert(j);
/* A recursive sweep through the graph that marks all units that matter to the anchor job, i.e. are
if (l->object->generation == generation)
continue;
- transaction_find_jobs_that_matter_to_anchor(l->object, generation);
+ transaction_find_jobs_that_matter_to_anchor_one(l->object, generation);
}
}
+static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, unsigned generation) {
+ Job *j;
+
+ assert(tr);
+
+ /* All anchor jobs (and reachable jobs) get marked in the same generation. The recursion uses the
+ * generation only to avoid re-traversing already-visited nodes, so reusing the generation across
+ * anchors is correct: nodes already marked by a previous anchor are skipped. */
+ SET_FOREACH(j, tr->anchor_jobs)
+ transaction_find_jobs_that_matter_to_anchor_one(j, generation);
+}
+
static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) {
JobDependency *last;
Job *k;
while ((k = j->transaction_next)) {
- if (tr->anchor_job == k) {
+ if (set_contains(tr->anchor_jobs, k)) {
transaction_merge_and_delete_job(tr, k, j, t);
j = k;
} else
bool keep = false;
LIST_FOREACH(transaction, k, j)
- if (tr->anchor_job == k ||
+ if (set_contains(tr->anchor_jobs, k) ||
!job_type_is_redundant(k->type, unit_active_state(k->unit)) ||
(k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) {
keep = true;
again = false;
HASHMAP_FOREACH(j, tr->jobs) {
- if (tr->anchor_job == j)
+ if (set_contains(tr->anchor_jobs, j))
continue;
if (!j->object_list) {
assert(!j->transaction_next);
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))
+ job_type_is_conflicting(j->unit->job->type, j->type)) {
+ _cleanup_free_ char *anchors = NULL;
+ Job *a;
+
+ SET_FOREACH(a, tr->anchor_jobs)
+ if (strextendf_with_separator(&anchors, ", ", "%s/%s",
+ a->unit->id, job_type_to_string(a->type)) < 0)
+ return -ENOMEM;
+
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).",
- tr->anchor_job->unit->id, job_type_to_string(tr->anchor_job->type),
+ "Transaction for %s is destructive (%s has '%s' job queued, but '%s' is included in transaction).",
+ strna(anchors),
j->unit->id, job_type_to_string(j->unit->job->type), job_type_to_string(j->type));
+ }
}
return 0;
installed_job = job_install(j);
if (installed_job != j) {
/* j has been merged into a previously installed job. */
- if (tr->anchor_job == j)
- tr->anchor_job = installed_job;
+ if (set_contains(tr->anchor_jobs, j)) {
+ r = set_remove_and_put(tr->anchor_jobs, j, installed_job);
+ if (r == -EEXIST)
+ /* installed_job is already an anchor; just drop the
+ * about-to-be-freed key so it doesn't linger. */
+ (void) set_remove(tr->anchor_jobs, j);
+ else
+ /* Old key was present and remove -> put cannot fail */
+ assert(r >= 0);
+ }
hashmap_remove_value(m->jobs, UINT32_TO_PTR(j->id), j);
free_and_replace_full(j, installed_job, job_free);
j->generation = 0;
/* First step: figure out which jobs matter. */
- transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++);
+ transaction_find_jobs_that_matter_to_anchor(tr, 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 (!job_dependency_new(by, job, FLAGS_SET(flags, TRANSACTION_MATTERS), FLAGS_SET(flags, TRANSACTION_CONFLICTS)))
return -ENOMEM;
} else {
- /* If the job has no parent job, it is the anchor job. */
- assert(!tr->anchor_job);
- tr->anchor_job = job;
+ /* If the job has no parent job, it is an anchor job. */
+ r = set_put(tr->anchor_jobs, job);
+ if (r < 0 && r != -EEXIST)
+ return r;
if (FLAGS_SET(flags, TRANSACTION_REENQUEUE_ANCHOR))
job->refuse_late_merge = true;
assert(tr);
assert(m);
+ assert(set_size(tr->anchor_jobs) == 1);
HASHMAP_FOREACH_KEY(u, k, m->units) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
if (!shall_stop_on_isolate(tr, u))
continue;
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, TRANSACTION_MATTERS, &e);
+ /* JOB_ISOLATE only ever has a single anchor (we reject multi-anchor isolate in the
+ * manager) so picking the first anchor is correct. */
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, set_first(tr->anchor_jobs), TRANSACTION_MATTERS, &e);
if (r < 0)
log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %s", bus_error_message(&e, r));
}
int transaction_add_triggering_jobs(Transaction *tr, Unit *u) {
Unit *trigger;
+ Job *by;
int r;
assert(tr);
assert(u);
+ /* The unit's own anchor job is the head of the list in tr->jobs. We pull in JOB_STOP for all
+ * triggers and link them to that anchor, so they are correctly grouped per anchor when there are
+ * multiple anchors in the transaction. */
+ by = ASSERT_PTR(hashmap_get(tr->jobs, u));
+
UNIT_FOREACH_DEPENDENCY_SAFE(trigger, u, UNIT_ATOM_TRIGGERED_BY) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
if (hashmap_contains(tr->jobs, trigger))
continue;
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, trigger, tr->anchor_job, TRANSACTION_MATTERS, &e);
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, trigger, by, TRANSACTION_MATTERS, &e);
if (r < 0)
log_unit_warning_errno(u, r, "Cannot add triggered by job, ignoring: %s", bus_error_message(&e, r));
}
*tr = (Transaction) {
.jobs = hashmap_new(NULL),
+ .anchor_jobs = set_new(NULL),
.irreversible = irreversible,
.id = id,
};
- if (!tr->jobs)
+ if (!tr->jobs || !tr->anchor_jobs) {
+ hashmap_free(tr->jobs);
+ set_free(tr->anchor_jobs);
return NULL;
+ }
return TAKE_PTR(tr);
}
return NULL;
assert(hashmap_isempty(tr->jobs));
+ assert(set_isempty(tr->anchor_jobs));
+
+ set_free(tr->anchor_jobs);
hashmap_free(tr->jobs);
return mfree(tr);
typedef struct Transaction {
/* Jobs to be added */
Hashmap *jobs; /* Unit object => Job object list 1:1 */
- Job *anchor_job; /* The job the user asked for */
+ Set *anchor_jobs; /* the jobs the user asked for */
bool irreversible;
uint64_t id;