Annotation of src/usr.bin/sndiod/file.c, Revision 1.6
1.6 ! ratchov 1: /* $OpenBSD: file.c,v 1.5 2014/03/17 17:17:01 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/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
1.4 ratchov 64:
65: void timo_update(unsigned int);
66: void timo_init(void);
67: void timo_done(void);
68: void file_sigalrm(int);
1.1 ratchov 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: {
1.5 ratchov 210: static char *states[] = { "ini", "zom" };
1.1 ratchov 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: struct pollfd pfds[MAXFDS];
278: struct file *f, **pf;
279: struct timespec ts;
280: #ifdef DEBUG
281: struct timespec sleepts;
282: struct timespec ts0, ts1;
283: long us;
1.6 ! ratchov 284: int i, n, nfds;
1.1 ratchov 285: #endif
286: long long delta_nsec;
1.6 ! ratchov 287: int revents, res, immed;
1.1 ratchov 288:
289: /*
290: * cleanup zombies
291: */
292: pf = &file_list;
293: while ((f = *pf) != NULL) {
294: if (f->state == FILE_ZOMB) {
295: *pf = f->next;
296: xfree(f);
297: } else
298: pf = &f->next;
299: }
300:
301: if (file_list == NULL && timo_queue == NULL) {
302: #ifdef DEBUG
303: if (log_level >= 3)
304: log_puts("nothing to do...\n");
305: #endif
306: return 0;
307: }
308:
309: log_flush();
310: #ifdef DEBUG
311: if (log_level >= 4)
312: log_puts("poll:");
313: #endif
314: nfds = 0;
1.6 ! ratchov 315: immed = 0;
1.1 ratchov 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: }
1.6 ! ratchov 328: if (n < 0) {
! 329: immed = 1;
! 330: n = 0;
! 331: }
1.1 ratchov 332: f->pfd = pfds + nfds;
333: nfds += n;
334: #ifdef DEBUG
335: if (log_level >= 4) {
336: log_puts("=");
337: for (i = 0; i < n; i++) {
338: if (i > 0)
339: log_puts(",");
340: log_putx(f->pfd[i].events);
341: }
342: }
343: #endif
344: }
345: #ifdef DEBUG
346: if (log_level >= 4)
347: log_puts("\n");
348: #endif
349:
350: #ifdef DEBUG
351: clock_gettime(CLOCK_MONOTONIC, &sleepts);
352: file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec);
353: file_utime += sleepts.tv_nsec - file_ts.tv_nsec;
354: #endif
1.6 ! ratchov 355: if (!immed) {
! 356: res = poll(pfds, nfds, -1);
! 357: if (res < 0 && errno != EINTR)
! 358: err(1, "poll");
! 359: #ifdef DEBUG
! 360: if (log_level >= 4 && res >= 0) {
! 361: log_puts("poll: return:");
! 362: for (i = 0; i < nfds; i++) {
! 363: log_puts(" ");
! 364: log_putx(pfds[i].revents);
! 365: }
! 366: log_puts("\n");
1.1 ratchov 367: }
368: #endif
1.6 ! ratchov 369: } else
! 370: res = 0;
1.1 ratchov 371: clock_gettime(CLOCK_MONOTONIC, &ts);
372: #ifdef DEBUG
373: file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec);
374: file_wtime += ts.tv_nsec - sleepts.tv_nsec;
375: #endif
376: delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec);
377: delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
378: #ifdef DEBUG
379: if (delta_nsec < 0)
380: log_puts("file_poll: negative time interval\n");
381: #endif
382: file_ts = ts;
383: if (delta_nsec >= 0 && delta_nsec < 1000000000LL)
384: timo_update(delta_nsec / 1000);
385: else {
386: if (log_level >= 2)
387: log_puts("ignored huge clock delta\n");
388: }
1.6 ! ratchov 389: if (!immed && res <= 0)
1.1 ratchov 390: return 1;
391:
392: for (f = file_list; f != NULL; f = f->next) {
393: if (f->pfd == NULL)
394: continue;
395: #ifdef DEBUG
396: clock_gettime(CLOCK_MONOTONIC, &ts0);
397: #endif
1.3 ratchov 398: revents = (f->state != FILE_ZOMB) ?
399: f->ops->revents(f->arg, f->pfd) : 0;
1.1 ratchov 400: if ((revents & POLLHUP) && (f->state != FILE_ZOMB))
401: f->ops->hup(f->arg);
402: if ((revents & POLLIN) && (f->state != FILE_ZOMB))
403: f->ops->in(f->arg);
404: if ((revents & POLLOUT) && (f->state != FILE_ZOMB))
405: f->ops->out(f->arg);
406: #ifdef DEBUG
407: clock_gettime(CLOCK_MONOTONIC, &ts1);
408: us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
409: us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
410: if (log_level >= 4 || (log_level >= 3 && us >= 5000)) {
411: file_log(f);
412: log_puts(": processed in ");
413: log_putu(us);
414: log_puts("us\n");
415: }
416: #endif
417: }
418: return 1;
419: }
420:
421: /*
422: * handler for SIGALRM, invoked periodically
423: */
424: void
425: file_sigalrm(int i)
426: {
427: /* nothing to do, we only want poll() to return EINTR */
428: }
429:
430:
431: void
432: filelist_init(void)
433: {
434: static struct sigaction sa;
435: struct itimerval it;
436: sigset_t set;
437:
438: sigemptyset(&set);
439: (void)sigaddset(&set, SIGPIPE);
440: if (sigprocmask(SIG_BLOCK, &set, NULL))
441: err(1, "sigprocmask");
442: file_list = NULL;
443: if (clock_gettime(CLOCK_MONOTONIC, &file_ts) < 0) {
444: perror("clock_gettime");
445: exit(1);
446: }
447: sa.sa_flags = SA_RESTART;
448: sa.sa_handler = file_sigalrm;
449: sigfillset(&sa.sa_mask);
450: if (sigaction(SIGALRM, &sa, NULL) < 0) {
451: perror("sigaction");
452: exit(1);
453: }
454: it.it_interval.tv_sec = 0;
455: it.it_interval.tv_usec = TIMER_USEC;
456: it.it_value.tv_sec = 0;
457: it.it_value.tv_usec = TIMER_USEC;
458: if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
459: perror("setitimer");
460: exit(1);
461: }
462: log_sync = 0;
463: timo_init();
464: }
465:
466: void
467: filelist_done(void)
468: {
469: struct itimerval it;
470: #ifdef DEBUG
471: struct file *f;
472:
473: if (file_list != NULL) {
474: for (f = file_list; f != NULL; f = f->next) {
475: file_log(f);
476: log_puts(" not closed\n");
477: }
478: panic();
479: }
480: log_sync = 1;
481: log_flush();
482: #endif
483: timerclear(&it.it_value);
484: timerclear(&it.it_interval);
485: if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
486: perror("setitimer");
487: exit(1);
488: }
489: timo_done();
490: }