|
|
7e7c9f |
From ba36c6a8c8d134aa7196750af4a6651d5810f6d3 Mon Sep 17 00:00:00 2001
|
|
|
2f7d91 |
From: Lennart Poettering <lennart@poettering.net>
|
|
|
2f7d91 |
Date: Thu, 9 Aug 2018 16:26:27 +0200
|
|
|
2f7d91 |
Subject: [PATCH] core: rework StopWhenUnneeded= logic
|
|
|
2f7d91 |
|
|
|
2f7d91 |
Previously, we'd act immediately on StopWhenUnneeded= when a unit state
|
|
|
2f7d91 |
changes. With this rework we'll maintain a queue instead: whenever
|
|
|
2f7d91 |
there's the chance that StopWhenUneeded= might have an effect we enqueue
|
|
|
2f7d91 |
the unit, and process it later when we have nothing better to do.
|
|
|
2f7d91 |
|
|
|
2f7d91 |
This should make the implementation a bit more reliable, as the unit notify event
|
|
|
2f7d91 |
cannot immediately enqueue tons of side-effect jobs that might
|
|
|
2f7d91 |
contradict each other, but we do so only in a strictly ordered fashion,
|
|
|
2f7d91 |
from the main event loop.
|
|
|
2f7d91 |
|
|
|
2f7d91 |
This slightly changes the check when to consider a unit "unneeded".
|
|
|
2f7d91 |
Previously, we'd assume that a unit in "deactivating" state could also
|
|
|
2f7d91 |
be cleaned up. With this new logic we'll only consider units unneeded
|
|
|
2f7d91 |
that are fully up and have no job queued. This means that whenever
|
|
|
2f7d91 |
there's something pending for a unit we won't clean it up.
|
|
|
2f7d91 |
|
|
|
2f7d91 |
(cherry picked from commit a3c1168ac293f16d9343d248795bb4c246aaff4a)
|
|
|
2f7d91 |
|
|
|
7e7c9f |
Resolves: #1775291
|
|
|
2f7d91 |
---
|
|
|
2f7d91 |
src/core/manager.c | 43 +++++++++++++++
|
|
|
2f7d91 |
src/core/manager.h | 3 ++
|
|
|
2f7d91 |
src/core/unit.c | 132 ++++++++++++++++++++++++---------------------
|
|
|
2f7d91 |
src/core/unit.h | 7 +++
|
|
|
2f7d91 |
4 files changed, 124 insertions(+), 61 deletions(-)
|
|
|
2f7d91 |
|
|
|
2f7d91 |
diff --git a/src/core/manager.c b/src/core/manager.c
|
|
|
7e7c9f |
index 7bbe912dc9..db562b18a5 100644
|
|
|
2f7d91 |
--- a/src/core/manager.c
|
|
|
2f7d91 |
+++ b/src/core/manager.c
|
|
|
2f7d91 |
@@ -961,6 +961,45 @@ static unsigned manager_dispatch_gc_queue(Manager *m) {
|
|
|
2f7d91 |
return n;
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
|
|
|
2f7d91 |
+ unsigned n = 0;
|
|
|
2f7d91 |
+ Unit *u;
|
|
|
2f7d91 |
+ int r;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ assert(m);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ while ((u = m->stop_when_unneeded_queue)) {
|
|
|
2f7d91 |
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
|
2f7d91 |
+ assert(m->stop_when_unneeded_queue);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ assert(u->in_stop_when_unneeded_queue);
|
|
|
2f7d91 |
+ LIST_REMOVE(stop_when_unneeded_queue, m->stop_when_unneeded_queue, u);
|
|
|
2f7d91 |
+ u->in_stop_when_unneeded_queue = false;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ n++;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ if (!unit_is_unneeded(u))
|
|
|
2f7d91 |
+ continue;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ log_unit_debug(u->id, "Unit is not needed anymore.");
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ /* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
|
|
|
2f7d91 |
+ * service being unnecessary after a while. */
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ if (!ratelimit_test(&u->check_unneeded_ratelimit)) {
|
|
|
2f7d91 |
+ log_unit_warning(u->id, "Unit not needed anymore, but not stopping since we tried this too often recently.");
|
|
|
2f7d91 |
+ continue;
|
|
|
2f7d91 |
+ }
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
|
|
|
2f7d91 |
+ r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, &error, NULL);
|
|
|
2f7d91 |
+ if (r < 0)
|
|
|
2f7d91 |
+ log_unit_warning_errno(u->id, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
|
|
|
2f7d91 |
+ }
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ return n;
|
|
|
2f7d91 |
+}
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
static void manager_clear_jobs_and_units(Manager *m) {
|
|
|
2f7d91 |
Unit *u;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
@@ -977,6 +1016,7 @@ static void manager_clear_jobs_and_units(Manager *m) {
|
|
|
2f7d91 |
assert(!m->dbus_job_queue);
|
|
|
2f7d91 |
assert(!m->cleanup_queue);
|
|
|
2f7d91 |
assert(!m->gc_queue);
|
|
|
2f7d91 |
+ assert(!m->stop_when_unneeded_queue);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
assert(hashmap_isempty(m->jobs));
|
|
|
2f7d91 |
assert(hashmap_isempty(m->units));
|
|
|
2f7d91 |
@@ -2259,6 +2299,9 @@ int manager_loop(Manager *m) {
|
|
|
2f7d91 |
if (manager_dispatch_cgroup_queue(m) > 0)
|
|
|
2f7d91 |
continue;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+ if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
|
|
|
2f7d91 |
+ continue;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
if (manager_dispatch_dbus_queue(m) > 0)
|
|
|
2f7d91 |
continue;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
diff --git a/src/core/manager.h b/src/core/manager.h
|
|
|
2f7d91 |
index cfc564dfb6..f9280956e9 100644
|
|
|
2f7d91 |
--- a/src/core/manager.h
|
|
|
2f7d91 |
+++ b/src/core/manager.h
|
|
|
2f7d91 |
@@ -117,6 +117,9 @@ struct Manager {
|
|
|
2f7d91 |
/* Target units whose default target dependencies haven't been set yet */
|
|
|
2f7d91 |
LIST_HEAD(Unit, target_deps_queue);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+ /* Units that might be subject to StopWhenUnneeded= clean-up */
|
|
|
2f7d91 |
+ LIST_HEAD(Unit, stop_when_unneeded_queue);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
sd_event *event;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
/* We use two hash tables here, since the same PID might be
|
|
|
2f7d91 |
diff --git a/src/core/unit.c b/src/core/unit.c
|
|
|
7e7c9f |
index 750b8b9a5c..2b058dd3e8 100644
|
|
|
2f7d91 |
--- a/src/core/unit.c
|
|
|
2f7d91 |
+++ b/src/core/unit.c
|
|
|
7e7c9f |
@@ -396,6 +396,22 @@ void unit_add_to_dbus_queue(Unit *u) {
|
|
|
2f7d91 |
u->in_dbus_queue = true;
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+void unit_add_to_stop_when_unneeded_queue(Unit *u) {
|
|
|
2f7d91 |
+ assert(u);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ if (u->in_stop_when_unneeded_queue)
|
|
|
2f7d91 |
+ return;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ if (!u->stop_when_unneeded)
|
|
|
2f7d91 |
+ return;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
|
|
|
2f7d91 |
+ return;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ LIST_PREPEND(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
|
|
|
2f7d91 |
+ u->in_stop_when_unneeded_queue = true;
|
|
|
2f7d91 |
+}
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
static void bidi_set_free(Unit *u, Set *s) {
|
|
|
2f7d91 |
Iterator i;
|
|
|
2f7d91 |
Unit *other;
|
|
|
7e7c9f |
@@ -560,6 +576,9 @@ void unit_free(Unit *u) {
|
|
|
2f7d91 |
u->manager->n_in_gc_queue--;
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+ if (u->in_stop_when_unneeded_queue)
|
|
|
2f7d91 |
+ LIST_REMOVE(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
if (u->on_console)
|
|
|
2f7d91 |
manager_unref_console(u->manager);
|
|
|
2f7d91 |
|
|
|
7e7c9f |
@@ -1583,49 +1602,68 @@ bool unit_can_reload(Unit *u) {
|
|
|
2f7d91 |
return UNIT_VTABLE(u)->can_reload(u);
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
-static void unit_check_unneeded(Unit *u) {
|
|
|
2f7d91 |
- Iterator i;
|
|
|
2f7d91 |
- Unit *other;
|
|
|
2f7d91 |
+bool unit_is_unneeded(Unit *u) {
|
|
|
2f7d91 |
+ static const UnitDependency deps[] = {
|
|
|
2f7d91 |
+ UNIT_REQUIRED_BY,
|
|
|
2f7d91 |
+ UNIT_REQUIRED_BY_OVERRIDABLE,
|
|
|
2f7d91 |
+ UNIT_WANTED_BY,
|
|
|
2f7d91 |
+ UNIT_BOUND_BY,
|
|
|
2f7d91 |
+ };
|
|
|
2f7d91 |
+ size_t j;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
assert(u);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- /* If this service shall be shut down when unneeded then do
|
|
|
2f7d91 |
- * so. */
|
|
|
2f7d91 |
-
|
|
|
2f7d91 |
if (!u->stop_when_unneeded)
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
+ return false;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
+ /* Don't clean up while the unit is transitioning or is even inactive. */
|
|
|
2f7d91 |
+ if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
|
|
|
2f7d91 |
+ return false;
|
|
|
2f7d91 |
+ if (u->job)
|
|
|
2f7d91 |
+ return false;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i)
|
|
|
2f7d91 |
- if (unit_active_or_pending(other))
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
+ for (j = 0; j < ELEMENTSOF(deps); j++) {
|
|
|
2f7d91 |
+ Unit *other;
|
|
|
2f7d91 |
+ Iterator i;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i)
|
|
|
2f7d91 |
- if (unit_active_or_pending(other))
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
+ /* If a dependending unit has a job queued, or is active (or in transitioning), or is marked for
|
|
|
2f7d91 |
+ * restart, then don't clean this one up. */
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_WANTED_BY], i)
|
|
|
2f7d91 |
- if (unit_active_or_pending(other))
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
+ SET_FOREACH(other, u->dependencies[deps[j]], i) {
|
|
|
2f7d91 |
+ if (u->job)
|
|
|
2f7d91 |
+ return false;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i)
|
|
|
2f7d91 |
- if (unit_active_or_pending(other))
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
-
|
|
|
2f7d91 |
- /* If stopping a unit fails continously we might enter a stop
|
|
|
2f7d91 |
- * loop here, hence stop acting on the service being
|
|
|
2f7d91 |
- * unnecessary after a while. */
|
|
|
2f7d91 |
- if (!ratelimit_test(&u->check_unneeded_ratelimit)) {
|
|
|
2f7d91 |
- log_unit_warning(u->id, "Unit not needed anymore, but not stopping since we tried this too often recently.");
|
|
|
2f7d91 |
- return;
|
|
|
2f7d91 |
+ if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other)))
|
|
|
2f7d91 |
+ return false;
|
|
|
2f7d91 |
+ }
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- log_unit_info(u->id, "Unit %s is not needed anymore. Stopping.", u->id);
|
|
|
2f7d91 |
+ return true;
|
|
|
2f7d91 |
+}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
- /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
|
|
|
2f7d91 |
- manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL);
|
|
|
2f7d91 |
+static void check_unneeded_dependencies(Unit *u) {
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ static const UnitDependency deps[] = {
|
|
|
2f7d91 |
+ UNIT_REQUIRES,
|
|
|
2f7d91 |
+ UNIT_REQUIRES_OVERRIDABLE,
|
|
|
2f7d91 |
+ UNIT_REQUISITE,
|
|
|
2f7d91 |
+ UNIT_REQUISITE_OVERRIDABLE,
|
|
|
2f7d91 |
+ UNIT_WANTS,
|
|
|
2f7d91 |
+ UNIT_BINDS_TO,
|
|
|
2f7d91 |
+ };
|
|
|
2f7d91 |
+ size_t j;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ assert(u);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ /* Add all units this unit depends on to the queue that processes StopWhenUnneeded= behaviour. */
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ for (j = 0; j < ELEMENTSOF(deps); j++) {
|
|
|
2f7d91 |
+ Unit *other;
|
|
|
2f7d91 |
+ Iterator i;
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
+ SET_FOREACH(other, u->dependencies[deps[j]], i)
|
|
|
2f7d91 |
+ unit_add_to_stop_when_unneeded_queue(other);
|
|
|
2f7d91 |
+ }
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
static void unit_check_binds_to(Unit *u) {
|
|
|
7e7c9f |
@@ -1711,34 +1749,6 @@ static void retroactively_stop_dependencies(Unit *u) {
|
|
|
2f7d91 |
manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL);
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
-static void check_unneeded_dependencies(Unit *u) {
|
|
|
2f7d91 |
- Iterator i;
|
|
|
2f7d91 |
- Unit *other;
|
|
|
2f7d91 |
-
|
|
|
2f7d91 |
- assert(u);
|
|
|
2f7d91 |
- assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)));
|
|
|
2f7d91 |
-
|
|
|
2f7d91 |
- /* Garbage collect services that might not be needed anymore, if enabled */
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i)
|
|
|
2f7d91 |
- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
|
|
|
2f7d91 |
- unit_check_unneeded(other);
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
|
|
|
2f7d91 |
- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
|
|
|
2f7d91 |
- unit_check_unneeded(other);
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_WANTS], i)
|
|
|
2f7d91 |
- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
|
|
|
2f7d91 |
- unit_check_unneeded(other);
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i)
|
|
|
2f7d91 |
- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
|
|
|
2f7d91 |
- unit_check_unneeded(other);
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_REQUISITE_OVERRIDABLE], i)
|
|
|
2f7d91 |
- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
|
|
|
2f7d91 |
- unit_check_unneeded(other);
|
|
|
2f7d91 |
- SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i)
|
|
|
2f7d91 |
- if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
|
|
|
2f7d91 |
- unit_check_unneeded(other);
|
|
|
2f7d91 |
-}
|
|
|
2f7d91 |
-
|
|
|
2f7d91 |
void unit_start_on_failure(Unit *u) {
|
|
|
2f7d91 |
Unit *other;
|
|
|
2f7d91 |
Iterator i;
|
|
|
7e7c9f |
@@ -1916,7 +1926,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
|
|
|
2f7d91 |
}
|
|
|
2f7d91 |
|
|
|
2f7d91 |
/* stop unneeded units regardless if going down was expected or not */
|
|
|
2f7d91 |
- if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns))
|
|
|
2f7d91 |
+ if (UNIT_IS_INACTIVE_OR_FAILED(ns))
|
|
|
2f7d91 |
check_unneeded_dependencies(u);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
if (ns != os && ns == UNIT_FAILED) {
|
|
|
7e7c9f |
@@ -1977,7 +1987,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
|
|
|
2f7d91 |
if (u->manager->n_reloading <= 0) {
|
|
|
2f7d91 |
/* Maybe we finished startup and are now ready for
|
|
|
2f7d91 |
* being stopped because unneeded? */
|
|
|
2f7d91 |
- unit_check_unneeded(u);
|
|
|
2f7d91 |
+ unit_add_to_stop_when_unneeded_queue(u);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
/* Maybe we finished startup, but something we needed
|
|
|
2f7d91 |
* has vanished? Let's die then. (This happens when
|
|
|
2f7d91 |
diff --git a/src/core/unit.h b/src/core/unit.h
|
|
|
7e7c9f |
index e930d8ba5f..f426f3df21 100644
|
|
|
2f7d91 |
--- a/src/core/unit.h
|
|
|
2f7d91 |
+++ b/src/core/unit.h
|
|
|
7e7c9f |
@@ -172,6 +172,9 @@ struct Unit {
|
|
|
2f7d91 |
/* Target dependencies queue */
|
|
|
2f7d91 |
LIST_FIELDS(Unit, target_deps_queue);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+ /* Queue of units with StopWhenUnneeded set that shell be checked for clean-up. */
|
|
|
2f7d91 |
+ LIST_FIELDS(Unit, stop_when_unneeded_queue);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
/* PIDs we keep an eye on. Note that a unit might have many
|
|
|
2f7d91 |
* more, but these are the ones we care enough about to
|
|
|
2f7d91 |
* process SIGCHLD for */
|
|
|
7e7c9f |
@@ -245,6 +248,7 @@ struct Unit {
|
|
|
2f7d91 |
bool in_gc_queue:1;
|
|
|
2f7d91 |
bool in_cgroup_queue:1;
|
|
|
2f7d91 |
bool in_target_deps_queue:1;
|
|
|
2f7d91 |
+ bool in_stop_when_unneeded_queue:1;
|
|
|
2f7d91 |
|
|
|
2f7d91 |
bool sent_dbus_new_signal:1;
|
|
|
2f7d91 |
|
|
|
7e7c9f |
@@ -518,6 +522,7 @@ void unit_add_to_dbus_queue(Unit *u);
|
|
|
2f7d91 |
void unit_add_to_cleanup_queue(Unit *u);
|
|
|
2f7d91 |
void unit_add_to_gc_queue(Unit *u);
|
|
|
2f7d91 |
void unit_add_to_target_deps_queue(Unit *u);
|
|
|
2f7d91 |
+void unit_add_to_stop_when_unneeded_queue(Unit *u);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
int unit_merge(Unit *u, Unit *other);
|
|
|
2f7d91 |
int unit_merge_by_name(Unit *u, const char *other);
|
|
|
7e7c9f |
@@ -637,6 +642,8 @@ int unit_make_transient(Unit *u);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
int unit_require_mounts_for(Unit *u, const char *path);
|
|
|
2f7d91 |
|
|
|
2f7d91 |
+bool unit_is_unneeded(Unit *u);
|
|
|
2f7d91 |
+
|
|
|
2f7d91 |
pid_t unit_control_pid(Unit *u);
|
|
|
2f7d91 |
pid_t unit_main_pid(Unit *u);
|
|
|
2f7d91 |
|