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