1 /* $OpenBSD: file.c,v 1.25 2019/11/27 08:18:22 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 timout 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 void 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 log_puts("timo_add: already set\n"); 103 panic(); 104 } 105 if (delta == 0) { 106 log_puts("timo_add: zero timeout is evil\n"); 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 if (log_level >= 4) 140 log_puts("timo_del: not found\n"); 141 #endif 142 } 143 144 /* 145 * routine to be called by the timer when 'delta' 24-th of microsecond 146 * elapsed. This routine updates time referece used by timeouts and 147 * calls expired timeouts 148 */ 149 void 150 timo_update(unsigned int delta) 151 { 152 struct timo *to; 153 int diff; 154 155 /* 156 * update time reference 157 */ 158 timo_abstime += delta; 159 160 /* 161 * remove from the queue and run expired timeouts 162 */ 163 while (timo_queue != NULL) { 164 /* 165 * there is no overflow here because + and - are 166 * modulo 2^32, they are the same for both signed and 167 * unsigned integers 168 */ 169 diff = timo_queue->val - timo_abstime; 170 if (diff > 0) 171 break; 172 to = timo_queue; 173 timo_queue = to->next; 174 to->set = 0; 175 to->cb(to->arg); 176 } 177 } 178 179 /* 180 * initialize timeout queue 181 */ 182 void 183 timo_init(void) 184 { 185 timo_queue = NULL; 186 timo_abstime = 0; 187 } 188 189 /* 190 * destroy timeout queue 191 */ 192 void 193 timo_done(void) 194 { 195 #ifdef DEBUG 196 if (timo_queue != NULL) { 197 log_puts("timo_done: timo_queue not empty!\n"); 198 panic(); 199 } 200 #endif 201 timo_queue = (struct timo *)0xdeadbeef; 202 } 203 204 #ifdef DEBUG 205 void 206 file_log(struct file *f) 207 { 208 static char *states[] = { "ini", "zom" }; 209 210 log_puts(f->ops->name); 211 if (log_level >= 3) { 212 log_puts("("); 213 log_puts(f->name); 214 log_puts("|"); 215 log_puts(states[f->state]); 216 log_puts(")"); 217 } 218 } 219 #endif 220 221 struct file * 222 file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds) 223 { 224 struct file *f; 225 226 if (file_nfds + nfds > MAXFDS) { 227 #ifdef DEBUG 228 if (log_level >= 1) { 229 log_puts(name); 230 log_puts(": too many polled files\n"); 231 } 232 #endif 233 return NULL; 234 } 235 f = xmalloc(sizeof(struct file)); 236 f->max_nfds = nfds; 237 f->nfds = 0; 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 250 file_nfds += f->max_nfds; 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 263 file_nfds -= f->max_nfds; 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 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_UPTIME, &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_UPTIME, &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 309 int 310 file_poll(void) 311 { 312 struct pollfd pfds[MAXFDS], *pfd; 313 struct file *f, **pf; 314 struct timespec ts; 315 #ifdef DEBUG 316 struct timespec sleepts; 317 int i; 318 #endif 319 long long delta_nsec; 320 int nfds, res, timo; 321 322 /* 323 * cleanup zombies 324 */ 325 pf = &file_list; 326 while ((f = *pf) != NULL) { 327 if (f->state == FILE_ZOMB) { 328 *pf = f->next; 329 xfree(f); 330 } else 331 pf = &f->next; 332 } 333 334 if (file_list == NULL && timo_queue == NULL) { 335 #ifdef DEBUG 336 if (log_level >= 3) 337 log_puts("nothing to do...\n"); 338 #endif 339 return 0; 340 } 341 342 /* 343 * fill pollfd structures 344 */ 345 nfds = 0; 346 for (f = file_list; f != NULL; f = f->next) { 347 f->nfds = f->ops->pollfd(f->arg, pfds + nfds); 348 if (f->nfds == 0) 349 continue; 350 nfds += f->nfds; 351 } 352 #ifdef DEBUG 353 if (log_level >= 4) { 354 log_puts("poll:"); 355 pfd = pfds; 356 for (f = file_list; f != NULL; f = f->next) { 357 log_puts(" "); 358 log_puts(f->ops->name); 359 log_puts(":"); 360 for (i = 0; i < f->nfds; i++) { 361 log_puts(" "); 362 log_putx(pfd->events); 363 pfd++; 364 } 365 } 366 log_puts("\n"); 367 } 368 #endif 369 370 /* 371 * process files that do not rely on poll 372 */ 373 for (f = file_list; f != NULL; f = f->next) { 374 if (f->nfds > 0) 375 continue; 376 file_process(f, NULL); 377 } 378 379 /* 380 * Sleep. Calculate the number of milliseconds poll(2) must 381 * wait before the timo_update() needs to be called. If there are 382 * no timeouts scheduled, then call poll(2) with infinite 383 * timeout (i.e -1). 384 */ 385 #ifdef DEBUG 386 clock_gettime(CLOCK_UPTIME, &sleepts); 387 file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec); 388 file_utime += sleepts.tv_nsec - file_ts.tv_nsec; 389 #endif 390 if (timo_queue != NULL) { 391 timo = ((int)timo_queue->val - (int)timo_abstime) / 1000; 392 if (timo < TIMER_MSEC) 393 timo = TIMER_MSEC; 394 } else 395 timo = -1; 396 log_flush(); 397 res = poll(pfds, nfds, timo); 398 if (res == -1) { 399 if (errno != EINTR) { 400 log_puts("poll failed"); 401 panic(); 402 } 403 return 1; 404 } 405 406 /* 407 * run timeouts 408 */ 409 clock_gettime(CLOCK_UPTIME, &ts); 410 #ifdef DEBUG 411 file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec); 412 file_wtime += ts.tv_nsec - sleepts.tv_nsec; 413 #endif 414 if (timo_queue) { 415 delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec); 416 delta_nsec += ts.tv_nsec - file_ts.tv_nsec; 417 if (delta_nsec >= 0 && delta_nsec < 60000000000LL) 418 timo_update(delta_nsec / 1000); 419 else { 420 if (log_level >= 2) 421 log_puts("out-of-bounds clock delta\n"); 422 } 423 } 424 file_ts = ts; 425 426 /* 427 * process files that rely on poll 428 */ 429 pfd = pfds; 430 for (f = file_list; f != NULL; f = f->next) { 431 if (f->nfds == 0) 432 continue; 433 file_process(f, pfd); 434 pfd += f->nfds; 435 } 436 return 1; 437 } 438 439 void 440 filelist_init(void) 441 { 442 sigset_t set; 443 444 if (clock_gettime(CLOCK_UPTIME, &file_ts) == -1) { 445 log_puts("filelist_init: CLOCK_UPTIME unsupported\n"); 446 panic(); 447 } 448 sigemptyset(&set); 449 sigaddset(&set, SIGPIPE); 450 sigprocmask(SIG_BLOCK, &set, NULL); 451 file_list = NULL; 452 log_sync = 0; 453 timo_init(); 454 } 455 456 void 457 filelist_done(void) 458 { 459 #ifdef DEBUG 460 struct file *f; 461 462 if (file_list != NULL) { 463 for (f = file_list; f != NULL; f = f->next) { 464 file_log(f); 465 log_puts(" not closed\n"); 466 } 467 panic(); 468 } 469 log_sync = 1; 470 log_flush(); 471 #endif 472 timo_done(); 473 } 474