xref: /openbsd-src/usr.bin/sndiod/file.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: file.c,v 1.5 2014/03/17 17:17:01 ratchov Exp $	*/
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/time.h>
48 #include <sys/types.h>
49 
50 #include <err.h>
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <poll.h>
54 #include <signal.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <time.h>
58 
59 #include "file.h"
60 #include "utils.h"
61 
62 #define MAXFDS 100
63 #define TIMER_USEC 10000
64 
65 void timo_update(unsigned int);
66 void timo_init(void);
67 void timo_done(void);
68 void file_sigalrm(int);
69 
70 struct timespec file_ts;
71 struct file *file_list;
72 struct timo *timo_queue;
73 unsigned int timo_abstime;
74 int file_slowaccept = 0, file_nfds;
75 #ifdef DEBUG
76 long long file_wtime, file_utime;
77 #endif
78 
79 /*
80  * initialise a timeout structure, arguments are callback and argument
81  * that will be passed to the callback
82  */
83 void
84 timo_set(struct timo *o, void (*cb)(void *), void *arg)
85 {
86 	o->cb = cb;
87 	o->arg = arg;
88 	o->set = 0;
89 }
90 
91 /*
92  * schedule the callback in 'delta' 24-th of microseconds. The timeout
93  * must not be already scheduled
94  */
95 void
96 timo_add(struct timo *o, unsigned int delta)
97 {
98 	struct timo **i;
99 	unsigned int val;
100 	int diff;
101 
102 #ifdef DEBUG
103 	if (o->set) {
104 		log_puts("timo_add: already set\n");
105 		panic();
106 	}
107 	if (delta == 0) {
108 		log_puts("timo_add: zero timeout is evil\n");
109 		panic();
110 	}
111 #endif
112 	val = timo_abstime + delta;
113 	for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
114 		diff = (*i)->val - val;
115 		if (diff > 0) {
116 			break;
117 		}
118 	}
119 	o->set = 1;
120 	o->val = val;
121 	o->next = *i;
122 	*i = o;
123 }
124 
125 /*
126  * abort a scheduled timeout
127  */
128 void
129 timo_del(struct timo *o)
130 {
131 	struct timo **i;
132 
133 	for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
134 		if (*i == o) {
135 			*i = o->next;
136 			o->set = 0;
137 			return;
138 		}
139 	}
140 #ifdef DEBUG
141 	if (log_level >= 4)
142 		log_puts("timo_del: not found\n");
143 #endif
144 }
145 
146 /*
147  * routine to be called by the timer when 'delta' 24-th of microsecond
148  * elapsed. This routine updates time referece used by timeouts and
149  * calls expired timeouts
150  */
151 void
152 timo_update(unsigned int delta)
153 {
154 	struct timo *to;
155 	int diff;
156 
157 	/*
158 	 * update time reference
159 	 */
160 	timo_abstime += delta;
161 
162 	/*
163 	 * remove from the queue and run expired timeouts
164 	 */
165 	while (timo_queue != NULL) {
166 		/*
167 		 * there is no overflow here because + and - are
168 		 * modulo 2^32, they are the same for both signed and
169 		 * unsigned integers
170 		 */
171 		diff = timo_queue->val - timo_abstime;
172 		if (diff > 0)
173 			break;
174 		to = timo_queue;
175 		timo_queue = to->next;
176 		to->set = 0;
177 		to->cb(to->arg);
178 	}
179 }
180 
181 /*
182  * initialize timeout queue
183  */
184 void
185 timo_init(void)
186 {
187 	timo_queue = NULL;
188 	timo_abstime = 0;
189 }
190 
191 /*
192  * destroy timeout queue
193  */
194 void
195 timo_done(void)
196 {
197 #ifdef DEBUG
198 	if (timo_queue != NULL) {
199 		log_puts("timo_done: timo_queue not empty!\n");
200 		panic();
201 	}
202 #endif
203 	timo_queue = (struct timo *)0xdeadbeef;
204 }
205 
206 #ifdef DEBUG
207 void
208 file_log(struct file *f)
209 {
210 	static char *states[] = { "ini", "zom" };
211 
212 	log_puts(f->ops->name);
213 	if (log_level >= 3) {
214 		log_puts("(");
215 		log_puts(f->name);
216 		log_puts("|");
217 		log_puts(states[f->state]);
218 		log_puts(")");
219 	}
220 }
221 #endif
222 
223 struct file *
224 file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds)
225 {
226 	struct file *f;
227 
228 	if (file_nfds + nfds > MAXFDS) {
229 #ifdef DEBUG
230 		if (log_level >= 1) {
231 			log_puts(name);
232 			log_puts(": too many polled files\n");
233 		}
234 #endif
235 		return NULL;
236 	}
237 	f = xmalloc(sizeof(struct file));
238 	f->nfds = nfds;
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->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->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 int
275 file_poll(void)
276 {
277 	nfds_t nfds, n;
278 	struct pollfd pfds[MAXFDS];
279 	struct file *f, **pf;
280 	struct timespec ts;
281 #ifdef DEBUG
282 	struct timespec sleepts;
283 	struct timespec ts0, ts1;
284 	long us;
285 	int i;
286 #endif
287 	long long delta_nsec;
288 	int revents, res;
289 
290 	/*
291 	 * cleanup zombies
292 	 */
293 	pf = &file_list;
294 	while ((f = *pf) != NULL) {
295 		if (f->state == FILE_ZOMB) {
296 			*pf = f->next;
297 			xfree(f);
298 		} else
299 			pf = &f->next;
300 	}
301 
302 	if (file_list == NULL && timo_queue == NULL) {
303 #ifdef DEBUG
304 		if (log_level >= 3)
305 			log_puts("nothing to do...\n");
306 #endif
307 		return 0;
308 	}
309 
310 	log_flush();
311 #ifdef DEBUG
312 	if (log_level >= 4)
313 		log_puts("poll:");
314 #endif
315 	nfds = 0;
316 	for (f = file_list; f != NULL; f = f->next) {
317 #ifdef DEBUG
318 		if (log_level >= 4) {
319 			log_puts(" ");
320 			file_log(f);
321 		}
322 #endif
323 		n = f->ops->pollfd(f->arg, pfds + nfds);
324 		if (n == 0) {
325 			f->pfd = NULL;
326 			continue;
327 		}
328 		f->pfd = pfds + nfds;
329 		nfds += n;
330 #ifdef DEBUG
331 		if (log_level >= 4) {
332 			log_puts("=");
333 			for (i = 0; i < n; i++) {
334 				if (i > 0)
335 					log_puts(",");
336 				log_putx(f->pfd[i].events);
337 			}
338 		}
339 #endif
340 	}
341 #ifdef DEBUG
342 	if (log_level >= 4)
343 		log_puts("\n");
344 #endif
345 
346 #ifdef DEBUG
347 	clock_gettime(CLOCK_MONOTONIC, &sleepts);
348 	file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec);
349 	file_utime += sleepts.tv_nsec - file_ts.tv_nsec;
350 #endif
351 	res = poll(pfds, nfds, -1);
352 	if (res < 0 && errno != EINTR)
353 		err(1, "poll");
354 #ifdef DEBUG
355 	if (log_level >= 4) {
356 		log_puts("poll: return:");
357 		for (i = 0; i < nfds; i++) {
358 			log_puts(" ");
359 			log_putx(pfds[i].revents);
360 		}
361 		log_puts("\n");
362 	}
363 #endif
364 	clock_gettime(CLOCK_MONOTONIC, &ts);
365 #ifdef DEBUG
366 	file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec);
367 	file_wtime += ts.tv_nsec - sleepts.tv_nsec;
368 #endif
369 	delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec);
370 	delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
371 #ifdef DEBUG
372 	if (delta_nsec < 0)
373 		log_puts("file_poll: negative time interval\n");
374 #endif
375 	file_ts = ts;
376 	if (delta_nsec >= 0 && delta_nsec < 1000000000LL)
377 		timo_update(delta_nsec / 1000);
378 	else {
379 		if (log_level >= 2)
380 			log_puts("ignored huge clock delta\n");
381 	}
382 	if (res <= 0)
383 		return 1;
384 
385 	for (f = file_list; f != NULL; f = f->next) {
386 		if (f->pfd == NULL)
387 			continue;
388 #ifdef DEBUG
389 		clock_gettime(CLOCK_MONOTONIC, &ts0);
390 #endif
391 		revents = (f->state != FILE_ZOMB) ?
392 		    f->ops->revents(f->arg, f->pfd) : 0;
393 		if ((revents & POLLHUP) && (f->state != FILE_ZOMB))
394 			f->ops->hup(f->arg);
395 		if ((revents & POLLIN) && (f->state != FILE_ZOMB))
396 			f->ops->in(f->arg);
397 		if ((revents & POLLOUT) && (f->state != FILE_ZOMB))
398 			f->ops->out(f->arg);
399 #ifdef DEBUG
400 		clock_gettime(CLOCK_MONOTONIC, &ts1);
401 		us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
402 		us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
403 		if (log_level >= 4 || (log_level >= 3 && us >= 5000)) {
404 			file_log(f);
405 			log_puts(": processed in ");
406 			log_putu(us);
407 			log_puts("us\n");
408 		}
409 #endif
410 	}
411 	return 1;
412 }
413 
414 /*
415  * handler for SIGALRM, invoked periodically
416  */
417 void
418 file_sigalrm(int i)
419 {
420 	/* nothing to do, we only want poll() to return EINTR */
421 }
422 
423 
424 void
425 filelist_init(void)
426 {
427 	static struct sigaction sa;
428 	struct itimerval it;
429 	sigset_t set;
430 
431 	sigemptyset(&set);
432 	(void)sigaddset(&set, SIGPIPE);
433 	if (sigprocmask(SIG_BLOCK, &set, NULL))
434 		err(1, "sigprocmask");
435 	file_list = NULL;
436 	if (clock_gettime(CLOCK_MONOTONIC, &file_ts) < 0) {
437 		perror("clock_gettime");
438 		exit(1);
439 	}
440         sa.sa_flags = SA_RESTART;
441         sa.sa_handler = file_sigalrm;
442         sigfillset(&sa.sa_mask);
443         if (sigaction(SIGALRM, &sa, NULL) < 0) {
444 		perror("sigaction");
445 		exit(1);
446 	}
447 	it.it_interval.tv_sec = 0;
448 	it.it_interval.tv_usec = TIMER_USEC;
449 	it.it_value.tv_sec = 0;
450 	it.it_value.tv_usec = TIMER_USEC;
451 	if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
452 		perror("setitimer");
453 		exit(1);
454 	}
455 	log_sync = 0;
456 	timo_init();
457 }
458 
459 void
460 filelist_done(void)
461 {
462 	struct itimerval it;
463 #ifdef DEBUG
464 	struct file *f;
465 
466 	if (file_list != NULL) {
467 		for (f = file_list; f != NULL; f = f->next) {
468 			file_log(f);
469 			log_puts(" not closed\n");
470 		}
471 		panic();
472 	}
473 	log_sync = 1;
474 	log_flush();
475 #endif
476 	timerclear(&it.it_value);
477 	timerclear(&it.it_interval);
478 	if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
479 		perror("setitimer");
480 		exit(1);
481 	}
482 	timo_done();
483 }
484