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