/* $OpenBSD: file.c,v 1.27 2024/07/23 06:34:03 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * non-blocking file i/o module: each file can be read or written (or * both). To achieve non-blocking io, we simply use the poll() syscall * in an event loop and dispatch events to sub-modules. * * the module also provides trivial timeout implementation, * derived from: * * anoncvs@moule.caoua.org:/midish * * midish/timo.c rev 1.18 * midish/mdep.c rev 1.71 * * A timeout is used to schedule the call of a routine (the callback) * there is a global list of timeouts that is processed inside the * event loop. Timeouts work as follows: * * first the timo structure must be initialized with timo_set() * * then the timeout is scheduled (only once) with timo_add() * * if the timeout expires, the call-back is called; then it can * be scheduled again if needed. It's OK to reschedule it again * from the callback * * the timeout can be aborted with timo_del(), it is OK to try to * abort a timeout that has expired * */ #include #include #include #include #include #include #include #include #include "file.h" #include "utils.h" #define MAXFDS 100 #define TIMER_MSEC 5 void timo_update(unsigned int); void timo_init(void); void timo_done(void); int file_process(struct file *, struct pollfd *); struct timespec file_ts; struct file *file_list; struct timo *timo_queue; unsigned int timo_abstime; int file_slowaccept = 0, file_nfds; #ifdef DEBUG long long file_wtime, file_utime; #endif /* * initialise a timeout structure, arguments are callback and argument * that will be passed to the callback */ void timo_set(struct timo *o, void (*cb)(void *), void *arg) { o->cb = cb; o->arg = arg; o->set = 0; } /* * schedule the callback in 'delta' 24-th of microseconds. The timeout * must not be already scheduled */ void timo_add(struct timo *o, unsigned int delta) { struct timo **i; unsigned int val; int diff; #ifdef DEBUG if (o->set) { log_puts("timo_add: already set\n"); panic(); } if (delta == 0) { log_puts("timo_add: zero timeout is evil\n"); panic(); } #endif val = timo_abstime + delta; for (i = &timo_queue; *i != NULL; i = &(*i)->next) { diff = (*i)->val - val; if (diff > 0) { break; } } o->set = 1; o->val = val; o->next = *i; *i = o; } /* * abort a scheduled timeout */ void timo_del(struct timo *o) { struct timo **i; for (i = &timo_queue; *i != NULL; i = &(*i)->next) { if (*i == o) { *i = o->next; o->set = 0; return; } } #ifdef DEBUG if (log_level >= 4) log_puts("timo_del: not found\n"); #endif } /* * routine to be called by the timer when 'delta' 24-th of microsecond * elapsed. This routine updates time reference used by timeouts and * calls expired timeouts */ void timo_update(unsigned int delta) { struct timo *to; int diff; /* * update time reference */ timo_abstime += delta; /* * remove from the queue and run expired timeouts */ while (timo_queue != NULL) { /* * there is no overflow here because + and - are * modulo 2^32, they are the same for both signed and * unsigned integers */ diff = timo_queue->val - timo_abstime; if (diff > 0) break; to = timo_queue; timo_queue = to->next; to->set = 0; to->cb(to->arg); } } /* * initialize timeout queue */ void timo_init(void) { timo_queue = NULL; timo_abstime = 0; } /* * destroy timeout queue */ void timo_done(void) { #ifdef DEBUG if (timo_queue != NULL) { log_puts("timo_done: timo_queue not empty!\n"); panic(); } #endif timo_queue = (struct timo *)0xdeadbeef; } #ifdef DEBUG void file_log(struct file *f) { static char *states[] = { "ini", "zom" }; log_puts(f->ops->name); if (log_level >= 3) { log_puts("("); log_puts(f->name); log_puts("|"); log_puts(states[f->state]); log_puts(")"); } } #endif struct file * file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds) { struct file *f; if (file_nfds + nfds > MAXFDS) { #ifdef DEBUG if (log_level >= 1) { log_puts(name); log_puts(": too many polled files\n"); } #endif return NULL; } f = xmalloc(sizeof(struct file)); f->max_nfds = nfds; f->nfds = 0; f->ops = ops; f->arg = arg; f->name = name; f->state = FILE_INIT; f->next = file_list; file_list = f; #ifdef DEBUG if (log_level >= 3) { file_log(f); log_puts(": created\n"); } #endif file_nfds += f->max_nfds; return f; } void file_del(struct file *f) { #ifdef DEBUG if (f->state == FILE_ZOMB) { log_puts("bad state in file_del()\n"); panic(); } #endif file_nfds -= f->max_nfds; f->state = FILE_ZOMB; #ifdef DEBUG if (log_level >= 3) { file_log(f); log_puts(": destroyed\n"); } #endif } int file_process(struct file *f, struct pollfd *pfd) { int rc, revents; #ifdef DEBUG struct timespec ts0, ts1; long us; #endif #ifdef DEBUG if (log_level >= 3) clock_gettime(CLOCK_UPTIME, &ts0); #endif rc = 0; revents = (f->state != FILE_ZOMB) ? f->ops->revents(f->arg, pfd) : 0; if ((revents & POLLHUP) && (f->state != FILE_ZOMB)) { f->ops->hup(f->arg); rc = 1; } if ((revents & POLLIN) && (f->state != FILE_ZOMB)) { f->ops->in(f->arg); rc = 1; } if ((revents & POLLOUT) && (f->state != FILE_ZOMB)) { f->ops->out(f->arg); rc = 1; } #ifdef DEBUG if (log_level >= 3) { clock_gettime(CLOCK_UPTIME, &ts1); us = 1000000L * (ts1.tv_sec - ts0.tv_sec); us += (ts1.tv_nsec - ts0.tv_nsec) / 1000; if (log_level >= 4 || us >= 5000) { file_log(f); log_puts(": processed in "); log_putu(us); log_puts("us\n"); } } #endif return rc; } int file_poll(void) { struct pollfd pfds[MAXFDS], *pfd; struct file *f, **pf; struct timespec ts; #ifdef DEBUG struct timespec sleepts; int i; #endif long long delta_nsec; int nfds, res, timo; /* * cleanup zombies */ pf = &file_list; while ((f = *pf) != NULL) { if (f->state == FILE_ZOMB) { *pf = f->next; xfree(f); } else pf = &f->next; } if (file_list == NULL && timo_queue == NULL) { #ifdef DEBUG if (log_level >= 3) log_puts("nothing to do...\n"); #endif return 0; } /* * fill pollfd structures */ nfds = 0; for (f = file_list; f != NULL; f = f->next) { f->nfds = f->ops->pollfd(f->arg, pfds + nfds); if (f->nfds == 0) continue; nfds += f->nfds; } #ifdef DEBUG if (log_level >= 4) { log_puts("poll:"); pfd = pfds; for (f = file_list; f != NULL; f = f->next) { log_puts(" "); log_puts(f->ops->name); log_puts(":"); for (i = 0; i < f->nfds; i++) { log_puts(" "); log_putx(pfd->events); pfd++; } } log_puts("\n"); } #endif /* * process files that do not rely on poll */ res = 0; for (f = file_list; f != NULL; f = f->next) { if (f->nfds > 0) continue; res |= file_process(f, NULL); } /* * The processing may have changed the poll(2) conditions of * other files, so restart the loop to force their poll(2) event * masks to be reevaluated. */ if (res) return 1; /* * Sleep. Calculate the number of milliseconds poll(2) must * wait before the timo_update() needs to be called. If there are * no timeouts scheduled, then call poll(2) with infinite * timeout (i.e -1). */ #ifdef DEBUG clock_gettime(CLOCK_UPTIME, &sleepts); file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec); file_utime += sleepts.tv_nsec - file_ts.tv_nsec; #endif if (timo_queue != NULL) { timo = ((int)timo_queue->val - (int)timo_abstime) / 1000; if (timo < TIMER_MSEC) timo = TIMER_MSEC; } else timo = -1; log_flush(); res = poll(pfds, nfds, timo); if (res == -1) { if (errno != EINTR) { log_puts("poll failed"); panic(); } return 1; } /* * run timeouts */ clock_gettime(CLOCK_UPTIME, &ts); #ifdef DEBUG file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec); file_wtime += ts.tv_nsec - sleepts.tv_nsec; #endif if (timo_queue) { delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec); delta_nsec += ts.tv_nsec - file_ts.tv_nsec; if (delta_nsec >= 0 && delta_nsec < 60000000000LL) timo_update(delta_nsec / 1000); else { if (log_level >= 2) log_puts("out-of-bounds clock delta\n"); } } file_ts = ts; /* * process files that rely on poll */ pfd = pfds; for (f = file_list; f != NULL; f = f->next) { if (f->nfds == 0) continue; file_process(f, pfd); pfd += f->nfds; } return 1; } void filelist_init(void) { sigset_t set; if (clock_gettime(CLOCK_UPTIME, &file_ts) == -1) { log_puts("filelist_init: CLOCK_UPTIME unsupported\n"); panic(); } sigemptyset(&set); sigaddset(&set, SIGPIPE); sigprocmask(SIG_BLOCK, &set, NULL); file_list = NULL; log_sync = 0; timo_init(); } void filelist_done(void) { #ifdef DEBUG struct file *f; if (file_list != NULL) { for (f = file_list; f != NULL; f = f->next) { file_log(f); log_puts(" not closed\n"); } panic(); } log_sync = 1; log_flush(); #endif timo_done(); }