Merge pull request #7860 from poettering/watch-pids-rework

rework how the manager watches PIDs
This commit is contained in:
Lennart Poettering 2018-01-24 11:18:04 +01:00 committed by GitHub
commit 7b7e6b8df5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 381 additions and 120 deletions

4
TODO
View file

@ -39,6 +39,10 @@ Features:
* add bpf-based implementation of devices cgroup controller logic for compat with cgroupsv2 as supported by newest kernel
* introduce sd_id128_get_boot_app_specific() which is like
sd_id128_get_machine_app_specific(). After all on long-running systems both
IDs have similar properties.
* sd-bus: add vtable flag, that may be used to request client creds implicitly
and asynchronously before dispatching the operation

View file

@ -116,8 +116,13 @@ int sigchld_code_from_string(const char *s) _pure_;
int sched_policy_to_string_alloc(int i, char **s);
int sched_policy_from_string(const char *s);
#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p))
#define PID_TO_PTR(p) ((void*) ((uintptr_t) p))
static inline pid_t PTR_TO_PID(const void *p) {
return (pid_t) ((uintptr_t) p);
}
static inline void* PID_TO_PTR(pid_t pid) {
return (void*) ((uintptr_t) pid);
}
void valgrind_summary_hack(void);

View file

@ -1827,6 +1827,31 @@ static int unit_watch_pids_in_path(Unit *u, const char *path) {
return ret;
}
int unit_synthesize_cgroup_empty_event(Unit *u) {
int r;
assert(u);
/* Enqueue a synthetic cgroup empty event if this unit doesn't watch any PIDs anymore. This is compatibility
* support for non-unified systems where notifications aren't reliable, and hence need to take whatever we can
* get as notification source as soon as we stopped having any useful PIDs to watch for. */
if (!u->cgroup_path)
return -ENOENT;
r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
if (r < 0)
return r;
if (r > 0) /* On unified we have reliable notifications, and don't need this */
return 0;
if (!set_isempty(u->pids))
return 0;
unit_add_to_cgroup_empty_queue(u);
return 0;
}
int unit_watch_all_pids(Unit *u) {
int r;
@ -2161,40 +2186,46 @@ Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) {
Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid) {
_cleanup_free_ char *cgroup = NULL;
int r;
assert(m);
if (pid <= 0)
if (!pid_is_valid(pid))
return NULL;
r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
if (r < 0)
if (cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup) < 0)
return NULL;
return manager_get_unit_by_cgroup(m, cgroup);
}
Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) {
Unit *u;
Unit *u, **array;
assert(m);
if (pid <= 0)
/* Note that a process might be owned by multiple units, we return only one here, which is good enough for most
* cases, though not strictly correct. We prefer the one reported by cgroup membership, as that's the most
* relevant one as children of the process will be assigned to that one, too, before all else. */
if (!pid_is_valid(pid))
return NULL;
if (pid == 1)
if (pid == getpid_cached())
return hashmap_get(m->units, SPECIAL_INIT_SCOPE);
u = hashmap_get(m->watch_pids1, PID_TO_PTR(pid));
u = manager_get_unit_by_pid_cgroup(m, pid);
if (u)
return u;
u = hashmap_get(m->watch_pids2, PID_TO_PTR(pid));
u = hashmap_get(m->watch_pids, PID_TO_PTR(pid));
if (u)
return u;
return manager_get_unit_by_pid_cgroup(m, pid);
array = hashmap_get(m->watch_pids, PID_TO_PTR(-pid));
if (array)
return array[0];
return NULL;
}
int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {

View file

@ -192,6 +192,8 @@ Unit* manager_get_unit_by_pid(Manager *m, pid_t pid);
int unit_search_main_pid(Unit *u, pid_t *ret);
int unit_watch_all_pids(Unit *u);
int unit_synthesize_cgroup_empty_event(Unit *u);
int unit_get_memory_current(Unit *u, uint64_t *ret);
int unit_get_tasks_current(Unit *u, uint64_t *ret);
int unit_get_cpu_usage(Unit *u, nsec_t *ret);

View file

@ -533,7 +533,7 @@ int bus_cgroup_set_property(
if (streq(name, "MemoryLimitScale"))
return bus_cgroup_set_memory_scale(u, name, &c->memory_limit, message, flags, error);
if (streq(name, "TasksAccountingScale"))
if (streq(name, "TasksAccounting"))
return bus_cgroup_set_boolean(u, name, &c->tasks_accounting, CGROUP_MASK_PIDS, message, flags, error);
if (streq(name, "TasksMax"))

View file

@ -1205,8 +1205,7 @@ Manager* manager_free(Manager *m) {
hashmap_free(m->units);
hashmap_free(m->units_by_invocation_id);
hashmap_free(m->jobs);
hashmap_free(m->watch_pids1);
hashmap_free(m->watch_pids2);
hashmap_free(m->watch_pids);
hashmap_free(m->watch_bus);
set_free(m->startup_units);
@ -1915,22 +1914,27 @@ static void manager_invoke_notify_message(
const char *buf,
FDSet *fds) {
_cleanup_strv_free_ char **tags = NULL;
assert(m);
assert(u);
assert(ucred);
assert(buf);
tags = strv_split(buf, NEWLINE);
if (!tags) {
log_oom();
if (u->notifygen == m->notifygen) /* Already invoked on this same unit in this same iteration? */
return;
}
u->notifygen = m->notifygen;
if (UNIT_VTABLE(u)->notify_message) {
_cleanup_strv_free_ char **tags = NULL;
tags = strv_split(buf, NEWLINE);
if (!tags) {
log_oom();
return;
}
if (UNIT_VTABLE(u)->notify_message)
UNIT_VTABLE(u)->notify_message(u, ucred, tags, fds);
else if (DEBUG_LOGGING) {
} else if (DEBUG_LOGGING) {
_cleanup_free_ char *x = NULL, *y = NULL;
x = ellipsize(buf, 20, 90);
@ -1964,9 +1968,11 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
struct cmsghdr *cmsg;
struct ucred *ucred = NULL;
Unit *u1, *u2, *u3;
_cleanup_free_ Unit **array_copy = NULL;
Unit *u1, *u2, **array;
int r, *fd_array = NULL;
unsigned n_fds = 0;
bool found = false;
ssize_t n;
assert(m);
@ -2033,22 +2039,41 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
/* Make sure it's NUL-terminated. */
buf[n] = 0;
/* Notify every unit that might be interested, but try
* to avoid notifying the same one multiple times. */
/* Increase the generation counter used for filtering out duplicate unit invocations. */
m->notifygen++;
/* Notify every unit that might be interested, which might be multiple. */
u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid);
if (u1)
u2 = hashmap_get(m->watch_pids, PID_TO_PTR(ucred->pid));
array = hashmap_get(m->watch_pids, PID_TO_PTR(-ucred->pid));
if (array) {
size_t k = 0;
while (array[k])
k++;
array_copy = newdup(Unit*, array, k+1);
if (!array_copy)
log_oom();
}
/* And now invoke the per-unit callbacks. Note that manager_invoke_notify_message() will handle duplicate units
* make sure we only invoke each unit's handler once. */
if (u1) {
manager_invoke_notify_message(m, u1, ucred, buf, fds);
u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid));
if (u2 && u2 != u1)
found = true;
}
if (u2) {
manager_invoke_notify_message(m, u2, ucred, buf, fds);
found = true;
}
if (array_copy)
for (size_t i = 0; array_copy[i]; i++) {
manager_invoke_notify_message(m, array_copy[i], ucred, buf, fds);
found = true;
}
u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid));
if (u3 && u3 != u2 && u3 != u1)
manager_invoke_notify_message(m, u3, ucred, buf, fds);
if (!u1 && !u2 && !u3)
log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid);
if (!found)
log_warning("Cannot find unit for notify message of PID "PID_FMT", ignoring.", ucred->pid);
if (fdset_size(fds) > 0)
log_warning("Got extra auxiliary fds with notification message, closing them.");
@ -2056,29 +2081,25 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
return 0;
}
static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) {
uint64_t iteration;
static void manager_invoke_sigchld_event(
Manager *m,
Unit *u,
const siginfo_t *si) {
assert(m);
assert(u);
assert(si);
sd_event_get_iteration(m->event, &iteration);
log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id);
/* Already invoked the handler of this unit in this iteration? Then don't process this again */
if (u->sigchldgen == m->sigchldgen)
return;
u->sigchldgen = m->sigchldgen;
log_unit_debug(u, "Child "PID_FMT" belongs to %s.", si->si_pid, u->id);
unit_unwatch_pid(u, si->si_pid);
if (UNIT_VTABLE(u)->sigchld_event) {
if (set_size(u->pids) <= 1 ||
iteration != u->sigchldgen ||
unit_main_pid(u) == si->si_pid ||
unit_control_pid(u) == si->si_pid) {
UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status);
u->sigchldgen = iteration;
} else
log_debug("%s already issued a sigchld this iteration %" PRIu64 ", skipping. Pids still being watched %d", u->id, iteration, set_size(u->pids));
}
if (UNIT_VTABLE(u)->sigchld_event)
UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status);
}
static int manager_dispatch_sigchld(sd_event_source *source, void *userdata) {
@ -2105,8 +2126,9 @@ static int manager_dispatch_sigchld(sd_event_source *source, void *userdata) {
goto turn_off;
if (IN_SET(si.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED)) {
_cleanup_free_ Unit **array_copy = NULL;
_cleanup_free_ char *name = NULL;
Unit *u1, *u2, *u3;
Unit *u1, *u2, **array;
(void) get_process_comm(si.si_pid, &name);
@ -2118,17 +2140,36 @@ static int manager_dispatch_sigchld(sd_event_source *source, void *userdata) {
? exit_status_to_string(si.si_status, EXIT_STATUS_FULL)
: signal_to_string(si.si_status)));
/* And now figure out the unit this belongs
* to, it might be multiple... */
/* Increase the generation counter used for filtering out duplicate unit invocations */
m->sigchldgen++;
/* And now figure out the unit this belongs to, it might be multiple... */
u1 = manager_get_unit_by_pid_cgroup(m, si.si_pid);
u2 = hashmap_get(m->watch_pids, PID_TO_PTR(si.si_pid));
array = hashmap_get(m->watch_pids, PID_TO_PTR(-si.si_pid));
if (array) {
size_t n = 0;
/* Cound how many entries the array has */
while (array[n])
n++;
/* Make a copy of the array so that we don't trip up on the array changing beneath us */
array_copy = newdup(Unit*, array, n+1);
if (!array_copy)
log_oom();
}
/* Finally, execute them all. Note that u1, u2 and the array might contain duplicates, but
* that's fine, manager_invoke_sigchld_event() will ensure we only invoke the handlers once for
* each iteration. */
if (u1)
invoke_sigchld_event(m, u1, &si);
u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(si.si_pid));
if (u2 && u2 != u1)
invoke_sigchld_event(m, u2, &si);
u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(si.si_pid));
if (u3 && u3 != u2 && u3 != u1)
invoke_sigchld_event(m, u3, &si);
manager_invoke_sigchld_event(m, u1, &si);
if (u2)
manager_invoke_sigchld_event(m, u2, &si);
if (array_copy)
for (size_t i = 0; array_copy[i]; i++)
manager_invoke_sigchld_event(m, array_copy[i], &si);
}
/* And now, we actually reap the zombie. */

View file

@ -145,14 +145,14 @@ struct Manager {
sd_event *event;
/* We use two hash tables here, since the same PID might be
* watched by two different units: once the unit that forked
* it off, and possibly a different unit to which it was
* joined as cgroup member. Since we know that it is either
* one or two units for each PID we just use to hashmaps
* here. */
Hashmap *watch_pids1; /* pid => Unit object n:1 */
Hashmap *watch_pids2; /* pid => Unit object n:1 */
/* This maps PIDs we care about to units that are interested in. We allow multiple units to he interested in
* the same PID and multiple PIDs to be relevant to the same unit. Since in most cases only a single unit will
* be interested in the same PID we use a somewhat special encoding here: the first unit interested in a PID is
* stored directly in the hashmap, keyed by the PID unmodified. If there are other units interested too they'll
* be stored in a NULL-terminated array, and keyed by the negative PID. This is safe as pid_t is signed and
* negative PIDs are not used for regular processes but process groups, which we don't care about in this
* context, but this allows us to use the negative range for our own purposes. */
Hashmap *watch_pids; /* pid => unit as well as -pid => array of units */
/* A set contains all units which cgroup should be refreshed after startup */
Set *startup_units;
@ -350,8 +350,13 @@ struct Manager {
int first_boot; /* tri-state */
/* prefixes of e.g. RuntimeDirectory= */
/* Prefixes of e.g. RuntimeDirectory= */
char *prefix[_EXEC_DIRECTORY_TYPE_MAX];
/* Used in the SIGCHLD and sd_notify() message invocation logic to avoid that we dispatch the same event
* multiple times on the same unit. */
unsigned sigchldgen;
unsigned notifygen;
};
#define MANAGER_IS_SYSTEM(m) ((m)->unit_file_scope == UNIT_FILE_SYSTEM)

View file

@ -472,19 +472,16 @@ static void scope_notify_cgroup_empty_event(Unit *u) {
static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) {
/* If we get a SIGCHLD event for one of the processes we were
interested in, then we look for others to watch, under the
assumption that we'll sooner or later get a SIGCHLD for
them, as the original process we watched was probably the
parent of them, and they are hence now our children. */
assert(u);
/* If we get a SIGCHLD event for one of the processes we were interested in, then we look for others to
* watch, under the assumption that we'll sooner or later get a SIGCHLD for them, as the original
* process we watched was probably the parent of them, and they are hence now our children. */
unit_tidy_watch_pids(u, 0, 0);
unit_watch_all_pids(u);
/* If the PID set is empty now, then let's finish this off
(On unified we use proper notifications) */
if (cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) == 0 && set_isempty(u->pids))
scope_notify_cgroup_empty_event(u);
/* If the PID set is empty now, then let's finish this off. */
unit_synthesize_cgroup_empty_event(u);
}
static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) {

View file

@ -1133,8 +1133,7 @@ static int service_coldplug(Unit *u) {
if (s->main_pid > 0 &&
pid_is_unwaited(s->main_pid) &&
((s->deserialized_state == SERVICE_START && IN_SET(s->type, SERVICE_FORKING, SERVICE_DBUS, SERVICE_ONESHOT, SERVICE_NOTIFY)) ||
IN_SET(s->deserialized_state,
(IN_SET(s->deserialized_state,
SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING, SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
@ -2639,12 +2638,8 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
if (parse_pid(value, &pid) < 0)
log_unit_debug(u, "Failed to parse main-pid value: %s", value);
else {
service_set_main_pid(s, pid);
r = unit_watch_pid(UNIT(s), pid);
if (r < 0)
log_unit_debug_errno(u, r, "Failed to watch main PID, ignoring: %m");
}
else
(void) service_set_main_pid(s, pid);
} else if (streq(key, "main-pid-known")) {
int b;
@ -3015,6 +3010,7 @@ static void service_notify_cgroup_empty_event(Unit *u) {
}
static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
bool notify_dbus = true;
Service *s = SERVICE(u);
ServiceResult f;
@ -3293,23 +3289,21 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
assert_not_reached("Uh, control process died at wrong time.");
}
}
}
} else /* Neither control nor main PID? If so, don't notify about anything */
notify_dbus = false;
/* Notify clients about changed exit status */
unit_add_to_dbus_queue(u);
if (notify_dbus)
unit_add_to_dbus_queue(u);
/* We got one SIGCHLD for the service, let's watch all
* processes that are now running of the service, and watch
* that. Among the PIDs we then watch will be children
* reassigned to us, which hopefully allows us to identify
* when all children are gone */
/* If we get a SIGCHLD event for one of the processes we were interested in, then we look for others to watch,
* under the assumption that we'll sooner or later get a SIGCHLD for them, as the original process we watched
* was probably the parent of them, and they are hence now our children. */
unit_tidy_watch_pids(u, s->main_pid, s->control_pid);
unit_watch_all_pids(u);
/* If the PID set is empty now, then let's finish this off
(On unified we use proper notifications) */
if (cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) == 0 && set_isempty(u->pids))
unit_add_to_cgroup_empty_queue(u);
/* If the PID set is empty now, then let's check if the cgroup is empty too and finish off the unit. */
unit_synthesize_cgroup_empty_event(u);
}
static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) {

View file

@ -2534,44 +2534,97 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
}
int unit_watch_pid(Unit *u, pid_t pid) {
int q, r;
int r;
assert(u);
assert(pid >= 1);
assert(pid_is_valid(pid));
/* Watch a specific PID. We only support one or two units
* watching each PID for now, not more. */
/* Watch a specific PID */
r = set_ensure_allocated(&u->pids, NULL);
if (r < 0)
return r;
r = hashmap_ensure_allocated(&u->manager->watch_pids1, NULL);
r = hashmap_ensure_allocated(&u->manager->watch_pids, NULL);
if (r < 0)
return r;
r = hashmap_put(u->manager->watch_pids1, PID_TO_PTR(pid), u);
if (r == -EEXIST) {
r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL);
if (r < 0)
return r;
/* First try, let's add the unit keyed by "pid". */
r = hashmap_put(u->manager->watch_pids, PID_TO_PTR(pid), u);
if (r == -EEXIST) {
Unit **array;
bool found = false;
size_t n = 0;
r = hashmap_put(u->manager->watch_pids2, PID_TO_PTR(pid), u);
}
/* OK, the "pid" key is already assigned to a different unit. Let's see if the "-pid" key (which points
* to an array of Units rather than just a Unit), lists us already. */
q = set_put(u->pids, PID_TO_PTR(pid));
if (q < 0)
return q;
array = hashmap_get(u->manager->watch_pids, PID_TO_PTR(-pid));
if (array)
for (; array[n]; n++)
if (array[n] == u)
found = true;
return r;
if (found) /* Found it already? if so, do nothing */
r = 0;
else {
Unit **new_array;
/* Allocate a new array */
new_array = new(Unit*, n + 2);
if (!new_array)
return -ENOMEM;
memcpy_safe(new_array, array, sizeof(Unit*) * n);
new_array[n] = u;
new_array[n+1] = NULL;
/* Add or replace the old array */
r = hashmap_replace(u->manager->watch_pids, PID_TO_PTR(-pid), new_array);
if (r < 0) {
free(new_array);
return r;
}
free(array);
}
} else if (r < 0)
return r;
r = set_put(u->pids, PID_TO_PTR(pid));
if (r < 0)
return r;
return 0;
}
void unit_unwatch_pid(Unit *u, pid_t pid) {
assert(u);
assert(pid >= 1);
Unit **array;
assert(u);
assert(pid_is_valid(pid));
/* First let's drop the unit in case it's keyed as "pid". */
(void) hashmap_remove_value(u->manager->watch_pids, PID_TO_PTR(pid), u);
/* Then, let's also drop the unit, in case it's in the array keyed by -pid */
array = hashmap_get(u->manager->watch_pids, PID_TO_PTR(-pid));
if (array) {
size_t n, m = 0;
/* Let's iterate through the array, dropping our own entry */
for (n = 0; array[n]; n++)
if (array[n] != u)
array[m++] = array[n];
array[m] = NULL;
if (m == 0) {
/* The array is now empty, remove the entire entry */
assert(hashmap_remove(u->manager->watch_pids, PID_TO_PTR(-pid)) == array);
free(array);
}
}
(void) hashmap_remove_value(u->manager->watch_pids1, PID_TO_PTR(pid), u);
(void) hashmap_remove_value(u->manager->watch_pids2, PID_TO_PTR(pid), u);
(void) set_remove(u->pids, PID_TO_PTR(pid));
}

View file

@ -236,8 +236,10 @@ struct Unit {
* process SIGCHLD for */
Set *pids;
/* Used in sigchld event invocation to avoid repeat events being invoked */
uint64_t sigchldgen;
/* Used in SIGCHLD and sd_notify() message event invocation logic to avoid that we dispatch the same event
* multiple times on the same unit. */
unsigned sigchldgen;
unsigned notifygen;
/* Used during GC sweeps */
unsigned gc_marker;

View file

@ -377,6 +377,17 @@ tests += [
libselinux,
libblkid]],
[['src/test/test-watch-pid.c',
'src/test/test-helper.c'],
[libcore,
libshared],
[libmount,
threads,
librt,
libseccomp,
libselinux,
libblkid]],
[['src/test/test-hashmap.c',
'src/test/test-hashmap-plain.c',
test_hashmap_ordered_c],

View file

@ -520,6 +520,25 @@ static void test_safe_fork(void) {
assert_se(status.si_status == 88);
}
static void test_pid_to_ptr(void) {
assert_se(PTR_TO_PID(NULL) == 0);
assert_se(PID_TO_PTR(0) == NULL);
assert_se(PTR_TO_PID(PID_TO_PTR(1)) == 1);
assert_se(PTR_TO_PID(PID_TO_PTR(2)) == 2);
assert_se(PTR_TO_PID(PID_TO_PTR(-1)) == -1);
assert_se(PTR_TO_PID(PID_TO_PTR(-2)) == -2);
assert_se(PTR_TO_PID(PID_TO_PTR(INT16_MAX)) == INT16_MAX);
assert_se(PTR_TO_PID(PID_TO_PTR(INT16_MIN)) == INT16_MIN);
#if SIZEOF_PID_T >= 4
assert_se(PTR_TO_PID(PID_TO_PTR(INT32_MAX)) == INT32_MAX);
assert_se(PTR_TO_PID(PID_TO_PTR(INT32_MIN)) == INT32_MIN);
#endif
}
int main(int argc, char *argv[]) {
log_set_max_level(LOG_DEBUG);
@ -547,6 +566,7 @@ int main(int argc, char *argv[]) {
test_getpid_cached();
test_getpid_measure();
test_safe_fork();
test_pid_to_ptr();
return 0;
}

96
src/test/test-watch-pid.c Normal file
View file

@ -0,0 +1,96 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "log.h"
#include "manager.h"
#include "rm-rf.h"
#include "test-helper.h"
#include "tests.h"
int main(int argc, char *argv[]) {
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
Unit *a, *b, *c, *u;
Manager *m;
int r;
log_set_max_level(LOG_DEBUG);
log_parse_environment();
log_open();
if (getuid() != 0) {
log_notice("Not running as root, skipping kernel related tests.");
return EXIT_TEST_SKIP;
}
r = enter_cgroup_subroot();
if (r == -ENOMEDIUM) {
log_notice("cgroupfs not available, skipping tests");
return EXIT_TEST_SKIP;
}
assert_se(set_unit_path(get_testdata_dir("")) >= 0);
assert_se(runtime_dir = setup_fake_runtime_dir());
assert_se(manager_new(UNIT_FILE_USER, true, &m) >= 0);
assert_se(manager_startup(m, NULL, NULL) >= 0);
assert_se(a = unit_new(m, sizeof(Service)));
assert_se(unit_add_name(a, "a.service") >= 0);
assert_se(set_isempty(a->pids));
assert_se(b = unit_new(m, sizeof(Service)));
assert_se(unit_add_name(b, "b.service") >= 0);
assert_se(set_isempty(b->pids));
assert_se(c = unit_new(m, sizeof(Service)));
assert_se(unit_add_name(c, "c.service") >= 0);
assert_se(set_isempty(c->pids));
assert_se(hashmap_isempty(m->watch_pids));
assert_se(manager_get_unit_by_pid(m, 4711) == NULL);
assert_se(unit_watch_pid(a, 4711) >= 0);
assert_se(manager_get_unit_by_pid(m, 4711) == a);
assert_se(unit_watch_pid(a, 4711) >= 0);
assert_se(manager_get_unit_by_pid(m, 4711) == a);
assert_se(unit_watch_pid(b, 4711) >= 0);
u = manager_get_unit_by_pid(m, 4711);
assert_se(u == a || u == b);
assert_se(unit_watch_pid(b, 4711) >= 0);
u = manager_get_unit_by_pid(m, 4711);
assert_se(u == a || u == b);
assert_se(unit_watch_pid(c, 4711) >= 0);
u = manager_get_unit_by_pid(m, 4711);
assert_se(u == a || u == b || u == c);
assert_se(unit_watch_pid(c, 4711) >= 0);
u = manager_get_unit_by_pid(m, 4711);
assert_se(u == a || u == b || u == c);
unit_unwatch_pid(b, 4711);
u = manager_get_unit_by_pid(m, 4711);
assert_se(u == a || u == c);
unit_unwatch_pid(b, 4711);
u = manager_get_unit_by_pid(m, 4711);
assert_se(u == a || u == c);
unit_unwatch_pid(a, 4711);
assert_se(manager_get_unit_by_pid(m, 4711) == c);
unit_unwatch_pid(a, 4711);
assert_se(manager_get_unit_by_pid(m, 4711) == c);
unit_unwatch_pid(c, 4711);
assert_se(manager_get_unit_by_pid(m, 4711) == NULL);
unit_unwatch_pid(c, 4711);
assert_se(manager_get_unit_by_pid(m, 4711) == NULL);
manager_free(m);
return 0;
}