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