Annotation of src/usr.bin/sndiod/file.c, Revision 1.18
1.15 ratchov 1: /* $OpenBSD: file.c,v 1.14 2015/08/11 16:49:50 ratchov Exp $ */
1.1 ratchov 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
1.7 ratchov 61: #define TIMER_MSEC 5
1.4 ratchov 62:
63: void timo_update(unsigned int);
64: void timo_init(void);
65: void timo_done(void);
1.13 ratchov 66: void file_process(struct file *, struct pollfd *);
1.1 ratchov 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: {
1.5 ratchov 208: static char *states[] = { "ini", "zom" };
1.1 ratchov 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));
1.10 ratchov 236: f->max_nfds = nfds;
1.1 ratchov 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
1.10 ratchov 249: file_nfds += f->max_nfds;
1.1 ratchov 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
1.10 ratchov 262: file_nfds -= f->max_nfds;
1.1 ratchov 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:
1.12 ratchov 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_MONOTONIC, &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_MONOTONIC, &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:
1.1 ratchov 308: int
309: file_poll(void)
310: {
1.11 ratchov 311: struct pollfd pfds[MAXFDS], *pfd;
1.1 ratchov 312: struct file *f, **pf;
313: struct timespec ts;
314: #ifdef DEBUG
315: struct timespec sleepts;
1.9 ratchov 316: int i;
1.1 ratchov 317: #endif
318: long long delta_nsec;
1.13 ratchov 319: int nfds, res, timo;
1.12 ratchov 320:
321: log_flush();
1.1 ratchov 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:
1.12 ratchov 343: /*
344: * fill pollfd structures
345: */
1.1 ratchov 346: nfds = 0;
347: for (f = file_list; f != NULL; f = f->next) {
1.11 ratchov 348: f->nfds = f->ops->pollfd(f->arg, pfds + nfds);
349: if (f->nfds == 0)
1.1 ratchov 350: continue;
1.11 ratchov 351: nfds += f->nfds;
1.8 ratchov 352: }
1.1 ratchov 353: #ifdef DEBUG
1.8 ratchov 354: if (log_level >= 4) {
355: log_puts("poll:");
1.11 ratchov 356: pfd = pfds;
357: for (f = file_list; f != NULL; f = f->next) {
1.12 ratchov 358: if (f->nfds == 0)
1.11 ratchov 359: continue;
1.8 ratchov 360: log_puts(" ");
1.11 ratchov 361: log_puts(f->ops->name);
362: log_puts(":");
363: for (i = 0; i < f->nfds; i++) {
364: log_puts(" ");
365: log_putx(pfd->events);
366: pfd++;
1.1 ratchov 367: }
368: }
1.8 ratchov 369: log_puts("\n");
1.1 ratchov 370: }
1.12 ratchov 371: #endif
372:
373: /*
374: * process files that do not rely on poll
375: */
376: for (f = file_list; f != NULL; f = f->next) {
377: if (f->nfds > 0)
378: continue;
379: file_process(f, NULL);
380: }
381:
382: /*
1.13 ratchov 383: * Sleep. Calculate the number off milliseconds poll(2) must
384: * wait before the timo_update() needs to be called. If there're
1.15 ratchov 385: * no timeouts scheduled, then call poll(2) with infinite
386: * timeout (i.e -1).
1.12 ratchov 387: */
388: #ifdef DEBUG
1.1 ratchov 389: clock_gettime(CLOCK_MONOTONIC, &sleepts);
390: file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec);
391: file_utime += sleepts.tv_nsec - file_ts.tv_nsec;
392: #endif
1.13 ratchov 393: if (timo_queue != NULL) {
394: timo = ((int)timo_queue->val - (int)timo_abstime) / 1000;
395: if (timo < TIMER_MSEC)
396: timo = TIMER_MSEC;
397: } else
1.15 ratchov 398: timo = -1;
1.13 ratchov 399: res = poll(pfds, nfds, timo);
1.12 ratchov 400: if (res < 0) {
1.18 ! ratchov 401: if (errno != EINTR) {
! 402: log_puts("poll failed");
! 403: panic();
! 404: }
1.12 ratchov 405: return 1;
406: }
407:
408: /*
409: * run timeouts
410: */
1.1 ratchov 411: clock_gettime(CLOCK_MONOTONIC, &ts);
412: #ifdef DEBUG
413: file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec);
414: file_wtime += ts.tv_nsec - sleepts.tv_nsec;
415: #endif
1.16 ratchov 416: if (timo_queue) {
417: delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec);
418: delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
419: if (delta_nsec >= 0 && delta_nsec < 1000000000LL)
420: timo_update(delta_nsec / 1000);
421: else {
422: if (log_level >= 2)
423: log_puts("out-of-bounds clock delta\n");
424: }
1.1 ratchov 425: }
1.16 ratchov 426: file_ts = ts;
1.12 ratchov 427:
428: /*
429: * process files that rely on poll
430: */
1.11 ratchov 431: pfd = pfds;
1.1 ratchov 432: for (f = file_list; f != NULL; f = f->next) {
1.12 ratchov 433: if (f->nfds == 0)
1.1 ratchov 434: continue;
1.12 ratchov 435: file_process(f, pfd);
1.11 ratchov 436: pfd += f->nfds;
1.1 ratchov 437: }
438: return 1;
439: }
440:
441: void
442: filelist_init(void)
443: {
444: sigset_t set;
445:
1.18 ! ratchov 446: if (clock_gettime(CLOCK_MONOTONIC, &file_ts) < 0) {
! 447: log_puts("filelist_init: CLOCK_MONOTONIC unsupported\n");
! 448: panic();
! 449: }
1.1 ratchov 450: sigemptyset(&set);
1.18 ! ratchov 451: sigaddset(&set, SIGPIPE);
! 452: sigprocmask(SIG_BLOCK, &set, NULL);
1.1 ratchov 453: file_list = NULL;
454: log_sync = 0;
455: timo_init();
456: }
457:
458: void
459: filelist_done(void)
460: {
461: #ifdef DEBUG
462: struct file *f;
463:
464: if (file_list != NULL) {
465: for (f = file_list; f != NULL; f = f->next) {
466: file_log(f);
467: log_puts(" not closed\n");
468: }
469: panic();
470: }
471: log_sync = 1;
472: log_flush();
473: #endif
474: timo_done();
475: }