summaryrefslogtreecommitdiffstats
path: root/src/lib/child-wait.c
blob: 024d81d600851551389aafc825548c51329cd589 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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);
}