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