/*************************************************************************** * 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" static LIST_HEAD(readfd_list); static LIST_HEAD(writefd_list); static LIST_HEAD(exceptfd_list); static LIST_HEAD(timeout_list); struct fd_entry { struct list_head list; int fd; int type; 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) 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->type = type; entry->callback = callback; entry->privdata = privdata; switch (type) { case FD_READ: list_add_tail(&entry->list, &readfd_list); break; case FD_WRITE: list_add_tail(&entry->list, &writefd_list); break; case FD_EXCEPT: list_add_tail(&entry->list, &exceptfd_list); break; default: log_print(LOG_ERROR, "add_fd(): unknown type"); free(entry); return -1; } return 0; } int event_remove_fd(int fd) { struct fd_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &readfd_list, list) { if (entry->fd == fd) { list_del(&entry->list); free(entry); return 0; } } list_for_each_entry_safe(entry, tmp, &writefd_list, list) { if (entry->fd == fd) { list_del(&entry->list); free(entry); return 0; } } list_for_each_entry_safe(entry, tmp, &exceptfd_list, list) { if (entry->fd == fd) { list_del(&entry->list); free(entry); 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) { while (1) { fd_set readfds, *readfds_p = NULL; fd_set writefds, *writefds_p = NULL; fd_set exceptfds, *exceptfds_p =NULL; struct timeval timeout, *timeout_p = NULL; if (!list_empty(&readfd_list)) { struct fd_entry *entry; FD_ZERO(&readfds); list_for_each_entry(entry, &readfd_list, list) FD_SET(entry->fd, &readfds); readfds_p = &readfds; } if (!list_empty(&writefd_list)) { struct fd_entry *entry; FD_ZERO(&writefds); list_for_each_entry(entry, &writefd_list, list) FD_SET(entry->fd, &writefds); writefds_p = &writefds; } if (!list_empty(&exceptfd_list)) { struct fd_entry *entry; FD_ZERO(&exceptfds); list_for_each_entry(entry, &exceptfd_list, list) FD_SET(entry->fd, &exceptfds); exceptfds_p = &exceptfds; } 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); } } } int i = select(FD_SETSIZE, readfds_p, writefds_p, exceptfds_p, 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); } } if (readfds_p) { struct fd_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &readfd_list, list) { if (!FD_ISSET(entry->fd, &readfds)) continue; if (entry->callback(entry->fd, entry->privdata) != 0) { list_del(&entry->list); free(entry); } } } if (writefds_p) { struct fd_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &writefd_list, list) { if (FD_ISSET(entry->fd, &writefds)) continue; if (entry->callback(entry->fd, entry->privdata) != 0) { list_del(&entry->list); free(entry); } } } if (exceptfds_p) { struct fd_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &exceptfd_list, list) { if (FD_ISSET(entry->fd, &exceptfds)) continue; if (entry->callback(entry->fd, entry->privdata) != 0) { list_del(&entry->list); free(entry); } } } } }