summaryrefslogtreecommitdiffstats
path: root/src/lib/child-wait.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/child-wait.c')
-rw-r--r--src/lib/child-wait.c139
1 files changed, 139 insertions, 0 deletions
diff --git a/src/lib/child-wait.c b/src/lib/child-wait.c
new file mode 100644
index 0000000..024d81d
--- /dev/null
+++ b/src/lib/child-wait.c
@@ -0,0 +1,139 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "hash.h"
+#include "child-wait.h"
+
+#include <sys/wait.h>
+
+struct child_wait {
+ unsigned int pid_count;
+
+ child_wait_callback_t *callback;
+ void *context;
+};
+
+static int child_wait_refcount = 0;
+
+/* pid_t => wait */
+static HASH_TABLE(void *, struct child_wait *) child_pids;
+
+static void
+sigchld_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED);
+
+#undef child_wait_new_with_pid
+struct child_wait *
+child_wait_new_with_pid(pid_t pid, child_wait_callback_t *callback,
+ void *context)
+{
+ struct child_wait *wait;
+
+ wait = i_new(struct child_wait, 1);
+ wait->callback = callback;
+ wait->context = context;
+
+ if (pid != (pid_t)-1)
+ child_wait_add_pid(wait, pid);
+ return wait;
+}
+
+void child_wait_free(struct child_wait **_wait)
+{
+ struct child_wait *wait = *_wait;
+ struct hash_iterate_context *iter;
+ void *key;
+ struct child_wait *value;
+
+ *_wait = NULL;
+
+ if (wait->pid_count > 0) {
+ /* this should be rare, so iterating hash is fast enough */
+ iter = hash_table_iterate_init(child_pids);
+ while (hash_table_iterate(iter, child_pids, &key, &value)) {
+ if (value == wait) {
+ hash_table_remove(child_pids, key);
+ if (--wait->pid_count == 0)
+ break;
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+ }
+
+ i_free(wait);
+}
+
+void child_wait_add_pid(struct child_wait *wait, pid_t pid)
+{
+ wait->pid_count++;
+ hash_table_insert(child_pids, POINTER_CAST(pid), wait);
+
+ lib_signals_set_expected(SIGCHLD, TRUE, sigchld_handler, NULL);
+}
+
+void child_wait_remove_pid(struct child_wait *wait, pid_t pid)
+{
+ wait->pid_count--;
+ hash_table_remove(child_pids, POINTER_CAST(pid));
+
+ if (hash_table_count(child_pids) == 0)
+ lib_signals_set_expected(SIGCHLD, FALSE, sigchld_handler, NULL);
+}
+
+static void
+sigchld_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
+{
+ struct child_wait_status status;
+
+ while ((status.pid = waitpid(-1, &status.status, WNOHANG)) > 0) {
+ status.wait = hash_table_lookup(child_pids,
+ POINTER_CAST(status.pid));
+ if (status.wait != NULL) {
+ child_wait_remove_pid(status.wait, status.pid);
+ status.wait->callback(&status, status.wait->context);
+ }
+ }
+
+ if (status.pid == -1 && errno != EINTR && errno != ECHILD)
+ i_error("waitpid() failed: %m");
+}
+
+void child_wait_switch_ioloop(void)
+{
+ lib_signals_switch_ioloop(SIGCHLD, sigchld_handler, NULL);
+}
+
+void child_wait_init(void)
+{
+ if (child_wait_refcount++ > 0) {
+ child_wait_switch_ioloop();
+ return;
+ }
+
+ hash_table_create_direct(&child_pids, default_pool, 0);
+
+ lib_signals_set_handler(SIGCHLD, LIBSIG_FLAGS_SAFE,
+ sigchld_handler, NULL);
+}
+
+void child_wait_deinit(void)
+{
+ struct hash_iterate_context *iter;
+ void *key;
+ struct child_wait *value;
+
+ i_assert(child_wait_refcount > 0);
+ if (--child_wait_refcount > 0) {
+ child_wait_switch_ioloop();
+ return;
+ }
+
+ lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL);
+
+ iter = hash_table_iterate_init(child_pids);
+ while (hash_table_iterate(iter, child_pids, &key, &value))
+ i_free(value);
+ hash_table_iterate_deinit(&iter);
+
+ hash_table_destroy(&child_pids);
+}