1*7b639200Sratchov /* $OpenBSD: file.c,v 1.28 2024/12/20 07:35:56 ratchov Exp $ */ 287bc9f6aSratchov /* 387bc9f6aSratchov * Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org> 487bc9f6aSratchov * 587bc9f6aSratchov * Permission to use, copy, modify, and distribute this software for any 687bc9f6aSratchov * purpose with or without fee is hereby granted, provided that the above 787bc9f6aSratchov * copyright notice and this permission notice appear in all copies. 887bc9f6aSratchov * 987bc9f6aSratchov * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1087bc9f6aSratchov * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1187bc9f6aSratchov * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1287bc9f6aSratchov * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1387bc9f6aSratchov * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1487bc9f6aSratchov * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1587bc9f6aSratchov * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1687bc9f6aSratchov */ 1787bc9f6aSratchov /* 1887bc9f6aSratchov * non-blocking file i/o module: each file can be read or written (or 1987bc9f6aSratchov * both). To achieve non-blocking io, we simply use the poll() syscall 2087bc9f6aSratchov * in an event loop and dispatch events to sub-modules. 2187bc9f6aSratchov * 2287bc9f6aSratchov * the module also provides trivial timeout implementation, 2387bc9f6aSratchov * derived from: 2487bc9f6aSratchov * 2587bc9f6aSratchov * anoncvs@moule.caoua.org:/midish 2687bc9f6aSratchov * 2787bc9f6aSratchov * midish/timo.c rev 1.18 2887bc9f6aSratchov * midish/mdep.c rev 1.71 2987bc9f6aSratchov * 3087bc9f6aSratchov * A timeout is used to schedule the call of a routine (the callback) 3187bc9f6aSratchov * there is a global list of timeouts that is processed inside the 3287bc9f6aSratchov * event loop. Timeouts work as follows: 3387bc9f6aSratchov * 3487bc9f6aSratchov * first the timo structure must be initialized with timo_set() 3587bc9f6aSratchov * 3687bc9f6aSratchov * then the timeout is scheduled (only once) with timo_add() 3787bc9f6aSratchov * 3887bc9f6aSratchov * if the timeout expires, the call-back is called; then it can 3987bc9f6aSratchov * be scheduled again if needed. It's OK to reschedule it again 4087bc9f6aSratchov * from the callback 4187bc9f6aSratchov * 4287bc9f6aSratchov * the timeout can be aborted with timo_del(), it is OK to try to 43d9a51c35Sjmc * abort a timeout that has expired 4487bc9f6aSratchov * 4587bc9f6aSratchov */ 4687bc9f6aSratchov 4787bc9f6aSratchov #include <sys/types.h> 4887bc9f6aSratchov 4987bc9f6aSratchov #include <errno.h> 5087bc9f6aSratchov #include <fcntl.h> 5187bc9f6aSratchov #include <poll.h> 5287bc9f6aSratchov #include <signal.h> 5387bc9f6aSratchov #include <stdio.h> 5487bc9f6aSratchov #include <stdlib.h> 5587bc9f6aSratchov #include <time.h> 5687bc9f6aSratchov 5787bc9f6aSratchov #include "file.h" 5887bc9f6aSratchov #include "utils.h" 5987bc9f6aSratchov 6087bc9f6aSratchov #define MAXFDS 100 61df956371Sratchov #define TIMER_MSEC 5 6287bc9f6aSratchov 63fcda7a7eSratchov void timo_update(unsigned int); 64fcda7a7eSratchov void timo_init(void); 65fcda7a7eSratchov void timo_done(void); 6660ba7eecSratchov int file_process(struct file *, struct pollfd *); 67fcda7a7eSratchov 6887bc9f6aSratchov struct timespec file_ts; 6987bc9f6aSratchov struct file *file_list; 7087bc9f6aSratchov struct timo *timo_queue; 7187bc9f6aSratchov unsigned int timo_abstime; 7287bc9f6aSratchov int file_slowaccept = 0, file_nfds; 7387bc9f6aSratchov #ifdef DEBUG 7487bc9f6aSratchov long long file_wtime, file_utime; 7587bc9f6aSratchov #endif 7687bc9f6aSratchov 7787bc9f6aSratchov /* 7887bc9f6aSratchov * initialise a timeout structure, arguments are callback and argument 7987bc9f6aSratchov * that will be passed to the callback 8087bc9f6aSratchov */ 8187bc9f6aSratchov void 8287bc9f6aSratchov timo_set(struct timo *o, void (*cb)(void *), void *arg) 8387bc9f6aSratchov { 8487bc9f6aSratchov o->cb = cb; 8587bc9f6aSratchov o->arg = arg; 8687bc9f6aSratchov o->set = 0; 8787bc9f6aSratchov } 8887bc9f6aSratchov 8987bc9f6aSratchov /* 9087bc9f6aSratchov * schedule the callback in 'delta' 24-th of microseconds. The timeout 9187bc9f6aSratchov * must not be already scheduled 9287bc9f6aSratchov */ 9387bc9f6aSratchov void 9487bc9f6aSratchov timo_add(struct timo *o, unsigned int delta) 9587bc9f6aSratchov { 9687bc9f6aSratchov struct timo **i; 9787bc9f6aSratchov unsigned int val; 9887bc9f6aSratchov int diff; 9987bc9f6aSratchov 10087bc9f6aSratchov #ifdef DEBUG 10187bc9f6aSratchov if (o->set) { 102*7b639200Sratchov logx(0, "timo_add: already set"); 10387bc9f6aSratchov panic(); 10487bc9f6aSratchov } 10587bc9f6aSratchov if (delta == 0) { 106*7b639200Sratchov logx(0, "timo_add: zero timeout is evil"); 10787bc9f6aSratchov panic(); 10887bc9f6aSratchov } 10987bc9f6aSratchov #endif 11087bc9f6aSratchov val = timo_abstime + delta; 11187bc9f6aSratchov for (i = &timo_queue; *i != NULL; i = &(*i)->next) { 11287bc9f6aSratchov diff = (*i)->val - val; 11387bc9f6aSratchov if (diff > 0) { 11487bc9f6aSratchov break; 11587bc9f6aSratchov } 11687bc9f6aSratchov } 11787bc9f6aSratchov o->set = 1; 11887bc9f6aSratchov o->val = val; 11987bc9f6aSratchov o->next = *i; 12087bc9f6aSratchov *i = o; 12187bc9f6aSratchov } 12287bc9f6aSratchov 12387bc9f6aSratchov /* 12487bc9f6aSratchov * abort a scheduled timeout 12587bc9f6aSratchov */ 12687bc9f6aSratchov void 12787bc9f6aSratchov timo_del(struct timo *o) 12887bc9f6aSratchov { 12987bc9f6aSratchov struct timo **i; 13087bc9f6aSratchov 13187bc9f6aSratchov for (i = &timo_queue; *i != NULL; i = &(*i)->next) { 13287bc9f6aSratchov if (*i == o) { 13387bc9f6aSratchov *i = o->next; 13487bc9f6aSratchov o->set = 0; 13587bc9f6aSratchov return; 13687bc9f6aSratchov } 13787bc9f6aSratchov } 13887bc9f6aSratchov #ifdef DEBUG 139*7b639200Sratchov logx(4, "timo_del: not found"); 14087bc9f6aSratchov #endif 14187bc9f6aSratchov } 14287bc9f6aSratchov 14387bc9f6aSratchov /* 14487bc9f6aSratchov * routine to be called by the timer when 'delta' 24-th of microsecond 145d9a51c35Sjmc * elapsed. This routine updates time reference used by timeouts and 14687bc9f6aSratchov * calls expired timeouts 14787bc9f6aSratchov */ 14887bc9f6aSratchov void 14987bc9f6aSratchov timo_update(unsigned int delta) 15087bc9f6aSratchov { 15187bc9f6aSratchov struct timo *to; 15287bc9f6aSratchov int diff; 15387bc9f6aSratchov 15487bc9f6aSratchov /* 15587bc9f6aSratchov * update time reference 15687bc9f6aSratchov */ 15787bc9f6aSratchov timo_abstime += delta; 15887bc9f6aSratchov 15987bc9f6aSratchov /* 16087bc9f6aSratchov * remove from the queue and run expired timeouts 16187bc9f6aSratchov */ 16287bc9f6aSratchov while (timo_queue != NULL) { 16387bc9f6aSratchov /* 16487bc9f6aSratchov * there is no overflow here because + and - are 16587bc9f6aSratchov * modulo 2^32, they are the same for both signed and 16687bc9f6aSratchov * unsigned integers 16787bc9f6aSratchov */ 16887bc9f6aSratchov diff = timo_queue->val - timo_abstime; 16987bc9f6aSratchov if (diff > 0) 17087bc9f6aSratchov break; 17187bc9f6aSratchov to = timo_queue; 17287bc9f6aSratchov timo_queue = to->next; 17387bc9f6aSratchov to->set = 0; 17487bc9f6aSratchov to->cb(to->arg); 17587bc9f6aSratchov } 17687bc9f6aSratchov } 17787bc9f6aSratchov 17887bc9f6aSratchov /* 17987bc9f6aSratchov * initialize timeout queue 18087bc9f6aSratchov */ 18187bc9f6aSratchov void 18287bc9f6aSratchov timo_init(void) 18387bc9f6aSratchov { 18487bc9f6aSratchov timo_queue = NULL; 18587bc9f6aSratchov timo_abstime = 0; 18687bc9f6aSratchov } 18787bc9f6aSratchov 18887bc9f6aSratchov /* 18987bc9f6aSratchov * destroy timeout queue 19087bc9f6aSratchov */ 19187bc9f6aSratchov void 19287bc9f6aSratchov timo_done(void) 19387bc9f6aSratchov { 19487bc9f6aSratchov #ifdef DEBUG 19587bc9f6aSratchov if (timo_queue != NULL) { 196*7b639200Sratchov logx(0, "timo_done: timo_queue not empty!"); 19787bc9f6aSratchov panic(); 19887bc9f6aSratchov } 19987bc9f6aSratchov #endif 20087bc9f6aSratchov timo_queue = (struct timo *)0xdeadbeef; 20187bc9f6aSratchov } 20287bc9f6aSratchov 20387bc9f6aSratchov struct file * 20487bc9f6aSratchov file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds) 20587bc9f6aSratchov { 20687bc9f6aSratchov struct file *f; 20787bc9f6aSratchov 20887bc9f6aSratchov if (file_nfds + nfds > MAXFDS) { 20987bc9f6aSratchov #ifdef DEBUG 210*7b639200Sratchov logx(1, "%s: too many polled files", name); 21187bc9f6aSratchov #endif 21287bc9f6aSratchov return NULL; 21387bc9f6aSratchov } 21487bc9f6aSratchov f = xmalloc(sizeof(struct file)); 215e4bc365bSratchov f->max_nfds = nfds; 2164c0d1d38Sratchov f->nfds = 0; 21787bc9f6aSratchov f->ops = ops; 21887bc9f6aSratchov f->arg = arg; 21987bc9f6aSratchov f->name = name; 22087bc9f6aSratchov f->state = FILE_INIT; 22187bc9f6aSratchov f->next = file_list; 22287bc9f6aSratchov file_list = f; 22387bc9f6aSratchov #ifdef DEBUG 224*7b639200Sratchov logx(3, "%s: created", f->name); 22587bc9f6aSratchov #endif 226e4bc365bSratchov file_nfds += f->max_nfds; 22787bc9f6aSratchov return f; 22887bc9f6aSratchov } 22987bc9f6aSratchov 23087bc9f6aSratchov void 23187bc9f6aSratchov file_del(struct file *f) 23287bc9f6aSratchov { 23387bc9f6aSratchov #ifdef DEBUG 23487bc9f6aSratchov if (f->state == FILE_ZOMB) { 235*7b639200Sratchov logx(0, "%s: %s: bad state in file_del", __func__, f->name); 23687bc9f6aSratchov panic(); 23787bc9f6aSratchov } 23887bc9f6aSratchov #endif 239e4bc365bSratchov file_nfds -= f->max_nfds; 24087bc9f6aSratchov f->state = FILE_ZOMB; 24187bc9f6aSratchov #ifdef DEBUG 242*7b639200Sratchov logx(3, "%s: destroyed", f->name); 24387bc9f6aSratchov #endif 24487bc9f6aSratchov } 24587bc9f6aSratchov 24660ba7eecSratchov int 247633fcf5dSratchov file_process(struct file *f, struct pollfd *pfd) 24887bc9f6aSratchov { 24960ba7eecSratchov int rc, revents; 25087bc9f6aSratchov #ifdef DEBUG 25187bc9f6aSratchov struct timespec ts0, ts1; 25287bc9f6aSratchov long us; 25387bc9f6aSratchov #endif 25487bc9f6aSratchov 25587bc9f6aSratchov #ifdef DEBUG 25608729cdbSratchov if (log_level >= 3) 2573471b98aSratchov clock_gettime(CLOCK_UPTIME, &ts0); 25887bc9f6aSratchov #endif 25960ba7eecSratchov rc = 0; 260d135469bSratchov revents = (f->state != FILE_ZOMB) ? 2611e4c42baSratchov f->ops->revents(f->arg, pfd) : 0; 26260ba7eecSratchov if ((revents & POLLHUP) && (f->state != FILE_ZOMB)) { 26387bc9f6aSratchov f->ops->hup(f->arg); 26460ba7eecSratchov rc = 1; 26560ba7eecSratchov } 26660ba7eecSratchov if ((revents & POLLIN) && (f->state != FILE_ZOMB)) { 26787bc9f6aSratchov f->ops->in(f->arg); 26860ba7eecSratchov rc = 1; 26960ba7eecSratchov } 27060ba7eecSratchov if ((revents & POLLOUT) && (f->state != FILE_ZOMB)) { 27187bc9f6aSratchov f->ops->out(f->arg); 27260ba7eecSratchov rc = 1; 27360ba7eecSratchov } 27487bc9f6aSratchov #ifdef DEBUG 27508729cdbSratchov if (log_level >= 3) { 2763471b98aSratchov clock_gettime(CLOCK_UPTIME, &ts1); 27787bc9f6aSratchov us = 1000000L * (ts1.tv_sec - ts0.tv_sec); 27887bc9f6aSratchov us += (ts1.tv_nsec - ts0.tv_nsec) / 1000; 279*7b639200Sratchov if (us >= 5000) 280*7b639200Sratchov logx(4, "%s: processed in %luus", f->name, us); 28108729cdbSratchov } 28287bc9f6aSratchov #endif 28360ba7eecSratchov return rc; 284633fcf5dSratchov } 285633fcf5dSratchov 286*7b639200Sratchov #ifdef DEBUG 287*7b639200Sratchov size_t 288*7b639200Sratchov filelist_fmt(char *buf, size_t size, struct pollfd *pfd, int ret) 289*7b639200Sratchov { 290*7b639200Sratchov struct file *f; 291*7b639200Sratchov char *p = buf, *end = buf + size; 292*7b639200Sratchov const char *sep = ""; 293*7b639200Sratchov int i; 294*7b639200Sratchov 295*7b639200Sratchov for (f = file_list; f != NULL; f = f->next) { 296*7b639200Sratchov p += snprintf(p, p < end ? end - p : 0, "%s%s:", sep, f->name); 297*7b639200Sratchov for (i = 0; i < f->nfds; i++) { 298*7b639200Sratchov p += snprintf(p, p < end ? end - p : 0, " 0x%x", 299*7b639200Sratchov ret ? pfd->revents : pfd->events); 300*7b639200Sratchov pfd++; 301*7b639200Sratchov } 302*7b639200Sratchov sep = ", "; 303*7b639200Sratchov } 304*7b639200Sratchov return p - buf; 305*7b639200Sratchov } 306*7b639200Sratchov #endif 307*7b639200Sratchov 308633fcf5dSratchov int 309633fcf5dSratchov file_poll(void) 310633fcf5dSratchov { 311633fcf5dSratchov struct pollfd pfds[MAXFDS], *pfd; 312633fcf5dSratchov struct file *f, **pf; 313633fcf5dSratchov struct timespec ts; 314633fcf5dSratchov #ifdef DEBUG 315633fcf5dSratchov struct timespec sleepts; 316*7b639200Sratchov char str[128]; 317633fcf5dSratchov #endif 318633fcf5dSratchov long long delta_nsec; 319de56ec40Sratchov int nfds, res, timo; 320633fcf5dSratchov 321633fcf5dSratchov /* 322633fcf5dSratchov * cleanup zombies 323633fcf5dSratchov */ 324633fcf5dSratchov pf = &file_list; 325633fcf5dSratchov while ((f = *pf) != NULL) { 326633fcf5dSratchov if (f->state == FILE_ZOMB) { 327633fcf5dSratchov *pf = f->next; 328633fcf5dSratchov xfree(f); 329633fcf5dSratchov } else 330633fcf5dSratchov pf = &f->next; 331633fcf5dSratchov } 332633fcf5dSratchov 333633fcf5dSratchov if (file_list == NULL && timo_queue == NULL) { 334633fcf5dSratchov #ifdef DEBUG 335*7b639200Sratchov logx(3, "nothing to do..."); 336633fcf5dSratchov #endif 337633fcf5dSratchov return 0; 338633fcf5dSratchov } 339633fcf5dSratchov 340633fcf5dSratchov /* 341633fcf5dSratchov * fill pollfd structures 342633fcf5dSratchov */ 343633fcf5dSratchov nfds = 0; 344633fcf5dSratchov for (f = file_list; f != NULL; f = f->next) { 345633fcf5dSratchov f->nfds = f->ops->pollfd(f->arg, pfds + nfds); 346633fcf5dSratchov if (f->nfds == 0) 347633fcf5dSratchov continue; 348633fcf5dSratchov nfds += f->nfds; 349633fcf5dSratchov } 350633fcf5dSratchov #ifdef DEBUG 351*7b639200Sratchov logx(4, "poll [%s]", (filelist_fmt(str, sizeof(str), pfds, 0), str)); 352633fcf5dSratchov #endif 353633fcf5dSratchov 354633fcf5dSratchov /* 355633fcf5dSratchov * process files that do not rely on poll 356633fcf5dSratchov */ 35760ba7eecSratchov res = 0; 358633fcf5dSratchov for (f = file_list; f != NULL; f = f->next) { 359633fcf5dSratchov if (f->nfds > 0) 360633fcf5dSratchov continue; 36160ba7eecSratchov res |= file_process(f, NULL); 362633fcf5dSratchov } 36360ba7eecSratchov /* 36460ba7eecSratchov * The processing may have changed the poll(2) conditions of 36560ba7eecSratchov * other files, so restart the loop to force their poll(2) event 36660ba7eecSratchov * masks to be reevaluated. 36760ba7eecSratchov */ 36860ba7eecSratchov if (res) 36960ba7eecSratchov return 1; 370633fcf5dSratchov 371633fcf5dSratchov /* 3727c71888cSratchov * Sleep. Calculate the number of milliseconds poll(2) must 3737c71888cSratchov * wait before the timo_update() needs to be called. If there are 3744466229bSratchov * no timeouts scheduled, then call poll(2) with infinite 3754466229bSratchov * timeout (i.e -1). 376633fcf5dSratchov */ 377633fcf5dSratchov #ifdef DEBUG 3783471b98aSratchov clock_gettime(CLOCK_UPTIME, &sleepts); 379633fcf5dSratchov file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec); 380633fcf5dSratchov file_utime += sleepts.tv_nsec - file_ts.tv_nsec; 381633fcf5dSratchov #endif 382de56ec40Sratchov if (timo_queue != NULL) { 383de56ec40Sratchov timo = ((int)timo_queue->val - (int)timo_abstime) / 1000; 384de56ec40Sratchov if (timo < TIMER_MSEC) 385de56ec40Sratchov timo = TIMER_MSEC; 386de56ec40Sratchov } else 3874466229bSratchov timo = -1; 3889c0af79aSratchov log_flush(); 389de56ec40Sratchov res = poll(pfds, nfds, timo); 3903aaa63ebSderaadt if (res == -1) { 391babe36e9Sratchov if (errno != EINTR) { 392*7b639200Sratchov logx(0, "poll failed"); 393babe36e9Sratchov panic(); 394babe36e9Sratchov } 395633fcf5dSratchov return 1; 396633fcf5dSratchov } 397633fcf5dSratchov 398633fcf5dSratchov /* 399633fcf5dSratchov * run timeouts 400633fcf5dSratchov */ 4013471b98aSratchov clock_gettime(CLOCK_UPTIME, &ts); 402633fcf5dSratchov #ifdef DEBUG 403633fcf5dSratchov file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec); 404633fcf5dSratchov file_wtime += ts.tv_nsec - sleepts.tv_nsec; 405633fcf5dSratchov #endif 406753b38d1Sratchov if (timo_queue) { 407633fcf5dSratchov delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec); 408633fcf5dSratchov delta_nsec += ts.tv_nsec - file_ts.tv_nsec; 409b8b8f36cSratchov if (delta_nsec >= 0 && delta_nsec < 60000000000LL) 410633fcf5dSratchov timo_update(delta_nsec / 1000); 411*7b639200Sratchov else 412*7b639200Sratchov logx(2, "out-of-bounds clock delta"); 413753b38d1Sratchov } 414753b38d1Sratchov file_ts = ts; 415633fcf5dSratchov 416633fcf5dSratchov /* 417633fcf5dSratchov * process files that rely on poll 418633fcf5dSratchov */ 419633fcf5dSratchov pfd = pfds; 420633fcf5dSratchov for (f = file_list; f != NULL; f = f->next) { 421633fcf5dSratchov if (f->nfds == 0) 422633fcf5dSratchov continue; 423633fcf5dSratchov file_process(f, pfd); 4241e4c42baSratchov pfd += f->nfds; 42587bc9f6aSratchov } 42687bc9f6aSratchov return 1; 42787bc9f6aSratchov } 42887bc9f6aSratchov 42987bc9f6aSratchov void 43087bc9f6aSratchov filelist_init(void) 43187bc9f6aSratchov { 43287bc9f6aSratchov sigset_t set; 43387bc9f6aSratchov 4343aaa63ebSderaadt if (clock_gettime(CLOCK_UPTIME, &file_ts) == -1) { 435*7b639200Sratchov logx(0, "filelist_init: CLOCK_UPTIME unsupported"); 436babe36e9Sratchov panic(); 43787bc9f6aSratchov } 438babe36e9Sratchov sigemptyset(&set); 439babe36e9Sratchov sigaddset(&set, SIGPIPE); 440babe36e9Sratchov sigprocmask(SIG_BLOCK, &set, NULL); 441babe36e9Sratchov file_list = NULL; 44287bc9f6aSratchov log_sync = 0; 44387bc9f6aSratchov timo_init(); 44487bc9f6aSratchov } 44587bc9f6aSratchov 44687bc9f6aSratchov void 44787bc9f6aSratchov filelist_done(void) 44887bc9f6aSratchov { 44987bc9f6aSratchov #ifdef DEBUG 45087bc9f6aSratchov struct file *f; 45187bc9f6aSratchov 45287bc9f6aSratchov if (file_list != NULL) { 453*7b639200Sratchov for (f = file_list; f != NULL; f = f->next) 454*7b639200Sratchov logx(0, "%s: not closed", f->name); 45587bc9f6aSratchov panic(); 45687bc9f6aSratchov } 45787bc9f6aSratchov log_sync = 1; 45887bc9f6aSratchov log_flush(); 45987bc9f6aSratchov #endif 46087bc9f6aSratchov timo_done(); 46187bc9f6aSratchov } 462