lib/process: Add helper for check process clean exit
[petitboot] / lib / waiter / waiter.c
1
2 #include <poll.h>
3 #include <stdbool.h>
4 #include <string.h>
5 #include <assert.h>
6 #include <errno.h>
7 #include <sys/time.h>
8
9 #include <talloc/talloc.h>
10 #include <list/list.h>
11
12 #include "waiter.h"
13
14 struct waiter {
15         struct waitset  *set;
16         enum {
17                 WAITER_IO,
18                 WAITER_TIME,
19         } type;
20         union {
21                 struct {
22                         int     fd;
23                         int     events;
24                 } io;
25                 struct timeval  timeout;
26         };
27         waiter_cb       callback;
28         void            *arg;
29
30         bool                    active;
31         struct list_item        list;
32 };
33
34 struct waitset {
35         struct waiter   **waiters;
36         int             n_waiters;
37         bool            waiters_changed;
38
39         struct timeval  next_timeout;
40
41         /* These are kept consistent over each call to waiter_poll, as
42          * set->waiters may be updated (by waiters' callbacks calling
43          * waiter_register or waiter_remove) during iteration. */
44         struct pollfd   *pollfds;
45         int             n_pollfds;
46         struct waiter   **io_waiters;
47         int             n_io_waiters;
48         struct waiter   **time_waiters;
49         int             n_time_waiters;
50
51         struct list     free_list;
52 };
53
54 struct waitset *waitset_create(void *ctx)
55 {
56         struct waitset *set = talloc_zero(ctx, struct waitset);
57         list_init(&set->free_list);
58         return set;
59 }
60
61 static struct waiter *waiter_new(struct waitset *set)
62 {
63         struct waiter **waiters, *waiter;
64
65         waiter = talloc(set, struct waiter);
66         if (!waiter)
67                 return NULL;
68
69         waiters = talloc_realloc(set, set->waiters,
70                         struct waiter *, set->n_waiters + 1);
71
72         if (!waiters) {
73                 talloc_free(waiter);
74                 return NULL;
75         }
76
77         set->waiters_changed = true;
78         set->waiters = waiters;
79         set->n_waiters++;
80
81         set->waiters[set->n_waiters - 1] = waiter;
82         waiter->active = true;
83         return waiter;
84 }
85
86 struct waiter *waiter_register_io(struct waitset *set, int fd, int events,
87                 waiter_cb callback, void *arg)
88 {
89         struct waiter *waiter = waiter_new(set);
90
91         waiter->type = WAITER_IO;
92         waiter->set = set;
93         waiter->io.fd = fd;
94         waiter->io.events = events;
95         waiter->callback = callback;
96         waiter->arg = arg;
97
98         return waiter;
99 }
100
101 struct waiter *waiter_register_timeout(struct waitset *set, int delay_ms,
102                 waiter_cb callback, void *arg)
103 {
104         struct waiter *waiter = waiter_new(set);
105         struct timeval now, delay;
106
107         delay.tv_sec = delay_ms / 1000;
108         delay.tv_usec = 1000 * (delay_ms % 1000);
109
110         gettimeofday(&now, NULL);
111
112         timeradd(&now, &delay, &waiter->timeout);
113
114         waiter->type = WAITER_TIME;
115         waiter->set = set;
116         waiter->callback = callback;
117         waiter->arg = arg;
118
119         return waiter;
120 }
121
122 void waiter_remove(struct waiter *waiter)
123 {
124         struct waitset *set = waiter->set;
125         int i;
126
127         for (i = 0; i < set->n_waiters; i++)
128                 if (set->waiters[i] == waiter)
129                         break;
130
131         assert(i < set->n_waiters);
132
133         set->n_waiters--;
134         memmove(&set->waiters[i], &set->waiters[i+1],
135                 (set->n_waiters - i) * sizeof(set->waiters[0]));
136
137         set->waiters = talloc_realloc(set, set->waiters,
138                         struct waiter *, set->n_waiters);
139         set->waiters_changed = true;
140
141         waiter->active = false;
142         list_add(&set->free_list, &waiter->list);
143 }
144
145 static void update_waiters(struct waitset *set)
146 {
147         int n_io, n_time, i_io, i_time, i;
148
149         if (!set->waiters_changed)
150                 return;
151
152         n_io = n_time = 0;
153
154         for (i = 0; i < set->n_waiters; i++) {
155                 if (set->waiters[i]->type == WAITER_IO)
156                         n_io++;
157                 else if (set->waiters[i]->type == WAITER_TIME)
158                         n_time++;
159         }
160
161         /* realloc if counts have changed */
162         if (set->n_io_waiters != n_io) {
163                 set->io_waiters = talloc_realloc(set, set->io_waiters,
164                                 struct waiter *, n_io);
165                 set->pollfds = talloc_realloc(set, set->pollfds,
166                                 struct pollfd, n_io);
167                 set->n_io_waiters = n_io;
168         }
169         if (set->n_time_waiters != n_time) {
170                 set->time_waiters = talloc_realloc(set, set->time_waiters,
171                                 struct waiter *, n_time);
172                 set->n_time_waiters = n_time;
173         }
174
175         i_io = 0;
176         i_time = 0;
177
178         timerclear(&set->next_timeout);
179
180         for (i = 0; i < set->n_waiters; i++) {
181                 struct waiter *waiter = set->waiters[i];
182
183                 /* IO waiters: copy to io_waiters, populate pollfds */
184                 if (waiter->type == WAITER_IO) {
185                         set->pollfds[i_io].fd = waiter->io.fd;
186                         set->pollfds[i_io].events = waiter->io.events;
187                         set->io_waiters[i_io] = waiter;
188                         i_io++;
189                 }
190
191                 /* time waiters: copy to time_waiters, calculate next expiry */
192                 if (waiter->type == WAITER_TIME) {
193                         if (!timerisset(&set->next_timeout) ||
194                                         timercmp(&waiter->timeout,
195                                                 &set->next_timeout, <))
196                                 set->next_timeout = waiter->timeout;
197
198                         set->time_waiters[i_time] = waiter;
199                         i_time++;
200                 }
201         }
202 }
203
204 int waiter_poll(struct waitset *set)
205 {
206         struct timeval now, timeout;
207         struct waiter *waiter, *tmp;
208         int timeout_ms;
209         int i, rc;
210
211         /* If the waiters have been updated, we need to update our
212          * consistent copy */
213         update_waiters(set);
214
215         if (timerisset(&set->next_timeout)) {
216                 gettimeofday(&now, NULL);
217                 timersub(&set->next_timeout, &now, &timeout);
218                 timeout_ms = timeout.tv_sec * 1000 +
219                                 timeout.tv_usec / 1000;
220                 if (timeout_ms < 0)
221                         timeout_ms = 0;
222         } else {
223                 timeout_ms = -1;
224         }
225
226         rc = poll(set->pollfds, set->n_io_waiters, timeout_ms);
227
228         if (rc < 0) {
229                 if (errno == EINTR)
230                         rc = 0;
231                 goto out;
232         }
233
234         for (i = 0; i < set->n_io_waiters; i++) {
235                 struct waiter *waiter = set->io_waiters[i];
236
237                 if (!waiter->active)
238                         continue;
239
240                 if (!set->pollfds[i].revents)
241                         continue;
242                 rc = waiter->callback(waiter->arg);
243
244                 if (rc)
245                         waiter_remove(waiter);
246         }
247
248         if (set->n_time_waiters > 0)
249                 gettimeofday(&now, NULL);
250
251         for (i = 0; i < set->n_time_waiters; i++) {
252                 struct waiter *waiter = set->time_waiters[i];
253
254                 if (!waiter->active)
255                         continue;
256
257                 if (timercmp(&waiter->timeout, &now, >))
258                         continue;
259
260                 waiter->callback(waiter->arg);
261
262                 waiter_remove(waiter);
263         }
264
265         rc = 0;
266
267 out:
268         /* free any waiters that have been removed */
269         list_for_each_entry_safe(&set->free_list, waiter, tmp, list)
270                 talloc_free(waiter);
271         list_init(&set->free_list);
272
273         return rc;
274 }