1 /*	$OpenBSD$	*/
2 /*
3  * Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 /*
18  * non-blocking file i/o module: each file can be read or written (or
19  * both). To achieve non-blocking io, we simply use the poll() syscall
20  * in an event loop and dispatch events to sub-modules.
21  *
22  * the module also provides trivial timeout implementation,
23  * derived from:
24  *
25  * 	anoncvs@moule.caoua.org:/midish
26  *
27  *		midish/timo.c rev 1.18
28  * 		midish/mdep.c rev 1.71
29  *
30  * A timeout is used to schedule the call of a routine (the callback)
31  * there is a global list of timeouts that is processed inside the
32  * event loop. Timeouts work as follows:
33  *
34  *	first the timo structure must be initialized with timo_set()
35  *
36  *	then the timeout is scheduled (only once) with timo_add()
37  *
38  *	if the timeout expires, the call-back is called; then it can
39  *	be scheduled again if needed. It's OK to reschedule it again
40  *	from the callback
41  *
42  *	the timeout can be aborted with timo_del(), it is OK to try to
43  *	abort a timout that has expired
44  *
45  */
46 
47 #include <sys/types.h>
48 
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <poll.h>
52 #include <signal.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <time.h>
56 #include "bsd-compat.h"
57 
58 #include "file.h"
59 #include "utils.h"
60 
61 #define MAXFDS 100
62 #define TIMER_MSEC 5
63 
64 void timo_update(unsigned int);
65 void timo_init(void);
66 void timo_done(void);
67 void file_process(struct file *, struct pollfd *);
68 
69 struct timespec file_ts;
70 struct file *file_list;
71 struct timo *timo_queue;
72 unsigned int timo_abstime;
73 int file_slowaccept = 0, file_nfds;
74 #ifdef DEBUG
75 long long file_wtime, file_utime;
76 #endif
77 
78 /*
79  * initialise a timeout structure, arguments are callback and argument
80  * that will be passed to the callback
81  */
82 void
83 timo_set(struct timo *o, void (*cb)(void *), void *arg)
84 {
85 	o->cb = cb;
86 	o->arg = arg;
87 	o->set = 0;
88 }
89 
90 /*
91  * schedule the callback in 'delta' 24-th of microseconds. The timeout
92  * must not be already scheduled
93  */
94 void
95 timo_add(struct timo *o, unsigned int delta)
96 {
97 	struct timo **i;
98 	unsigned int val;
99 	int diff;
100 
101 #ifdef DEBUG
102 	if (o->set) {
103 		log_puts("timo_add: already set\n");
104 		panic();
105 	}
106 	if (delta == 0) {
107 		log_puts("timo_add: zero timeout is evil\n");
108 		panic();
109 	}
110 #endif
111 	val = timo_abstime + delta;
112 	for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
113 		diff = (*i)->val - val;
114 		if (diff > 0) {
115 			break;
116 		}
117 	}
118 	o->set = 1;
119 	o->val = val;
120 	o->next = *i;
121 	*i = o;
122 }
123 
124 /*
125  * abort a scheduled timeout
126  */
127 void
128 timo_del(struct timo *o)
129 {
130 	struct timo **i;
131 
132 	for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
133 		if (*i == o) {
134 			*i = o->next;
135 			o->set = 0;
136 			return;
137 		}
138 	}
139 #ifdef DEBUG
140 	if (log_level >= 4)
141 		log_puts("timo_del: not found\n");
142 #endif
143 }
144 
145 /*
146  * routine to be called by the timer when 'delta' 24-th of microsecond
147  * elapsed. This routine updates time referece used by timeouts and
148  * calls expired timeouts
149  */
150 void
151 timo_update(unsigned int delta)
152 {
153 	struct timo *to;
154 	int diff;
155 
156 	/*
157 	 * update time reference
158 	 */
159 	timo_abstime += delta;
160 
161 	/*
162 	 * remove from the queue and run expired timeouts
163 	 */
164 	while (timo_queue != NULL) {
165 		/*
166 		 * there is no overflow here because + and - are
167 		 * modulo 2^32, they are the same for both signed and
168 		 * unsigned integers
169 		 */
170 		diff = timo_queue->val - timo_abstime;
171 		if (diff > 0)
172 			break;
173 		to = timo_queue;
174 		timo_queue = to->next;
175 		to->set = 0;
176 		to->cb(to->arg);
177 	}
178 }
179 
180 /*
181  * initialize timeout queue
182  */
183 void
184 timo_init(void)
185 {
186 	timo_queue = NULL;
187 	timo_abstime = 0;
188 }
189 
190 /*
191  * destroy timeout queue
192  */
193 void
194 timo_done(void)
195 {
196 #ifdef DEBUG
197 	if (timo_queue != NULL) {
198 		log_puts("timo_done: timo_queue not empty!\n");
199 		panic();
200 	}
201 #endif
202 	timo_queue = (struct timo *)0xdeadbeef;
203 }
204 
205 #ifdef DEBUG
206 void
207 file_log(struct file *f)
208 {
209 	static char *states[] = { "ini", "zom" };
210 
211 	log_puts(f->ops->name);
212 	if (log_level >= 3) {
213 		log_puts("(");
214 		log_puts(f->name);
215 		log_puts("|");
216 		log_puts(states[f->state]);
217 		log_puts(")");
218 	}
219 }
220 #endif
221 
222 struct file *
223 file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds)
224 {
225 	struct file *f;
226 
227 	if (file_nfds + nfds > MAXFDS) {
228 #ifdef DEBUG
229 		if (log_level >= 1) {
230 			log_puts(name);
231 			log_puts(": too many polled files\n");
232 		}
233 #endif
234 		return NULL;
235 	}
236 	f = xmalloc(sizeof(struct file));
237 	f->max_nfds = nfds;
238 	f->nfds = 0;
239 	f->ops = ops;
240 	f->arg = arg;
241 	f->name = name;
242 	f->state = FILE_INIT;
243 	f->next = file_list;
244 	file_list = f;
245 #ifdef DEBUG
246 	if (log_level >= 3) {
247 		file_log(f);
248 		log_puts(": created\n");
249 	}
250 #endif
251 	file_nfds += f->max_nfds;
252 	return f;
253 }
254 
255 void
256 file_del(struct file *f)
257 {
258 #ifdef DEBUG
259 	if (f->state == FILE_ZOMB) {
260 		log_puts("bad state in file_del()\n");
261 		panic();
262 	}
263 #endif
264 	file_nfds -= f->max_nfds;
265 	f->state = FILE_ZOMB;
266 #ifdef DEBUG
267 	if (log_level >= 3) {
268 		file_log(f);
269 		log_puts(": destroyed\n");
270 	}
271 #endif
272 }
273 
274 void
275 file_process(struct file *f, struct pollfd *pfd)
276 {
277 	int revents;
278 #ifdef DEBUG
279 	struct timespec ts0, ts1;
280 	long us;
281 #endif
282 
283 #ifdef DEBUG
284 	if (log_level >= 3)
285 		clock_gettime(CLOCK_UPTIME, &ts0);
286 #endif
287 	revents = (f->state != FILE_ZOMB) ?
288 	    f->ops->revents(f->arg, pfd) : 0;
289 	if ((revents & POLLHUP) && (f->state != FILE_ZOMB))
290 		f->ops->hup(f->arg);
291 	if ((revents & POLLIN) && (f->state != FILE_ZOMB))
292 		f->ops->in(f->arg);
293 	if ((revents & POLLOUT) && (f->state != FILE_ZOMB))
294 		f->ops->out(f->arg);
295 #ifdef DEBUG
296 	if (log_level >= 3) {
297 		clock_gettime(CLOCK_UPTIME, &ts1);
298 		us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
299 		us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
300 		if (log_level >= 4 || us >= 5000) {
301 			file_log(f);
302 			log_puts(": processed in ");
303 			log_putu(us);
304 			log_puts("us\n");
305 		}
306 	}
307 #endif
308 }
309 
310 int
311 file_poll(void)
312 {
313 	struct pollfd pfds[MAXFDS], *pfd;
314 	struct file *f, **pf;
315 	struct timespec ts;
316 #ifdef DEBUG
317 	struct timespec sleepts;
318 	int i;
319 #endif
320 	long long delta_nsec;
321 	int nfds, res, timo;
322 
323 	/*
324 	 * cleanup zombies
325 	 */
326 	pf = &file_list;
327 	while ((f = *pf) != NULL) {
328 		if (f->state == FILE_ZOMB) {
329 			*pf = f->next;
330 			xfree(f);
331 		} else
332 			pf = &f->next;
333 	}
334 
335 	if (file_list == NULL && timo_queue == NULL) {
336 #ifdef DEBUG
337 		if (log_level >= 3)
338 			log_puts("nothing to do...\n");
339 #endif
340 		return 0;
341 	}
342 
343 	/*
344 	 * fill pollfd structures
345 	 */
346 	nfds = 0;
347 	for (f = file_list; f != NULL; f = f->next) {
348 		f->nfds = f->ops->pollfd(f->arg, pfds + nfds);
349 		if (f->nfds == 0)
350 			continue;
351 		nfds += f->nfds;
352 	}
353 #ifdef DEBUG
354 	if (log_level >= 4) {
355 		log_puts("poll:");
356 		pfd = pfds;
357 		for (f = file_list; f != NULL; f = f->next) {
358 			log_puts(" ");
359 			log_puts(f->ops->name);
360 			log_puts(":");
361 			for (i = 0; i < f->nfds; i++) {
362 				log_puts(" ");
363 				log_putx(pfd->events);
364 				pfd++;
365 			}
366 		}
367 		log_puts("\n");
368 	}
369 #endif
370 
371 	/*
372 	 * process files that do not rely on poll
373 	 */
374 	for (f = file_list; f != NULL; f = f->next) {
375 		if (f->nfds > 0)
376 			continue;
377 		file_process(f, NULL);
378 	}
379 
380 	/*
381 	 * Sleep. Calculate the number of milliseconds poll(2) must
382 	 * wait before the timo_update() needs to be called. If there are
383 	 * no timeouts scheduled, then call poll(2) with infinite
384 	 * timeout (i.e -1).
385 	 */
386 #ifdef DEBUG
387 	clock_gettime(CLOCK_UPTIME, &sleepts);
388 	file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec);
389 	file_utime += sleepts.tv_nsec - file_ts.tv_nsec;
390 #endif
391 	if (timo_queue != NULL) {
392 		timo = ((int)timo_queue->val - (int)timo_abstime) / 1000;
393 		if (timo < TIMER_MSEC)
394 			timo = TIMER_MSEC;
395 	} else
396 		timo = -1;
397 	log_flush();
398 	res = poll(pfds, nfds, timo);
399 	if (res == -1) {
400 		if (errno != EINTR) {
401 			log_puts("poll failed");
402 			panic();
403 		}
404 		return 1;
405 	}
406 
407 	/*
408 	 * run timeouts
409 	 */
410 	clock_gettime(CLOCK_UPTIME, &ts);
411 #ifdef DEBUG
412 	file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec);
413 	file_wtime += ts.tv_nsec - sleepts.tv_nsec;
414 #endif
415 	if (timo_queue) {
416 		delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec);
417 		delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
418 		if (delta_nsec >= 0 && delta_nsec < 60000000000LL)
419 			timo_update(delta_nsec / 1000);
420 		else {
421 			if (log_level >= 2)
422 				log_puts("out-of-bounds clock delta\n");
423 		}
424 	}
425 	file_ts = ts;
426 
427 	/*
428 	 * process files that rely on poll
429 	 */
430 	pfd = pfds;
431 	for (f = file_list; f != NULL; f = f->next) {
432 		if (f->nfds == 0)
433 			continue;
434 		file_process(f, pfd);
435 		pfd += f->nfds;
436 	}
437 	return 1;
438 }
439 
440 void
441 filelist_init(void)
442 {
443 	sigset_t set;
444 
445 	if (clock_gettime(CLOCK_UPTIME, &file_ts) == -1) {
446 		log_puts("filelist_init: CLOCK_UPTIME unsupported\n");
447 		panic();
448 	}
449 	sigemptyset(&set);
450 	sigaddset(&set, SIGPIPE);
451 	sigprocmask(SIG_BLOCK, &set, NULL);
452 	file_list = NULL;
453 	log_sync = 0;
454 	timo_init();
455 }
456 
457 void
458 filelist_done(void)
459 {
460 #ifdef DEBUG
461 	struct file *f;
462 
463 	if (file_list != NULL) {
464 		for (f = file_list; f != NULL; f = f->next) {
465 			file_log(f);
466 			log_puts(" not closed\n");
467 		}
468 		panic();
469 	}
470 	log_sync = 1;
471 	log_flush();
472 #endif
473 	timo_done();
474 }
475