diff options
Diffstat (limited to 'src/lib/child-wait.c')
-rw-r--r-- | src/lib/child-wait.c | 139 |
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); +} |