modified IOMultiplexer (added epoll & kevent support)
[TransparentIRC.git] / src / IOHandler.c
index 1843accb8f9cde95a5b25daab8827ce8677b3c10..7a9f3b3d30e7aa4b098c7c83cae4dd031dbbe3dc 100644 (file)
 #include "IOHandler.h"
 #include "IOEngine.h"
 
+#define MAXLOG 1024
+
 struct IODescriptor *first_descriptor = NULL;
+struct IODescriptor *timer_priority = NULL;
+
+void iohandler_log(enum IOLogType type, char *text, ...) {
+    va_list arg_list;
+    char logBuf[MAXLOG+1];
+    int pos;
+    logBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(logBuf, MAXLOG - 1, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLOG - 1)) pos = MAXLOG - 1;
+    logBuf[pos] = '\n';
+    logBuf[pos+1] = '\0';
+    
+    //do something with logBuf
+    //...
+}
 
 /* IO Engines */
 extern struct IOEngine engine_select; /* select system call (should always be useable) */
+extern struct IOEngine engine_kevent;
+extern struct IOEngine engine_epoll;
 
 struct IOEngine *engine = NULL;
 
 static void iohandler_init_engine() {
     //try other engines
+    if(!engine && engine_kevent.init && engine_kevent.init())
+        engine = &engine_kevent;
+    if(!engine && engine_epoll.init && engine_epoll.init())
+        engine = &engine_epoll;
     
-    if(engine) {
-        //found an useable IO engine
-    } else if (engine_select.init())
-        engine = &engine_select;
-    else {
-        //STDERR: found no useable IO engine
+    if (!engine) {
+        if(engine_select.init())
+            engine = &engine_select;
+        else {
+            iohandler_log(IOLOG_FATAL, "found no useable IO engine");
+            return;
+        }
+    }
+    iohandler_log(IOLOG_DEBUG, "using %s IO engine", engine->name);
+}
+
+static void iohandler_append(struct IODescriptor *descriptor) {
+    struct timeval *timeout = ((descriptor->timeout.tv_sec || descriptor->timeout.tv_usec) ? &descriptor->timeout : NULL);
+    if(timeout) {
+        struct IODescriptor *iofd;
+        int set_priority = 1;
+        descriptor->timeout = *timeout;
+        if(timer_priority)
+            iofd = timer_priority;
+        else
+            iofd = first_descriptor;
+        if(iofd) {
+            for(;;iofd = iofd->next) {
+                if(timeval_is_smaler(timeout, (&iofd->timeout))) {
+                    descriptor->prev = iofd->prev;
+                    descriptor->next = iofd;
+                    iofd->prev = descriptor;
+                    if(iofd->prev)
+                        iofd->prev->next = descriptor;
+                    if(set_priority)
+                        timer_priority = descriptor;
+                    break;
+                }
+                if(iofd == timer_priority)
+                    set_priority = 0;
+                if(iofd->next == NULL) {
+                    descriptor->next = NULL;
+                    descriptor->prev = iofd;
+                    iofd->next = descriptor;
+                    if(set_priority)
+                        timer_priority = descriptor;
+                    break;
+                }
+            }
+        } else {
+            descriptor->prev = NULL;
+            descriptor->next = NULL;
+            first_descriptor = descriptor;
+            timer_priority = descriptor;
+        }
+        
+    } else {
+        descriptor->prev = NULL;
+        descriptor->next = first_descriptor;
+        if(first_descriptor)
+            first_descriptor->prev = descriptor;
+        first_descriptor = descriptor;
     }
 }
 
-struct IODescriptor *iohandler_add(int sockfd, enum IOType type, iohandler_callback *callback) {
+static void iohandler_remove(struct IODescriptor *descriptor, int engine_remove) {
+    //remove IODescriptor from the list
+    if(descriptor->prev)
+        descriptor->prev->next = descriptor->next;
+    else
+        first_descriptor = descriptor->next;
+    if(descriptor->next)
+        descriptor->next->prev = descriptor->prev;
+    if(descriptor == timer_priority)
+        timer_priority = descriptor->next;
+    
+    if(engine_remove)
+        engine->remove(descriptor);
+    if(descriptor->readbuf.buffer)
+        free(descriptor->readbuf.buffer);
+    if(descriptor->writebuf.buffer)
+        free(descriptor->writebuf.buffer);
+    iohandler_log(IOLOG_DEBUG, "removed IODescriptor (%d) of type `%s`", descriptor->fd, iohandler_iotype_name(descriptor->type));
+    free(descriptor);
+}
+
+struct IODescriptor *iohandler_add(int sockfd, enum IOType type, struct timeval *timeout, iohandler_callback *callback) {
     //just add a new IODescriptor
     struct IODescriptor *descriptor = calloc(1, sizeof(*descriptor));
-    if(!descriptor) return NULL;
+    if(!descriptor) {
+        iohandler_log(IOLOG_ERROR, "could not allocate memory for IODescriptor in %s:%d", __FILE__, __LINE__);
+        return NULL;
+    }
     descriptor->fd = sockfd;
     descriptor->type = type;
     descriptor->state = IO_CLOSED;
     descriptor->callback = callback;
+    if(timeout)
+        descriptor->timeout = *timeout;
     if(type != IOTYPE_TIMER) {
         descriptor->readbuf.buffer = malloc(IO_READ_BUFLEN + 2);
         descriptor->readbuf.bufpos = 0;
@@ -62,30 +164,28 @@ struct IODescriptor *iohandler_add(int sockfd, enum IOType type, iohandler_callb
     engine->add(descriptor);
     
     //add IODescriptor to the list
-    descriptor->prev = NULL;
-    descriptor->next = first_descriptor;
-    if(first_descriptor)
-        first_descriptor->prev = descriptor;
-    first_descriptor = descriptor;
+    iohandler_append(descriptor);
     
+    iohandler_log(IOLOG_DEBUG, "added custom socket descriptor (%d) as type `%s`", sockfd, iohandler_iotype_name(type));
     return descriptor;
 }
 
-static void iohandler_remove(struct IODescriptor *descriptor, int engine_remove) {
-    //remove IODescriptor from the list
+void iohandler_set_timeout(struct IODescriptor *descriptor, struct timeval *timeout) {
     if(descriptor->prev)
         descriptor->prev->next = descriptor->next;
     else
         first_descriptor = descriptor->next;
     if(descriptor->next)
         descriptor->next->prev = descriptor->prev;
-    if(engine_remove)
-        engine->remove(descriptor);
-    if(descriptor->readbuf.buffer)
-        free(descriptor->readbuf.buffer);
-    if(descriptor->writebuf.buffer)
-        free(descriptor->writebuf.buffer);
-    free(descriptor);
+    if(descriptor == timer_priority)
+        timer_priority = descriptor->next;
+    if(timeout) 
+        descriptor->timeout = *timeout;
+    else {
+        descriptor->timeout.tv_sec = 0;
+        descriptor->timeout.tv_usec = 0;
+    }
+    iohandler_append(descriptor);
 }
 
 static void iohandler_increase_iobuf(struct IOBuffer *iobuf, size_t required) {
@@ -99,10 +199,12 @@ static void iohandler_increase_iobuf(struct IOBuffer *iobuf, size_t required) {
 
 struct IODescriptor *iohandler_timer(struct timeval timeout, iohandler_callback *callback) {
     struct IODescriptor *descriptor;
-    descriptor = iohandler_add(-1, IOTYPE_TIMER, callback);
-    if(!descriptor) return NULL;
-    descriptor->timeout = timeout;
-    engine->update(descriptor);
+    descriptor = iohandler_add(-1, IOTYPE_TIMER, &timeout, callback);
+    if(!descriptor) {
+        iohandler_log(IOLOG_ERROR, "could not allocate memory for IODescriptor in %s:%d", __FILE__, __LINE__);
+        return NULL;
+    }
+    iohandler_log(IOLOG_DEBUG, "added timer descriptor (sec: %d; usec: %d)", timeout.tv_sec, timeout.tv_usec);
     return descriptor;
 }
 
@@ -121,6 +223,7 @@ struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port,
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_flags |= AI_CANONNAME;
     if (getaddrinfo (hostname, NULL, &hints, &res)) {
+        iohandler_log(IOLOG_ERROR, "could not resolve %d to an IP address", hostname);
         return NULL;
     }
     while (res) {
@@ -139,7 +242,10 @@ struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port,
     
     if(ip6) {
         sockfd = socket(AF_INET6, SOCK_STREAM, 0);
-        if(sockfd == -1) return NULL;
+        if(sockfd == -1) {
+            iohandler_log(IOLOG_ERROR, "could not create socket in %s:%d", __FILE__, __LINE__);
+            return NULL;
+        }
         
         ip6->sin6_family = AF_INET6;
         ip6->sin6_port = htons(port);
@@ -166,7 +272,10 @@ struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port,
         dstaddrlen = sizeof(*ip6);
     } else if(ip4) {
         sockfd = socket(AF_INET, SOCK_STREAM, 0);
-        if(sockfd == -1) return NULL;
+        if(sockfd == -1) {
+            iohandler_log(IOLOG_ERROR, "could not create socket in %s:%d", __FILE__, __LINE__);
+            return NULL;
+        }
         
         ip4->sin_family = AF_INET;
         ip4->sin_port = htons(port);
@@ -195,16 +304,19 @@ struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port,
         return NULL;
     //make sockfd unblocking
 #if defined(F_GETFL)
-    flags = fcntl(sockfd, F_GETFL);
-    fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
-    flags = fcntl(sockfd, F_GETFD);
-    fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
+    {
+        int flags;
+        flags = fcntl(sockfd, F_GETFL);
+        fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
+        flags = fcntl(sockfd, F_GETFD);
+        fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
+    }
 #else
     /* I hope you're using the Win32 backend or something else that
      * automatically marks the file descriptor non-blocking...
      */
 #endif
-    descriptor = iohandler_add(sockfd, IOTYPE_CLIENT, callback);
+    descriptor = iohandler_add(sockfd, IOTYPE_CLIENT, NULL, callback);
     if(!descriptor) {
         close(sockfd);
         return NULL;
@@ -213,6 +325,7 @@ struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port,
     descriptor->state = IO_CONNECTING;
     descriptor->read_lines = 1;
     engine->update(descriptor);
+    iohandler_log(IOLOG_DEBUG, "added client socket (%d) connecting to %s:%d", sockfd, hostname, port);
     return descriptor;
 }
 
@@ -271,16 +384,19 @@ struct IODescriptor *iohandler_listen(const char *hostname, unsigned int port, i
         return NULL;
     //make sockfd unblocking
 #if defined(F_GETFL)
-    flags = fcntl(sockfd, F_GETFL);
-    fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
-    flags = fcntl(sockfd, F_GETFD);
-    fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
+    {
+        int flags;
+        flags = fcntl(sockfd, F_GETFL);
+        fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
+        flags = fcntl(sockfd, F_GETFD);
+        fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
+    }
 #else
     /* I hope you're using the Win32 backend or something else that
      * automatically marks the file descriptor non-blocking...
      */
 #endif
-    descriptor = iohandler_add(sockfd, IOTYPE_SERVER, callback);
+    descriptor = iohandler_add(sockfd, IOTYPE_SERVER, NULL, callback);
     if(!descriptor) {
         close(sockfd);
         return NULL;
@@ -288,6 +404,7 @@ struct IODescriptor *iohandler_listen(const char *hostname, unsigned int port, i
     listen(sockfd, 1);
     descriptor->state = IO_LISTENING;
     engine->update(descriptor);
+    iohandler_log(IOLOG_DEBUG, "added server socket (%d) listening on %s:%d", sockfd, hostname, port);
     return descriptor;
 }
 
@@ -297,11 +414,18 @@ void iohandler_write(struct IODescriptor *iofd, const char *line) {
 }
 
 void iohandler_send(struct IODescriptor *iofd, const char *data, size_t datalen) {
-    if(iofd->type == IOTYPE_TIMER || iofd->state == IO_CLOSED) return; //can not write to timer? :D
+    if(iofd->type == IOTYPE_TIMER || iofd->state == IO_CLOSED) {
+        iohandler_log(IOLOG_ERROR, "could not write to socket (%s)", (iofd->type == IOTYPE_TIMER ? "IOTYPE_TIMER" : "IO_CLOSED"));
+        return;
+    }
+    iohandler_log(IOLOG_DEBUG, "add %d to writebuf (fd: %d): %s", datalen, iofd->fd, data);
     if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen) {
+        iohandler_log(IOLOG_DEBUG, "increase writebuf (curr: %d) to %d (+%d bytes)", iofd->writebuf.buflen, iofd->writebuf.bufpos + datalen, (iofd->writebuf.bufpos + datalen - iofd->writebuf.buflen));
         iohandler_increase_iobuf(&iofd->writebuf, iofd->writebuf.bufpos + datalen);
-        if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen)
+        if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen) {
+            iohandler_log(IOLOG_ERROR, "increase writebuf (curr: %d) to %d (+%d bytes) FAILED", iofd->writebuf.buflen, iofd->writebuf.bufpos + datalen, (iofd->writebuf.bufpos + datalen - iofd->writebuf.buflen));
             return;
+        }
     }
     memcpy(iofd->writebuf.buffer + iofd->writebuf.bufpos, data, datalen);
     iofd->writebuf.bufpos += datalen;
@@ -324,26 +448,32 @@ void iohandler_printf(struct IODescriptor *iofd, const char *text, ...) {
 
 void iohandler_try_write(struct IODescriptor *iofd) {
     if(!iofd->writebuf.bufpos) return;
+    iohandler_log(IOLOG_DEBUG, "write writebuf (%d bytes) to socket (fd: %d)", iofd->writebuf.bufpos, iofd->fd);
     int res = send(iofd->fd, iofd->writebuf.buffer, iofd->writebuf.bufpos, 0);
     if(res < 0) {
         if (errno != EAGAIN) {
-            //error: could not write
+            iohandler_log(IOLOG_ERROR, "could not write to socket (fd: %d): %d - %s", iofd->fd, errno, strerror(errno));
         }
     } else {
         iofd->writebuf.bufpos -= res;
-        engine->update(iofd);
+        if(iofd->state != IO_CLOSED)
+            engine->update(iofd);
     }
 }
 
 void iohandler_close(struct IODescriptor *iofd) {
     int engine_remove = 1;
+    iofd->state = IO_CLOSED;
     if(iofd->writebuf.bufpos) {
         //try to send everything before closing
 #if defined(F_GETFL)
-        flags = fcntl(sockfd, F_GETFL);
-        fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
-        flags = fcntl(sockfd, F_GETFD);
-        fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
+        {
+            int flags;
+            flags = fcntl(iofd->fd, F_GETFL);
+            fcntl(iofd->fd, F_SETFL, flags & ~O_NONBLOCK);
+            flags = fcntl(iofd->fd, F_GETFD);
+            fcntl(iofd->fd, F_SETFD, flags|FD_CLOEXEC);
+        }
 #else
         engine_remove = 0;
         engine->remove(iofd);
@@ -357,11 +487,13 @@ void iohandler_close(struct IODescriptor *iofd) {
 }
 
 void iohandler_update(struct IODescriptor *iofd) {
+    iohandler_log(IOLOG_DEBUG, "external call to iohandler_update (fd: %d)", iofd->fd);
     engine->update(iofd);
 }
 
 static void iohandler_trigger_event(struct IOEvent *event) {
     if(!event->iofd->callback) return;
+    iohandler_log(IOLOG_DEBUG, "triggering event (%s) for %s (fd: %d)", iohandler_ioeventtype_name(event->type), iohandler_iotype_name(event->iofd->type), event->iofd->fd);
     event->iofd->callback(event);
 }
 
@@ -378,7 +510,7 @@ void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
             if(readable) {
                 callback_event.data.accept_fd = accept(iofd->fd, NULL, 0);
                 if(callback_event.data.accept_fd < 0) {
-                    //error: could not accept
+                    iohandler_log(IOLOG_ERROR, "could not accept client (server fd: %d): %d - %s", iofd->fd, errno, strerror(errno));
                 } else
                     callback_event.type = IOEVENT_ACCEPT;
             }
@@ -391,6 +523,7 @@ void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
                 if (getsockopt(iofd->fd, SOL_SOCKET, SO_ERROR, &callback_event.data.errid, &arglen) < 0)
                     callback_event.data.errid = errno;
                 iofd->state = IO_CLOSED;
+                               engine->update(iofd);
             } else if(writeable) {
                 callback_event.type = IOEVENT_CONNECTED;
                 iofd->state = IO_CONNECTED;
@@ -404,11 +537,13 @@ void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
                     if(bytes <= 0) {
                         if (errno != EAGAIN) {
                             iofd->state = IO_CLOSED;
+                                                       engine->update(iofd);
                             callback_event.type = IOEVENT_CLOSED;
                             callback_event.data.errid = errno;
                         }
                     } else {
                         int i, used_bytes = 0;
+                        iohandler_log(IOLOG_DEBUG, "received %d bytes (fd: %d). readbuf position: %d", bytes, iofd->fd, iofd->readbuf.bufpos);
                         iofd->readbuf.bufpos += bytes;
                         callback_event.type = IOEVENT_RECV;
                         for(i = 0; i < iofd->readbuf.bufpos; i++) {
@@ -417,11 +552,13 @@ void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
                             else if(iofd->readbuf.buffer[i] == '\n' || iofd->readbuf.buffer[i] == '\r') {
                                 iofd->readbuf.buffer[i] = 0;
                                 callback_event.data.recv_str = iofd->readbuf.buffer + used_bytes;
+                                iohandler_log(IOLOG_DEBUG, "parsed line (%d bytes): %s", i - used_bytes, iofd->readbuf.buffer + used_bytes);
                                 used_bytes = i+1;
                                 iohandler_trigger_event(&callback_event);
                             } else if(i + 1 - used_bytes >= LINELEN) { //512 max
                                 iofd->readbuf.buffer[i] = 0;
                                 callback_event.data.recv_str = iofd->readbuf.buffer + used_bytes;
+                                iohandler_log(IOLOG_DEBUG, "parsed and stripped line (%d bytes): %s", i - used_bytes, iofd->readbuf.buffer + used_bytes);
                                 for(; i < iofd->readbuf.bufpos; i++) { //skip the rest of the line
                                     if(iofd->readbuf.buffer[i] == '\n' || (iofd->readbuf.buffer[i] == '\r' && iofd->readbuf.buffer[i+1] != '\n')) {
                                         break;
@@ -432,9 +569,11 @@ void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
                             }
                         }
                         if(used_bytes) {
-                            if(used_bytes == iofd->readbuf.bufpos)
+                            if(used_bytes == iofd->readbuf.bufpos) {
                                 iofd->readbuf.bufpos = 0;
-                            else {
+                                iohandler_log(IOLOG_DEBUG, "readbuf fully processed (set buffer position to 0)");
+                            } else {
+                                iohandler_log(IOLOG_DEBUG, "readbuf rest: %d bytes (used %d bytes)", iofd->readbuf.bufpos - used_bytes, used_bytes);
                                 memmove(iofd->readbuf.buffer, iofd->readbuf.buffer + used_bytes, iofd->readbuf.bufpos - used_bytes);
                                 iofd->readbuf.bufpos -= used_bytes;
                             }
@@ -464,3 +603,58 @@ void iohandler_poll() {
     }
 }
 
+//debugging functions
+char *iohandler_iotype_name(enum IOType type) {
+    switch(type) {
+        case IOTYPE_UNKNOWN:
+            return "IOTYPE_UNKNOWN";
+        case IOTYPE_SERVER:
+            return "IOTYPE_SERVER";
+        case IOTYPE_CLIENT:
+            return "IOTYPE_CLIENT";
+        case IOTYPE_STDIN:
+            return "IOTYPE_STDIN";
+        case IOTYPE_TIMER:
+            return "IOTYPE_TIMER";
+        default:
+            return "(UNDEFINED)";
+    }
+}
+
+char *iohandler_iostatus_name(enum IOStatus status) {
+    switch(status) {
+        case IO_CLOSED:
+            return "IO_CLOSED";
+        case IO_LISTENING:
+            return "IO_LISTENING";
+        case IO_CONNECTING:
+            return "IO_CONNECTING";
+        case IO_CONNECTED:
+            return "IO_CONNECTED";
+        default:
+            return "(UNDEFINED)";
+    }
+}
+
+char *iohandler_ioeventtype_name(enum IOEventType type) {
+    switch(type) {
+        case IOEVENT_IGNORE:
+            return "IOEVENT_IGNORE";
+        case IOEVENT_READABLE:
+            return "IOEVENT_READABLE";
+        case IOEVENT_RECV:
+            return "IOEVENT_RECV";
+        case IOEVENT_CONNECTED:
+            return "IOEVENT_CONNECTED";
+        case IOEVENT_NOTCONNECTED:
+            return "IOEVENT_NOTCONNECTED";
+        case IOEVENT_CLOSED:
+            return "IOEVENT_CLOSED";
+        case IOEVENT_ACCEPT:
+            return "IOEVENT_ACCEPT";
+        case IOEVENT_TIMEOUT:
+            return "IOEVENT_TIMEOUT";
+        default:
+            return "(UNDEFINED)";
+    }
+}