1 /*
2  * Copyright (c) 2013-2014, Sony Mobile Communications Inc.
3  * Copyright (c) 2014, Courtney Cavin
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  - Redistributions of source code must retain the above copyright notice,
9  *  this list of conditions and the following disclaimer.
10  *
11  *  - Redistributions in binary form must reproduce the above copyright notice,
12  *  this list of conditions and the following disclaimer in the documentation
13  *  and/or other materials provided with the distribution.
14  *
15  *  - Neither the name of the organization nor the names of its contributors
16  *  may be used to endorse or promote products derived from this software
17  *  without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <poll.h>
36 
37 #include "list.h"
38 #include "waiter.h"
39 #include "util.h"
40 
41 struct pollset {
42 	int nfds;
43 	int cause;
44 };
45 
pollset_create(int count)46 static struct pollset *pollset_create(int count)
47 {
48 	struct pollset *ps;
49 
50 	ps = calloc(1, sizeof(*ps) + sizeof(struct pollfd) * count);
51 	if (ps == NULL)
52 		return NULL;
53 
54 	return ps;
55 }
56 
pollset_destroy(struct pollset * ps)57 static void pollset_destroy(struct pollset *ps)
58 {
59 	free(ps);
60 }
61 
pollset_reset(struct pollset * ps)62 static void pollset_reset(struct pollset *ps)
63 {
64 	ps->nfds = 0;
65 }
66 
pollset_add_fd(struct pollset * ps,int fd)67 static void pollset_add_fd(struct pollset *ps, int fd)
68 {
69 	struct pollfd *pfd = (struct pollfd *)(ps + 1);
70 	pfd[ps->nfds].fd = fd;
71 	pfd[ps->nfds].events = POLLERR | POLLIN;
72 	ps->nfds++;
73 }
74 
pollset_wait(struct pollset * ps,int ms)75 static int pollset_wait(struct pollset *ps, int ms)
76 {
77 	struct pollfd *pfd = (struct pollfd *)(ps + 1);
78 	int rc;
79 	int i;
80 
81 	rc = poll(pfd, ps->nfds, ms);
82 	if (rc <= 0)
83 		return rc;
84 
85 	ps->cause = -1;
86 	for (i = 0; i < ps->nfds; ++i) {
87 		if (pfd[i].revents & (POLLERR | POLLIN)) {
88 			ps->cause = i;
89 			break;
90 		}
91 	}
92 	return rc;
93 
94 }
95 
pollset_cause_fd(struct pollset * ps,int fd)96 static int pollset_cause_fd(struct pollset *ps, int fd)
97 {
98 	struct pollfd *pfd = (struct pollfd *)(ps + 1);
99 	return (ps->cause >= 0 && pfd[ps->cause].fd == fd);
100 }
101 
102 enum waiter_type {
103 	WATCH_TYPE_NULL,
104 	WATCH_TYPE_FD,
105 	WATCH_TYPE_TIMEOUT,
106 };
107 
108 struct waiter_ticket {
109 	enum waiter_type type;
110 	union {
111 		int filedes;
112 		unsigned int event;
113 		unsigned int interval;
114 	};
115 	struct {
116 		void (* fn)(void *data, struct waiter_ticket *);
117 		void *data;
118 	} callback;
119 
120 	uint64_t start;
121 	int updated;
122 	struct waiter *waiter;
123 	struct list_item list_item;
124 };
125 
126 struct waiter {
127 	struct list tickets;
128 	struct pollset *pollset;
129 	int count;
130 };
131 
waiter_create(void)132 struct waiter *waiter_create(void)
133 {
134 	struct waiter *w;
135 
136 	w = calloc(1, sizeof(*w));
137 	if (w == NULL)
138 		return NULL;
139 
140 	list_init(&w->tickets);
141 	return w;
142 }
143 
waiter_destroy(struct waiter * w)144 void waiter_destroy(struct waiter *w)
145 {
146 	struct waiter_ticket *ticket;
147 	struct list_item *safe;
148 	struct list_item *node;
149 
150 	list_for_each_safe(&w->tickets, node, safe) {
151 		ticket = list_entry(node, struct waiter_ticket, list_item);
152 		free(ticket);
153 	}
154 
155 	if (w->pollset)
156 		pollset_destroy(w->pollset);
157 	free(w);
158 }
159 
waiter_synchronize(struct waiter * w)160 void waiter_synchronize(struct waiter *w)
161 {
162 	struct waiter_ticket *oticket;
163 	struct waiter_ticket *ticket;
164 	struct list_item *node;
165 
166 	list_for_each(&w->tickets, node) {
167 		struct list_item *onode;
168 		ticket = list_entry(node, struct waiter_ticket, list_item);
169 
170 		if (ticket->type != WATCH_TYPE_TIMEOUT)
171 			continue;
172 
173 		list_for_each_after(node, onode) {
174 			oticket = list_entry(onode, struct waiter_ticket, list_item);
175 			if (oticket->type != WATCH_TYPE_TIMEOUT)
176 				continue;
177 
178 			if (oticket->interval == ticket->interval) {
179 				oticket->start = ticket->start;
180 				break;
181 			}
182 		}
183 	}
184 }
185 
waiter_wait(struct waiter * w)186 void waiter_wait(struct waiter *w)
187 {
188 	struct pollset *ps = w->pollset;
189 	struct waiter_ticket *ticket;
190 	struct list_item *node;
191 	uint64_t term_time;
192 	uint64_t now;
193 	int rc;
194 
195 	pollset_reset(ps);
196 
197 	term_time = (uint64_t)-1;
198 	list_for_each(&w->tickets, node) {
199 		ticket = list_entry(node, struct waiter_ticket, list_item);
200 		switch (ticket->type) {
201 		case WATCH_TYPE_TIMEOUT:
202 			if (ticket->start + ticket->interval < term_time)
203 				term_time = ticket->start + ticket->interval;
204 			break;
205 		case WATCH_TYPE_FD:
206 			pollset_add_fd(ps, ticket->filedes);
207 			break;
208 		case WATCH_TYPE_NULL:
209 			break;
210 		}
211 	}
212 
213 	if (term_time == (uint64_t)-1) { /* wait forever */
214 		rc = pollset_wait(ps, -1);
215 	} else {
216 		now = time_ms();
217 		if (now >= term_time) { /* already past timeout, skip poll */
218 			rc = 0;
219 		} else {
220 			uint64_t delta;
221 
222 			delta = term_time - now;
223 			if (delta > ((1u << 31) - 1))
224 				delta = ((1u << 31) - 1);
225 			rc = pollset_wait(ps, (int)delta);
226 		}
227 	}
228 
229 	if (rc < 0)
230 		return;
231 
232 	now = time_ms();
233 	list_for_each(&w->tickets, node) {
234 		int fresh = 0;
235 
236 		ticket = list_entry(node, struct waiter_ticket, list_item);
237 		switch (ticket->type) {
238 		case WATCH_TYPE_TIMEOUT:
239 			if (now >= ticket->start + ticket->interval) {
240 				ticket->start = now;
241 				fresh = !ticket->updated;
242 			}
243 			break;
244 		case WATCH_TYPE_FD:
245 			if (rc == 0) /* timed-out */
246 				break;
247 			if (pollset_cause_fd(ps, ticket->filedes))
248 				fresh = !ticket->updated;
249 			break;
250 		case WATCH_TYPE_NULL:
251 			break;
252 		}
253 		if (fresh) {
254 			ticket->updated = 1;
255 			if (ticket->callback.fn)
256 				(* ticket->callback.fn)(
257 						ticket->callback.data,
258 						ticket
259 				);
260 		}
261 	}
262 }
263 
waiter_wait_timeout(struct waiter * w,unsigned int ms)264 int waiter_wait_timeout(struct waiter *w, unsigned int ms)
265 {
266 	struct waiter_ticket ticket;
267 	int rc;
268 
269 	memset(&ticket, 0, sizeof(ticket));
270 	waiter_ticket_set_timeout(&ticket, ms);
271 	list_append(&w->tickets, &ticket.list_item);
272 	w->count++;
273 
274 	waiter_wait(w);
275 	rc = waiter_ticket_check(&ticket);
276 
277 	list_remove(&w->tickets, &ticket.list_item);
278 	w->count--;
279 
280 	return -!rc;
281 }
282 
waiter_ticket_set_null(struct waiter_ticket * ticket)283 void waiter_ticket_set_null(struct waiter_ticket *ticket)
284 {
285 	ticket->type = WATCH_TYPE_NULL;
286 }
287 
waiter_ticket_set_fd(struct waiter_ticket * ticket,int fd)288 void waiter_ticket_set_fd(struct waiter_ticket *ticket, int fd)
289 {
290 	ticket->type = WATCH_TYPE_FD;
291 	ticket->filedes = fd;
292 }
293 
waiter_ticket_set_timeout(struct waiter_ticket * ticket,unsigned int ms)294 void waiter_ticket_set_timeout(struct waiter_ticket *ticket, unsigned int ms)
295 {
296 	ticket->type = WATCH_TYPE_TIMEOUT;
297 	ticket->interval = ms;
298 	ticket->start = time_ms();
299 }
300 
waiter_add_null(struct waiter * w)301 struct waiter_ticket *waiter_add_null(struct waiter *w)
302 {
303 	struct waiter_ticket *ticket;
304 
305 	ticket = calloc(1, sizeof(*ticket));
306 	if (ticket == NULL)
307 		return NULL;
308 	ticket->waiter = w;
309 
310 	list_append(&w->tickets, &ticket->list_item);
311 	if ((w->count % 32) == 0) {
312 		if (w->pollset)
313 			pollset_destroy(w->pollset);
314 		w->pollset = pollset_create(w->count + 33);
315 		if (w->pollset == NULL)
316 			return NULL;
317 	}
318 	w->count++;
319 
320 	waiter_ticket_set_null(ticket);
321 
322 	return ticket;
323 }
324 
waiter_add_fd(struct waiter * w,int fd)325 struct waiter_ticket *waiter_add_fd(struct waiter *w, int fd)
326 {
327 	struct waiter_ticket *ticket;
328 
329 	ticket = waiter_add_null(w);
330 	if (ticket == NULL)
331 		return NULL;
332 
333 	waiter_ticket_set_fd(ticket, fd);
334 
335 	return ticket;
336 }
337 
waiter_add_timeout(struct waiter * w,unsigned int ms)338 struct waiter_ticket *waiter_add_timeout(struct waiter *w, unsigned int ms)
339 {
340 	struct waiter_ticket *ticket;
341 
342 	ticket = waiter_add_null(w);
343 	if (ticket == NULL)
344 		return NULL;
345 
346 	waiter_ticket_set_timeout(ticket, ms);
347 
348 	return ticket;
349 }
350 
waiter_ticket_delete(struct waiter_ticket * ticket)351 void waiter_ticket_delete(struct waiter_ticket *ticket)
352 {
353 	struct waiter *w = ticket->waiter;
354 	list_remove(&w->tickets, &ticket->list_item);
355 	w->count--;
356 	free(ticket);
357 }
358 
waiter_ticket_callback(struct waiter_ticket * ticket,waiter_ticket_cb_t cb_fn,void * data)359 void waiter_ticket_callback(struct waiter_ticket *ticket, waiter_ticket_cb_t cb_fn, void *data)
360 {
361 	ticket->callback.fn = cb_fn;
362 	ticket->callback.data = data;
363 }
364 
waiter_ticket_check(const struct waiter_ticket * ticket)365 int waiter_ticket_check(const struct waiter_ticket *ticket)
366 {
367 	return -(ticket->updated == 0);
368 }
369 
waiter_ticket_clear(struct waiter_ticket * ticket)370 int waiter_ticket_clear(struct waiter_ticket *ticket)
371 {
372 	int ret;
373 
374 	ret = waiter_ticket_check(ticket);
375 	ticket->updated = 0;
376 
377 	return ret;
378 }
379