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