Annotation of src/usr.bin/sndiod/file.c, Revision 1.5
1.5 ! ratchov 1: /* $OpenBSD: file.c,v 1.4 2013/11/18 17:37:45 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: 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
1.3 ratchov 391: revents = (f->state != FILE_ZOMB) ?
392: f->ops->revents(f->arg, f->pfd) : 0;
1.1 ratchov 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: }