1 /* SPDX-License-Identifier: BSD-2-Clause */ 2 /* 3 * eloop - portable event based main loop. 4 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name> 5 * All rights reserved. 6 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/time.h> 30 31 #include <assert.h> 32 #include <errno.h> 33 #include <limits.h> 34 #include <poll.h> 35 #include <signal.h> 36 #include <stdint.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <unistd.h> 40 41 /* config.h should define HAVE_PPOLL, etc. */ 42 #if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H) 43 #include "config.h" 44 #endif 45 46 #if defined(HAVE_PPOLL) 47 #elif defined(HAVE_POLLTS) 48 #define ppoll pollts 49 #elif !defined(HAVE_PSELECT) 50 #pragma message("Compiling eloop with pselect(2) support.") 51 #define HAVE_PSELECT 52 #define ppoll eloop_ppoll 53 #endif 54 55 #include "eloop.h" 56 57 #ifndef UNUSED 58 #define UNUSED(a) (void)((a)) 59 #endif 60 #ifndef __unused 61 #ifdef __GNUC__ 62 #define __unused __attribute__((__unused__)) 63 #else 64 #define __unused 65 #endif 66 #endif 67 68 #ifdef HAVE_PSELECT 69 #include <sys/select.h> 70 #endif 71 72 /* Our structures require TAILQ macros, which really every libc should 73 * ship as they are useful beyond belief. 74 * Sadly some libc's don't have sys/queue.h and some that do don't have 75 * the TAILQ_FOREACH macro. For those that don't, the application using 76 * this implementation will need to ship a working queue.h somewhere. 77 * If we don't have sys/queue.h found in config.h, then 78 * allow QUEUE_H to override loading queue.h in the current directory. */ 79 #ifndef TAILQ_FOREACH 80 #ifdef HAVE_SYS_QUEUE_H 81 #include <sys/queue.h> 82 #elif defined(QUEUE_H) 83 #define __QUEUE_HEADER(x) #x 84 #define _QUEUE_HEADER(x) __QUEUE_HEADER(x) 85 #include _QUEUE_HEADER(QUEUE_H) 86 #else 87 #include "queue.h" 88 #endif 89 #endif 90 91 #ifdef ELOOP_DEBUG 92 #include <stdio.h> 93 #endif 94 95 /* 96 * Allow a backlog of signals. 97 * If you use many eloops in the same process, they should all 98 * use the same signal handler or have the signal handler unset. 99 * Otherwise the signal might not behave as expected. 100 */ 101 #define ELOOP_NSIGNALS 5 102 103 /* 104 * time_t is a signed integer of an unspecified size. 105 * To adjust for time_t wrapping, we need to work the maximum signed 106 * value and use that as a maximum. 107 */ 108 #ifndef TIME_MAX 109 #define TIME_MAX ((1ULL << (sizeof(time_t) * NBBY - 1)) - 1) 110 #endif 111 /* The unsigned maximum is then simple - multiply by two and add one. */ 112 #ifndef UTIME_MAX 113 #define UTIME_MAX (TIME_MAX * 2) + 1 114 #endif 115 116 struct eloop_event { 117 TAILQ_ENTRY(eloop_event) next; 118 int fd; 119 void (*read_cb)(void *); 120 void *read_cb_arg; 121 void (*write_cb)(void *); 122 void *write_cb_arg; 123 struct pollfd *pollfd; 124 }; 125 126 struct eloop_timeout { 127 TAILQ_ENTRY(eloop_timeout) next; 128 unsigned int seconds; 129 unsigned int nseconds; 130 void (*callback)(void *); 131 void *arg; 132 int queue; 133 }; 134 135 struct eloop { 136 TAILQ_HEAD (event_head, eloop_event) events; 137 size_t nevents; 138 struct event_head free_events; 139 140 struct timespec now; 141 TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; 142 struct timeout_head free_timeouts; 143 144 const int *signals; 145 size_t signals_len; 146 void (*signal_cb)(int, void *); 147 void *signal_cb_ctx; 148 149 struct pollfd *fds; 150 size_t nfds; 151 152 int exitnow; 153 int exitcode; 154 }; 155 156 #ifdef HAVE_REALLOCARRAY 157 #define eloop_realloca reallocarray 158 #else 159 /* Handy routing to check for potential overflow. 160 * reallocarray(3) and reallocarr(3) are not portable. */ 161 #define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2)) 162 static void * 163 eloop_realloca(void *ptr, size_t n, size_t size) 164 { 165 166 if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) { 167 errno = EOVERFLOW; 168 return NULL; 169 } 170 return realloc(ptr, n * size); 171 } 172 #endif 173 174 #ifdef HAVE_PSELECT 175 /* Wrapper around pselect, to imitate the ppoll call. */ 176 static int 177 eloop_ppoll(struct pollfd * fds, nfds_t nfds, 178 const struct timespec *ts, const sigset_t *sigmask) 179 { 180 fd_set read_fds, write_fds; 181 nfds_t n; 182 int maxfd, r; 183 184 FD_ZERO(&read_fds); 185 FD_ZERO(&write_fds); 186 maxfd = 0; 187 for (n = 0; n < nfds; n++) { 188 if (fds[n].events & POLLIN) { 189 FD_SET(fds[n].fd, &read_fds); 190 if (fds[n].fd > maxfd) 191 maxfd = fds[n].fd; 192 } 193 if (fds[n].events & POLLOUT) { 194 FD_SET(fds[n].fd, &write_fds); 195 if (fds[n].fd > maxfd) 196 maxfd = fds[n].fd; 197 } 198 } 199 200 r = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask); 201 if (r > 0) { 202 for (n = 0; n < nfds; n++) { 203 fds[n].revents = 204 FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0; 205 if (FD_ISSET(fds[n].fd, &write_fds)) 206 fds[n].revents |= POLLOUT; 207 } 208 } 209 210 return r; 211 } 212 #endif 213 214 unsigned long long 215 eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp, 216 unsigned int *nsp) 217 { 218 unsigned long long tsecs, usecs, secs; 219 long nsecs; 220 221 if (tsp->tv_sec < 0) /* time wreapped */ 222 tsecs = UTIME_MAX - (unsigned long long)(-tsp->tv_sec); 223 else 224 tsecs = (unsigned long long)tsp->tv_sec; 225 if (usp->tv_sec < 0) /* time wrapped */ 226 usecs = UTIME_MAX - (unsigned long long)(-usp->tv_sec); 227 else 228 usecs = (unsigned long long)usp->tv_sec; 229 230 if (usecs > tsecs) /* time wrapped */ 231 secs = (UTIME_MAX - usecs) + tsecs; 232 else 233 secs = tsecs - usecs; 234 235 nsecs = tsp->tv_nsec - usp->tv_nsec; 236 if (nsecs < 0) { 237 if (secs == 0) 238 nsecs = 0; 239 else { 240 secs--; 241 nsecs += NSEC_PER_SEC; 242 } 243 } 244 if (nsp != NULL) 245 *nsp = (unsigned int)nsecs; 246 return secs; 247 } 248 249 static void 250 eloop_reduce_timers(struct eloop *eloop) 251 { 252 struct timespec now; 253 unsigned long long secs; 254 unsigned int nsecs; 255 struct eloop_timeout *t; 256 257 clock_gettime(CLOCK_MONOTONIC, &now); 258 secs = eloop_timespec_diff(&now, &eloop->now, &nsecs); 259 260 TAILQ_FOREACH(t, &eloop->timeouts, next) { 261 if (secs > t->seconds) { 262 t->seconds = 0; 263 t->nseconds = 0; 264 } else { 265 t->seconds -= (unsigned int)secs; 266 if (nsecs > t->nseconds) { 267 if (t->seconds == 0) 268 t->nseconds = 0; 269 else { 270 t->seconds--; 271 t->nseconds = NSEC_PER_SEC 272 - (nsecs - t->nseconds); 273 } 274 } else 275 t->nseconds -= nsecs; 276 } 277 } 278 279 eloop->now = now; 280 } 281 282 static void 283 eloop_event_setup_fds(struct eloop *eloop) 284 { 285 struct eloop_event *e; 286 struct pollfd *pfd; 287 288 pfd = eloop->fds; 289 TAILQ_FOREACH(e, &eloop->events, next) { 290 #ifdef ELOOP_DEBUG 291 fprintf(stderr, "%s(%d) fd=%d, rcb=%p, wcb=%p\n", 292 __func__, getpid(), e->fd, e->read_cb, e->write_cb); 293 #endif 294 e->pollfd = pfd; 295 pfd->fd = e->fd; 296 pfd->events = 0; 297 if (e->read_cb != NULL) 298 pfd->events |= POLLIN; 299 if (e->write_cb != NULL) 300 pfd->events |= POLLOUT; 301 pfd->revents = 0; 302 pfd++; 303 } 304 } 305 306 size_t 307 eloop_event_count(const struct eloop *eloop) 308 { 309 310 return eloop->nevents; 311 } 312 313 int 314 eloop_event_add_rw(struct eloop *eloop, int fd, 315 void (*read_cb)(void *), void *read_cb_arg, 316 void (*write_cb)(void *), void *write_cb_arg) 317 { 318 struct eloop_event *e; 319 struct pollfd *pfd; 320 321 assert(eloop != NULL); 322 assert(read_cb != NULL || write_cb != NULL); 323 if (fd == -1) { 324 errno = EINVAL; 325 return -1; 326 } 327 328 TAILQ_FOREACH(e, &eloop->events, next) { 329 if (e->fd == fd) 330 break; 331 } 332 333 if (e == NULL) { 334 if (eloop->nevents + 1 > eloop->nfds) { 335 pfd = eloop_realloca(eloop->fds, eloop->nevents + 1, 336 sizeof(*pfd)); 337 if (pfd == NULL) 338 return -1; 339 eloop->fds = pfd; 340 eloop->nfds++; 341 } 342 343 e = TAILQ_FIRST(&eloop->free_events); 344 if (e != NULL) 345 TAILQ_REMOVE(&eloop->free_events, e, next); 346 else { 347 e = malloc(sizeof(*e)); 348 if (e == NULL) 349 return -1; 350 } 351 TAILQ_INSERT_HEAD(&eloop->events, e, next); 352 eloop->nevents++; 353 e->fd = fd; 354 e->read_cb = read_cb; 355 e->read_cb_arg = read_cb_arg; 356 e->write_cb = write_cb; 357 e->write_cb_arg = write_cb_arg; 358 goto setup; 359 } 360 361 if (read_cb) { 362 e->read_cb = read_cb; 363 e->read_cb_arg = read_cb_arg; 364 } 365 if (write_cb) { 366 e->write_cb = write_cb; 367 e->write_cb_arg = write_cb_arg; 368 } 369 370 setup: 371 eloop_event_setup_fds(eloop); 372 return 0; 373 } 374 375 int 376 eloop_event_add(struct eloop *eloop, int fd, 377 void (*read_cb)(void *), void *read_cb_arg) 378 { 379 380 return eloop_event_add_rw(eloop, fd, read_cb, read_cb_arg, NULL, NULL); 381 } 382 383 int 384 eloop_event_add_w(struct eloop *eloop, int fd, 385 void (*write_cb)(void *), void *write_cb_arg) 386 { 387 388 return eloop_event_add_rw(eloop, fd, NULL,NULL, write_cb, write_cb_arg); 389 } 390 391 int 392 eloop_event_delete_write(struct eloop *eloop, int fd, int write_only) 393 { 394 struct eloop_event *e; 395 396 assert(eloop != NULL); 397 398 TAILQ_FOREACH(e, &eloop->events, next) { 399 if (e->fd == fd) 400 break; 401 } 402 if (e == NULL) { 403 errno = ENOENT; 404 return -1; 405 } 406 407 if (write_only) { 408 if (e->read_cb == NULL) 409 goto remove; 410 e->write_cb = NULL; 411 e->write_cb_arg = NULL; 412 goto done; 413 } 414 415 remove: 416 TAILQ_REMOVE(&eloop->events, e, next); 417 TAILQ_INSERT_TAIL(&eloop->free_events, e, next); 418 eloop->nevents--; 419 420 done: 421 eloop_event_setup_fds(eloop); 422 return 1; 423 } 424 425 /* 426 * This implementation should cope with UINT_MAX seconds on a system 427 * where time_t is INT32_MAX. It should also cope with the monotonic timer 428 * wrapping, although this is highly unlikely. 429 * unsigned int should match or be greater than any on wire specified timeout. 430 */ 431 static int 432 eloop_q_timeout_add(struct eloop *eloop, int queue, 433 unsigned int seconds, unsigned int nseconds, 434 void (*callback)(void *), void *arg) 435 { 436 struct eloop_timeout *t, *tt = NULL; 437 438 assert(eloop != NULL); 439 assert(callback != NULL); 440 assert(nseconds <= NSEC_PER_SEC); 441 442 /* Remove existing timeout if present. */ 443 TAILQ_FOREACH(t, &eloop->timeouts, next) { 444 if (t->callback == callback && t->arg == arg) { 445 TAILQ_REMOVE(&eloop->timeouts, t, next); 446 break; 447 } 448 } 449 450 if (t == NULL) { 451 /* No existing, so allocate or grab one from the free pool. */ 452 if ((t = TAILQ_FIRST(&eloop->free_timeouts))) { 453 TAILQ_REMOVE(&eloop->free_timeouts, t, next); 454 } else { 455 if ((t = malloc(sizeof(*t))) == NULL) 456 return -1; 457 } 458 } 459 460 eloop_reduce_timers(eloop); 461 462 t->seconds = seconds; 463 t->nseconds = nseconds; 464 t->callback = callback; 465 t->arg = arg; 466 t->queue = queue; 467 468 /* The timeout list should be in chronological order, 469 * soonest first. */ 470 TAILQ_FOREACH(tt, &eloop->timeouts, next) { 471 if (t->seconds < tt->seconds || 472 (t->seconds == tt->seconds && t->nseconds < tt->nseconds)) 473 { 474 TAILQ_INSERT_BEFORE(tt, t, next); 475 return 0; 476 } 477 } 478 TAILQ_INSERT_TAIL(&eloop->timeouts, t, next); 479 return 0; 480 } 481 482 int 483 eloop_q_timeout_add_tv(struct eloop *eloop, int queue, 484 const struct timespec *when, void (*callback)(void *), void *arg) 485 { 486 487 if (when->tv_sec < 0 || (unsigned long)when->tv_sec > UINT_MAX) { 488 errno = EINVAL; 489 return -1; 490 } 491 if (when->tv_nsec < 0 || when->tv_nsec > NSEC_PER_SEC) { 492 errno = EINVAL; 493 return -1; 494 } 495 496 return eloop_q_timeout_add(eloop, queue, 497 (unsigned int)when->tv_sec, (unsigned int)when->tv_sec, 498 callback, arg); 499 } 500 501 int 502 eloop_q_timeout_add_sec(struct eloop *eloop, int queue, unsigned int seconds, 503 void (*callback)(void *), void *arg) 504 { 505 506 return eloop_q_timeout_add(eloop, queue, seconds, 0, callback, arg); 507 } 508 509 int 510 eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when, 511 void (*callback)(void *), void *arg) 512 { 513 unsigned long seconds, nseconds; 514 515 seconds = when / MSEC_PER_SEC; 516 if (seconds > UINT_MAX) { 517 errno = EINVAL; 518 return -1; 519 } 520 521 nseconds = (when % MSEC_PER_SEC) * NSEC_PER_MSEC; 522 return eloop_q_timeout_add(eloop, queue, 523 (unsigned int)seconds, (unsigned int)nseconds, callback, arg); 524 } 525 526 int 527 eloop_q_timeout_delete(struct eloop *eloop, int queue, 528 void (*callback)(void *), void *arg) 529 { 530 struct eloop_timeout *t, *tt; 531 int n; 532 533 assert(eloop != NULL); 534 535 n = 0; 536 TAILQ_FOREACH_SAFE(t, &eloop->timeouts, next, tt) { 537 if ((queue == 0 || t->queue == queue) && 538 t->arg == arg && 539 (!callback || t->callback == callback)) 540 { 541 TAILQ_REMOVE(&eloop->timeouts, t, next); 542 TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); 543 n++; 544 } 545 } 546 return n; 547 } 548 549 void 550 eloop_exit(struct eloop *eloop, int code) 551 { 552 553 assert(eloop != NULL); 554 555 eloop->exitcode = code; 556 eloop->exitnow = 1; 557 } 558 559 void 560 eloop_enter(struct eloop *eloop) 561 { 562 563 eloop->exitnow = 0; 564 } 565 566 void 567 eloop_signal_set_cb(struct eloop *eloop, 568 const int *signals, size_t signals_len, 569 void (*signal_cb)(int, void *), void *signal_cb_ctx) 570 { 571 572 assert(eloop != NULL); 573 574 eloop->signals = signals; 575 eloop->signals_len = signals_len; 576 eloop->signal_cb = signal_cb; 577 eloop->signal_cb_ctx = signal_cb_ctx; 578 } 579 580 static volatile int _eloop_sig[ELOOP_NSIGNALS]; 581 static volatile size_t _eloop_nsig; 582 583 static void 584 eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg) 585 { 586 587 if (_eloop_nsig == __arraycount(_eloop_sig)) { 588 #ifdef ELOOP_DEBUG 589 fprintf(stderr, "%s: signal storm, discarding signal %d\n", 590 __func__, sig); 591 #endif 592 return; 593 } 594 595 _eloop_sig[_eloop_nsig++] = sig; 596 } 597 598 int 599 eloop_signal_mask(struct eloop *eloop, sigset_t *oldset) 600 { 601 sigset_t newset; 602 size_t i; 603 struct sigaction sa = { 604 .sa_sigaction = eloop_signal3, 605 .sa_flags = SA_SIGINFO, 606 }; 607 608 assert(eloop != NULL); 609 610 sigemptyset(&newset); 611 for (i = 0; i < eloop->signals_len; i++) 612 sigaddset(&newset, eloop->signals[i]); 613 if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) 614 return -1; 615 616 sigemptyset(&sa.sa_mask); 617 618 for (i = 0; i < eloop->signals_len; i++) { 619 if (sigaction(eloop->signals[i], &sa, NULL) == -1) 620 return -1; 621 } 622 return 0; 623 } 624 625 struct eloop * 626 eloop_new(void) 627 { 628 struct eloop *eloop; 629 630 eloop = calloc(1, sizeof(*eloop)); 631 if (eloop == NULL) 632 return NULL; 633 634 /* Check we have a working monotonic clock. */ 635 if (clock_gettime(CLOCK_MONOTONIC, &eloop->now) == -1) { 636 free(eloop); 637 return NULL; 638 } 639 640 TAILQ_INIT(&eloop->events); 641 TAILQ_INIT(&eloop->free_events); 642 TAILQ_INIT(&eloop->timeouts); 643 TAILQ_INIT(&eloop->free_timeouts); 644 eloop->exitcode = EXIT_FAILURE; 645 646 return eloop; 647 } 648 649 void 650 eloop_clear(struct eloop *eloop) 651 { 652 struct eloop_event *e; 653 struct eloop_timeout *t; 654 655 if (eloop == NULL) 656 return; 657 658 eloop->nevents = 0; 659 eloop->signals = NULL; 660 eloop->signals_len = 0; 661 662 while ((e = TAILQ_FIRST(&eloop->events))) { 663 TAILQ_REMOVE(&eloop->events, e, next); 664 free(e); 665 } 666 while ((e = TAILQ_FIRST(&eloop->free_events))) { 667 TAILQ_REMOVE(&eloop->free_events, e, next); 668 free(e); 669 } 670 while ((t = TAILQ_FIRST(&eloop->timeouts))) { 671 TAILQ_REMOVE(&eloop->timeouts, t, next); 672 free(t); 673 } 674 while ((t = TAILQ_FIRST(&eloop->free_timeouts))) { 675 TAILQ_REMOVE(&eloop->free_timeouts, t, next); 676 free(t); 677 } 678 679 free(eloop->fds); 680 eloop->fds = NULL; 681 eloop->nfds = 0; 682 } 683 684 void 685 eloop_free(struct eloop *eloop) 686 { 687 688 eloop_clear(eloop); 689 free(eloop); 690 } 691 692 int 693 eloop_start(struct eloop *eloop, sigset_t *signals) 694 { 695 int n; 696 struct eloop_event *e; 697 struct eloop_timeout *t; 698 struct timespec ts, *tsp; 699 700 assert(eloop != NULL); 701 702 for (;;) { 703 if (eloop->exitnow) 704 break; 705 706 if (_eloop_nsig != 0) { 707 n = _eloop_sig[--_eloop_nsig]; 708 if (eloop->signal_cb != NULL) 709 eloop->signal_cb(n, eloop->signal_cb_ctx); 710 continue; 711 } 712 713 t = TAILQ_FIRST(&eloop->timeouts); 714 if (t == NULL && eloop->nevents == 0) 715 break; 716 717 if (t != NULL) 718 eloop_reduce_timers(eloop); 719 720 if (t != NULL && t->seconds == 0 && t->nseconds == 0) { 721 TAILQ_REMOVE(&eloop->timeouts, t, next); 722 t->callback(t->arg); 723 TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); 724 continue; 725 } 726 727 if (t != NULL) { 728 if (t->seconds > INT_MAX) { 729 ts.tv_sec = (time_t)INT_MAX; 730 ts.tv_nsec = 0; 731 } else { 732 ts.tv_sec = (time_t)t->seconds; 733 ts.tv_nsec = (long)t->nseconds; 734 } 735 tsp = &ts; 736 } else 737 tsp = NULL; 738 739 n = ppoll(eloop->fds, (nfds_t)eloop->nevents, tsp, signals); 740 if (n == -1) { 741 if (errno == EINTR) 742 continue; 743 return -errno; 744 } 745 if (n == 0) 746 continue; 747 748 TAILQ_FOREACH(e, &eloop->events, next) { 749 if (e->pollfd->revents & POLLOUT) { 750 if (e->write_cb != NULL) { 751 e->write_cb(e->write_cb_arg); 752 break; 753 } 754 } 755 if (e->pollfd->revents) { 756 if (e->read_cb != NULL) { 757 e->read_cb(e->read_cb_arg); 758 break; 759 } 760 } 761 } 762 } 763 764 return eloop->exitcode; 765 } 766