From 49bfaaa65107a9d79fbb1276e44fd4e3c98b0e9c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 7 Feb 2014 11:58:25 +0100 Subject: [PATCH] core: allow PIDs to be watched by two units at the same time In some cases it is interesting to map a PID to two units at the same time. For example, when a user logs in via a getty, which is reexeced to /sbin/login that binary will be explicitly referenced as main pid of the getty service, as well as implicitly referenced as part of the session scope. Conflicts: src/core/manager.c src/core/manager.h --- src/core/manager.c | 201 ++++++++++++++++++++++++++++++----------------------- src/core/manager.h | 9 ++- src/core/unit.c | 28 ++++++-- 3 files changed, 142 insertions(+), 96 deletions(-) diff --git a/src/core/manager.c b/src/core/manager.c index db5094f..2829c95 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -525,7 +525,10 @@ int manager_new(SystemdRunningAs running_as, bool reexecuting, Manager **_m) { if (!(m->jobs = hashmap_new(trivial_hash_func, trivial_compare_func))) goto fail; - if (!(m->watch_pids = hashmap_new(trivial_hash_func, trivial_compare_func))) + if (!(m->watch_pids1 = hashmap_new(trivial_hash_func, trivial_compare_func))) + goto fail; + + if (!(m->watch_pids2 = hashmap_new(trivial_hash_func, trivial_compare_func))) goto fail; m->cgroup_unit = hashmap_new(string_hash_func, string_compare_func); @@ -740,7 +743,8 @@ void manager_free(Manager *m) { hashmap_free(m->units); hashmap_free(m->jobs); - hashmap_free(m->watch_pids); + hashmap_free(m->watch_pids1); + hashmap_free(m->watch_pids2); hashmap_free(m->watch_bus); if (m->epoll_fd >= 0) @@ -1247,6 +1251,26 @@ unsigned manager_dispatch_dbus_queue(Manager *m) { return n; } +static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, char *buf, size_t n) { + _cleanup_strv_free_ char **tags = NULL; + + assert(m); + assert(u); + assert(buf); + assert(n > 0); + + tags = strv_split(buf, "\n\r"); + if (!tags) { + log_oom(); + return; + } + + log_debug_unit(u->id, "Got notification message for unit %s", u->id); + + if (UNIT_VTABLE(u)->notify_message) + UNIT_VTABLE(u)->notify_message(u, pid, tags); +} + static int manager_process_notify_fd(Manager *m) { ssize_t n; @@ -1259,6 +1283,8 @@ static int manager_process_notify_fd(Manager *m) { .iov_len = sizeof(buf)-1, }; + bool found = false; + union { struct cmsghdr cmsghdr; uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; @@ -1272,7 +1298,6 @@ static int manager_process_notify_fd(Manager *m) { }; struct ucred *ucred; Unit *u; - _cleanup_strv_free_ char **tags = NULL; n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT); if (n <= 0) { @@ -1295,105 +1320,105 @@ static int manager_process_notify_fd(Manager *m) { ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); - u = hashmap_get(m->watch_pids, LONG_TO_PTR(ucred->pid)); - if (!u) { - u = manager_get_unit_by_pid(m, ucred->pid); - if (!u) { - log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid); - continue; - } - } - assert((size_t) n < sizeof(buf)); buf[n] = 0; - tags = strv_split(buf, "\n\r"); - if (!tags) - return log_oom(); - - log_debug_unit(u->id, "Got notification message for unit %s", u->id); - - if (UNIT_VTABLE(u)->notify_message) - UNIT_VTABLE(u)->notify_message(u, ucred->pid, tags); - } - - return 0; -} - -static int manager_dispatch_sigchld(Manager *m) { - assert(m); - - for (;;) { - siginfo_t si = {}; - Unit *u; - int r; - - /* First we call waitd() for a PID and do not reap the - * zombie. That way we can still access /proc/$PID for - * it while it is a zombie. */ - if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { - - if (errno == ECHILD) - break; - if (errno == EINTR) - continue; - - return -errno; + u = manager_get_unit_by_pid(m, ucred->pid); + if (u) { + manager_invoke_notify_message(m, u, ucred->pid, buf, n); + found = true; } - if (si.si_pid <= 0) - break; - - if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { - _cleanup_free_ char *name = NULL; - - get_process_comm(si.si_pid, &name); - log_debug("Got SIGCHLD for process %lu (%s)", (unsigned long) si.si_pid, strna(name)); + u = hashmap_get(m->watch_pids1, LONG_TO_PTR(ucred->pid)); + if (u) { + manager_invoke_notify_message(m, u, ucred->pid, buf, n); + found = true; } - /* Let's flush any message the dying child might still - * have queued for us. This ensures that the process - * still exists in /proc so that we can figure out - * which cgroup and hence unit it belongs to. */ - r = manager_process_notify_fd(m); - if (r < 0) - return r; - - /* And now figure out the unit this belongs to */ - u = hashmap_get(m->watch_pids, LONG_TO_PTR(si.si_pid)); - if (!u) - u = manager_get_unit_by_pid(m, si.si_pid); - - /* And now, we actually reap the zombie. */ - if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { - if (errno == EINTR) - continue; - - return -errno; + u = hashmap_get(m->watch_pids2, LONG_TO_PTR(ucred->pid)); + if (u) { + manager_invoke_notify_message(m, u, ucred->pid, buf, n); + found = true; } - if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED) - continue; + if (!found) + log_warning("Cannot find unit for notify message of PID %lu.",(long unsigned) ucred->pid); + } - log_debug("Child %lu died (code=%s, status=%i/%s)", - (long unsigned) si.si_pid, - sigchld_code_to_string(si.si_code), - si.si_status, - strna(si.si_code == CLD_EXITED - ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL) - : signal_to_string(si.si_status))); + return 0; +} - if (!u) - continue; +static void invoke_sigchld_event(Manager *m, Unit *u, siginfo_t *si) { + assert(m); + assert(u); + assert(si); - log_debug_unit(u->id, - "Child %lu belongs to %s", (long unsigned) si.si_pid, u->id); + log_debug_unit(u->id, "Child %lu belongs to %s",(long unsigned) si->si_pid, u->id); - unit_unwatch_pid(u, si.si_pid); - UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status); - } + unit_unwatch_pid(u, si->si_pid); + UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status); +} - return 0; +static int manager_dispatch_sigchld(Manager *m) { + assert(m); + + for (;;) { + siginfo_t si = {}; + + /* First we call waitd() for a PID and do not reap the + * zombie. That way we can still access /proc/$PID for + * it while it is a zombie. */ + if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { + + if (errno == ECHILD) + break; + + if (errno == EINTR) + continue; + + return -errno; + } + + if (si.si_pid <= 0) + break; + + if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { + _cleanup_free_ char *name = NULL; + Unit *u; + + get_process_comm(si.si_pid, &name); + + log_debug("Child %lu (%s) died (code=%s, status=%i/%s)", + (long unsigned) si.si_pid, strna(name), + sigchld_code_to_string(si.si_code), + si.si_status, + strna(si.si_code == CLD_EXITED + ? 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... */ + u = manager_get_unit_by_pid(m, si.si_pid); + if (u) + invoke_sigchld_event(m, u, &si); + u = hashmap_get(m->watch_pids1, LONG_TO_PTR(si.si_pid)); + if (u) + invoke_sigchld_event(m, u, &si); + u = hashmap_get(m->watch_pids2, LONG_TO_PTR(si.si_pid)); + if (u) + invoke_sigchld_event(m, u, &si); + } + + /* And now, we actually reap the zombie. */ + if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + } + + return 0; } static int manager_start_target(Manager *m, const char *name, JobMode mode) { diff --git a/src/core/manager.h b/src/core/manager.h index ee42c5e..0133ea5 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -125,7 +125,14 @@ struct Manager { /* Units that should be realized */ LIST_HEAD(Unit, cgroup_queue); - Hashmap *watch_pids; /* pid => Unit object n:1 */ + /* 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 */ char *notify_socket; diff --git a/src/core/unit.c b/src/core/unit.c index 0332094..a510eb2 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1665,16 +1665,27 @@ int unit_watch_pid(Unit *u, pid_t pid) { assert(u); assert(pid >= 1); + /* Watch a specific PID. We only support one or two units + * watching each PID for now, not more. */ + + r = hashmap_ensure_allocated(&u->manager->watch_pids1, trivial_hash_func, trivial_compare_func); + if (r < 0) + return r; + r = set_ensure_allocated(&u->pids, trivial_hash_func, trivial_compare_func); if (r < 0) return r; - /* Watch a specific PID. We only support one unit watching - * each PID for now. */ + r = hashmap_put(u->manager->watch_pids1, LONG_TO_PTR(pid), u); + if (r == -EEXIST) { + r = hashmap_ensure_allocated(&u->manager->watch_pids2, trivial_hash_func, trivial_compare_func); + if (r < 0) + return r; - r = set_put(u->pids, LONG_TO_PTR(pid)); + r = hashmap_put(u->manager->watch_pids2, LONG_TO_PTR(pid), u); + } - q = hashmap_put(u->manager->watch_pids, LONG_TO_PTR(pid), u); + q = set_put(u->pids, LONG_TO_PTR(pid)); if (q < 0) return q; @@ -1685,7 +1696,8 @@ void unit_unwatch_pid(Unit *u, pid_t pid) { assert(u); assert(pid >= 1); - hashmap_remove_value(u->manager->watch_pids, LONG_TO_PTR(pid), u); + hashmap_remove_value(u->manager->watch_pids1, LONG_TO_PTR(pid), u); + hashmap_remove_value(u->manager->watch_pids2, LONG_TO_PTR(pid), u); set_remove(u->pids, LONG_TO_PTR(pid)); } @@ -1758,8 +1770,10 @@ void unit_unwatch_all_pids(Unit *u) { assert(u); - SET_FOREACH(e, u->pids, i) - hashmap_remove_value(u->manager->watch_pids, e, u); + SET_FOREACH(e, u->pids, i) { + hashmap_remove_value(u->manager->watch_pids1, e, u); + hashmap_remove_value(u->manager->watch_pids2, e, u); + } set_free(u->pids); u->pids = NULL;