1 /* $OpenBSD: file.c,v 1.22 2016/06/30 21:37:29 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->ops = ops; 238 f->arg = arg; 239 f->name = name; 240 f->state = FILE_INIT; 241 f->next = file_list; 242 file_list = f; 243 #ifdef DEBUG 244 if (log_level >= 3) { 245 file_log(f); 246 log_puts(": created\n"); 247 } 248 #endif 249 file_nfds += f->max_nfds; 250 return f; 251 } 252 253 void 254 file_del(struct file *f) 255 { 256 #ifdef DEBUG 257 if (f->state == FILE_ZOMB) { 258 log_puts("bad state in file_del()\n"); 259 panic(); 260 } 261 #endif 262 file_nfds -= f->max_nfds; 263 f->state = FILE_ZOMB; 264 #ifdef DEBUG 265 if (log_level >= 3) { 266 file_log(f); 267 log_puts(": destroyed\n"); 268 } 269 #endif 270 } 271 272 void 273 file_process(struct file *f, struct pollfd *pfd) 274 { 275 int revents; 276 #ifdef DEBUG 277 struct timespec ts0, ts1; 278 long us; 279 #endif 280 281 #ifdef DEBUG 282 if (log_level >= 3) 283 clock_gettime(CLOCK_UPTIME, &ts0); 284 #endif 285 revents = (f->state != FILE_ZOMB) ? 286 f->ops->revents(f->arg, pfd) : 0; 287 if ((revents & POLLHUP) && (f->state != FILE_ZOMB)) 288 f->ops->hup(f->arg); 289 if ((revents & POLLIN) && (f->state != FILE_ZOMB)) 290 f->ops->in(f->arg); 291 if ((revents & POLLOUT) && (f->state != FILE_ZOMB)) 292 f->ops->out(f->arg); 293 #ifdef DEBUG 294 if (log_level >= 3) { 295 clock_gettime(CLOCK_UPTIME, &ts1); 296 us = 1000000L * (ts1.tv_sec - ts0.tv_sec); 297 us += (ts1.tv_nsec - ts0.tv_nsec) / 1000; 298 if (log_level >= 4 || us >= 5000) { 299 file_log(f); 300 log_puts(": processed in "); 301 log_putu(us); 302 log_puts("us\n"); 303 } 304 } 305 #endif 306 } 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 int i; 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 if (log_level >= 3) 336 log_puts("nothing to do...\n"); 337 #endif 338 return 0; 339 } 340 341 /* 342 * fill pollfd structures 343 */ 344 nfds = 0; 345 for (f = file_list; f != NULL; f = f->next) { 346 f->nfds = f->ops->pollfd(f->arg, pfds + nfds); 347 if (f->nfds == 0) 348 continue; 349 nfds += f->nfds; 350 } 351 #ifdef DEBUG 352 if (log_level >= 4) { 353 log_puts("poll:"); 354 pfd = pfds; 355 for (f = file_list; f != NULL; f = f->next) { 356 log_puts(" "); 357 log_puts(f->ops->name); 358 log_puts(":"); 359 for (i = 0; i < f->nfds; i++) { 360 log_puts(" "); 361 log_putx(pfd->events); 362 pfd++; 363 } 364 } 365 log_puts("\n"); 366 } 367 #endif 368 369 /* 370 * process files that do not rely on poll 371 */ 372 for (f = file_list; f != NULL; f = f->next) { 373 if (f->nfds > 0) 374 continue; 375 file_process(f, NULL); 376 } 377 378 /* 379 * Sleep. Calculate the number off milliseconds poll(2) must 380 * wait before the timo_update() needs to be called. If there're 381 * no timeouts scheduled, then call poll(2) with infinite 382 * timeout (i.e -1). 383 */ 384 #ifdef DEBUG 385 clock_gettime(CLOCK_UPTIME, &sleepts); 386 file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec); 387 file_utime += sleepts.tv_nsec - file_ts.tv_nsec; 388 #endif 389 if (timo_queue != NULL) { 390 timo = ((int)timo_queue->val - (int)timo_abstime) / 1000; 391 if (timo < TIMER_MSEC) 392 timo = TIMER_MSEC; 393 } else 394 timo = -1; 395 log_flush(); 396 res = poll(pfds, nfds, timo); 397 if (res < 0) { 398 if (errno != EINTR) { 399 log_puts("poll failed"); 400 panic(); 401 } 402 return 1; 403 } 404 405 /* 406 * run timeouts 407 */ 408 clock_gettime(CLOCK_UPTIME, &ts); 409 #ifdef DEBUG 410 file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec); 411 file_wtime += ts.tv_nsec - sleepts.tv_nsec; 412 #endif 413 if (timo_queue) { 414 delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec); 415 delta_nsec += ts.tv_nsec - file_ts.tv_nsec; 416 if (delta_nsec >= 0 && delta_nsec < 60000000000LL) 417 timo_update(delta_nsec / 1000); 418 else { 419 if (log_level >= 2) 420 log_puts("out-of-bounds clock delta\n"); 421 } 422 } 423 file_ts = ts; 424 425 /* 426 * process files that rely on poll 427 */ 428 pfd = pfds; 429 for (f = file_list; f != NULL; f = f->next) { 430 if (f->nfds == 0) 431 continue; 432 file_process(f, pfd); 433 pfd += f->nfds; 434 } 435 return 1; 436 } 437 438 void 439 filelist_init(void) 440 { 441 sigset_t set; 442 443 if (clock_gettime(CLOCK_UPTIME, &file_ts) < 0) { 444 log_puts("filelist_init: CLOCK_UPTIME unsupported\n"); 445 panic(); 446 } 447 sigemptyset(&set); 448 sigaddset(&set, SIGPIPE); 449 sigprocmask(SIG_BLOCK, &set, NULL); 450 file_list = NULL; 451 log_sync = 0; 452 timo_init(); 453 } 454 455 void 456 filelist_done(void) 457 { 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 timo_done(); 472 } 473