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