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