/*************************************************************************** * Copyright (C) 10/2006 by Olaf Rempel * * razzor@kopf-tisch.de * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "list.h" #include "logging.h" #include "event.h" #define FD_TYPE (FD_READ | FD_WRITE | FD_EXCEPT) #define FD_NEW 0x10 #define FD_DELETE 0x20 static LIST_HEAD(fd_list); static LIST_HEAD(timeout_list); struct fd_entry { struct list_head list; int fd; int flags; int (*callback)(int fd, void *privdata); void *privdata; }; struct timeout_entry { struct list_head list; struct timeval timeout; struct timeval nextrun; int (*callback)(void *privdata); void *privdata; }; int event_add_fd(int fd, int type, int (*callback)(int fd, void *privdata), void *privdata) { if (fd < 0 || fd > FD_SETSIZE || type == 0) return -1; struct fd_entry *entry; entry = malloc(sizeof(struct fd_entry)); if (entry == NULL) { log_print(LOG_ERROR, "event_add_fd(): out of memory"); return -1; } entry->fd = fd; entry->flags = (type & FD_TYPE) | FD_NEW; entry->callback = callback; entry->privdata = privdata; list_add_tail(&entry->list, &fd_list); return 0; } int event_remove_fd(int fd) { struct fd_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &fd_list, list) { if (entry->fd == fd) { entry->flags |= FD_DELETE; return 0; } } return -1; } static void calc_nextrun(struct timeval *timeout, struct timeval *nextrun) { struct timeval now; gettimeofday(&now, NULL); nextrun->tv_usec = now.tv_usec + timeout->tv_usec; nextrun->tv_sec = now.tv_sec + timeout->tv_sec; if (nextrun->tv_usec >= 1000000) { nextrun->tv_usec -= 1000000; nextrun->tv_sec++; } } static void calc_timeout(struct timeval *timeout, struct timeval *nextrun) { struct timeval now; gettimeofday(&now, NULL); timeout->tv_usec = nextrun->tv_usec - now.tv_usec; timeout->tv_sec = nextrun->tv_sec - now.tv_sec; if (timeout->tv_usec < 0) { timeout->tv_usec += 1000000; timeout->tv_sec--; } } static void schedule_nextrun(struct timeout_entry *entry) { struct timeout_entry *search; list_for_each_entry(search, &timeout_list, list) { if (search->nextrun.tv_sec > entry->nextrun.tv_sec) { list_add_tail(&entry->list, &search->list); return; } else if (search->nextrun.tv_sec == entry->nextrun.tv_sec && search->nextrun.tv_usec > entry->nextrun.tv_usec) { list_add_tail(&entry->list, &search->list); return; } } list_add_tail(&entry->list, &timeout_list); } int event_add_timeout(struct timeval *timeout, int (*callback)(void *privdata), void *privdata) { struct timeout_entry *entry; entry = malloc(sizeof(struct timeout_entry)); if (entry == NULL) { log_print(LOG_ERROR, "event_add_timeout(): out of memory"); return -1; } memcpy(&entry->timeout, timeout, sizeof(entry->timeout)); entry->callback = callback; entry->privdata = privdata; calc_nextrun(&entry->timeout, &entry->nextrun); schedule_nextrun(entry); return 0; } int event_loop(void) { fd_set *fdsets = malloc(sizeof(fd_set) * 3); if (fdsets == NULL) { log_print(LOG_ERROR, "event_loop(): out of memory"); return -1; } while (1) { fd_set *readfds = NULL, *writefds = NULL, *exceptfds = NULL; struct fd_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &fd_list, list) { entry->flags &= ~FD_NEW; if (entry->flags & FD_DELETE) { log_print(LOG_DEBUG, "event_loop: delete FD: %d (%p/%p)", entry->fd, entry->callback, entry->privdata); list_del(&entry->list); free(entry); } else if ((entry->flags & FD_READ) != 0) { if (readfds == NULL) { readfds = &fdsets[0]; FD_ZERO(readfds); } log_print(LOG_DEBUG, "event_loop: read FD: %d (%p/%p)", entry->fd, entry->callback, entry->privdata); FD_SET(entry->fd, readfds); } else if ((entry->flags & FD_WRITE) != 0) { if (writefds == NULL) { writefds = &fdsets[1]; FD_ZERO(writefds); } log_print(LOG_DEBUG, "event_loop: write FD: %d (%p/%p)", entry->fd, entry->callback, entry->privdata); FD_SET(entry->fd, writefds); } else if ((entry->flags & FD_EXCEPT) != 0) { if (exceptfds == NULL) { exceptfds = &fdsets[2]; FD_ZERO(exceptfds); } log_print(LOG_DEBUG, "event_loop: except FD: %d (%p/%p)", entry->fd, entry->callback, entry->privdata); FD_SET(entry->fd, exceptfds); } } struct timeval timeout, *timeout_p = NULL; if (!list_empty(&timeout_list)) { struct timeout_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &timeout_list, list) { calc_timeout(&timeout, &entry->nextrun); if (timeout.tv_sec >= 0 && timeout.tv_usec > 0) { timeout_p = &timeout; break; } // delayed timeout, exec NOW! list_del(&entry->list); int ret = entry->callback(entry->privdata); if (ret == 0) { calc_nextrun(&entry->timeout, &entry->nextrun); schedule_nextrun(entry); } else { free(entry); } } } log_print(LOG_DEBUG, ""); int i = select(FD_SETSIZE, readfds, writefds, exceptfds, timeout_p); if (i < 0) { /* On error, -1 is returned, and errno is set * appropriately; the sets and timeout become * undefined, so do not rely on their contents * after an error. */ continue; } else if (i == 0 && !list_empty(&timeout_list)) { struct timeout_entry *entry; entry = list_entry(timeout_list.next, typeof(*entry), list); list_del(&entry->list); int ret = entry->callback(entry->privdata); if (ret == 0) { calc_nextrun(&entry->timeout, &entry->nextrun); schedule_nextrun(entry); } else { free(entry); } } else { list_for_each_entry(entry, &fd_list, list) { if ((entry->flags & FD_NEW) != 0) { continue; } else if ((entry->flags & FD_READ) != 0) { if (!FD_ISSET(entry->fd, readfds)) continue; } else if ((entry->flags & FD_WRITE) != 0) { if (!FD_ISSET(entry->fd, writefds)) continue; } else if ((entry->flags & FD_EXCEPT) != 0) { if (!FD_ISSET(entry->fd, exceptfds)) continue; } if (entry->callback(entry->fd, entry->privdata) != 0) entry->flags |= FD_DELETE; } } } free(fdsets); }