]> git.saurik.com Git - redis.git/blobdiff - deps/hiredis/net.c
Merge remote-tracking branch 'origin/unstable' into unstable
[redis.git] / deps / hiredis / net.c
index 599ba9d6b951b6ddcc6a234002746663a6e579c0..438a129ba410bda49955bb3e45eca7695975f4b2 100644 (file)
@@ -1,6 +1,8 @@
 /* Extracted from anet.c to work properly with Hiredis error reporting.
  *
  * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,6 +33,7 @@
 #include "fmacros.h"
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/select.h>
 #include <sys/un.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
@@ -43,7 +46,7 @@
 #include <stdarg.h>
 #include <stdio.h>
 
-#include "hiredis.h"
+#include "net.h"
 #include "sds.h"
 
 /* Forward declaration */
@@ -65,7 +68,7 @@ static int redisCreateSocket(redisContext *c, int type) {
     return s;
 }
 
-static int redisSetNonBlock(redisContext *c, int fd) {
+static int redisSetBlocking(redisContext *c, int fd, int blocking) {
     int flags;
 
     /* Set the socket nonblocking.
@@ -77,9 +80,15 @@ static int redisSetNonBlock(redisContext *c, int fd) {
         close(fd);
         return REDIS_ERR;
     }
-    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+
+    if (blocking)
+        flags &= ~O_NONBLOCK;
+    else
+        flags |= O_NONBLOCK;
+
+    if (fcntl(fd, F_SETFL, flags) == -1) {
         __redisSetError(c,REDIS_ERR_IO,
-            sdscatprintf(sdsempty(), "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)));
+            sdscatprintf(sdsempty(), "fcntl(F_SETFL): %s", strerror(errno)));
         close(fd);
         return REDIS_ERR;
     }
@@ -91,19 +100,89 @@ static int redisSetTcpNoDelay(redisContext *c, int fd) {
     if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
         __redisSetError(c,REDIS_ERR_IO,
             sdscatprintf(sdsempty(), "setsockopt(TCP_NODELAY): %s", strerror(errno)));
+        close(fd);
         return REDIS_ERR;
     }
     return REDIS_OK;
 }
 
-int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
+static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {
+    struct timeval to;
+    struct timeval *toptr = NULL;
+    fd_set wfd;
+    int err;
+    socklen_t errlen;
+
+    /* Only use timeout when not NULL. */
+    if (timeout != NULL) {
+        to = *timeout;
+        toptr = &to;
+    }
+
+    if (errno == EINPROGRESS) {
+        FD_ZERO(&wfd);
+        FD_SET(fd, &wfd);
+
+        if (select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) {
+            __redisSetError(c,REDIS_ERR_IO,
+                sdscatprintf(sdsempty(), "select(2): %s", strerror(errno)));
+            close(fd);
+            return REDIS_ERR;
+        }
+
+        if (!FD_ISSET(fd, &wfd)) {
+            errno = ETIMEDOUT;
+            __redisSetError(c,REDIS_ERR_IO,NULL);
+            close(fd);
+            return REDIS_ERR;
+        }
+
+        err = 0;
+        errlen = sizeof(err);
+        if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
+            __redisSetError(c,REDIS_ERR_IO,
+                sdscatprintf(sdsempty(), "getsockopt(SO_ERROR): %s", strerror(errno)));
+            close(fd);
+            return REDIS_ERR;
+        }
+
+        if (err) {
+            errno = err;
+            __redisSetError(c,REDIS_ERR_IO,NULL);
+            close(fd);
+            return REDIS_ERR;
+        }
+
+        return REDIS_OK;
+    }
+
+    __redisSetError(c,REDIS_ERR_IO,NULL);
+    close(fd);
+    return REDIS_ERR;
+}
+
+int redisContextSetTimeout(redisContext *c, struct timeval tv) {
+    if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+        __redisSetError(c,REDIS_ERR_IO,
+            sdscatprintf(sdsempty(), "setsockopt(SO_RCVTIMEO): %s", strerror(errno)));
+        return REDIS_ERR;
+    }
+    if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+        __redisSetError(c,REDIS_ERR_IO,
+            sdscatprintf(sdsempty(), "setsockopt(SO_SNDTIMEO): %s", strerror(errno)));
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) {
     int s;
     int blocking = (c->flags & REDIS_BLOCK);
     struct sockaddr_in sa;
 
-    if ((s = redisCreateSocket(c,AF_INET)) == REDIS_ERR)
+    if ((s = redisCreateSocket(c,AF_INET)) < 0)
         return REDIS_ERR;
-    if (!blocking && redisSetNonBlock(c,s) == REDIS_ERR)
+    if (redisSetBlocking(c,s,0) != REDIS_OK)
         return REDIS_ERR;
 
     sa.sin_family = AF_INET;
@@ -114,7 +193,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
         he = gethostbyname(addr);
         if (he == NULL) {
             __redisSetError(c,REDIS_ERR_OTHER,
-                sdscatprintf(sdsempty(),"can't resolve: %s",addr));
+                sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
             close(s);
             return REDIS_ERR;
         }
@@ -125,29 +204,31 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
         if (errno == EINPROGRESS && !blocking) {
             /* This is ok. */
         } else {
-            __redisSetError(c,REDIS_ERR_IO,NULL);
-            close(s);
-            return REDIS_ERR;
+            if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+                return REDIS_ERR;
         }
     }
 
-    if (redisSetTcpNoDelay(c,s) != REDIS_OK) {
-        close(s);
+    /* Reset socket to be blocking after connect(2). */
+    if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
+        return REDIS_ERR;
+
+    if (redisSetTcpNoDelay(c,s) != REDIS_OK)
         return REDIS_ERR;
-    }
 
     c->fd = s;
+    c->flags |= REDIS_CONNECTED;
     return REDIS_OK;
 }
 
-int redisContextConnectUnix(redisContext *c, const char *path) {
+int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) {
     int s;
     int blocking = (c->flags & REDIS_BLOCK);
     struct sockaddr_un sa;
 
-    if ((s = redisCreateSocket(c,AF_LOCAL)) == REDIS_ERR)
+    if ((s = redisCreateSocket(c,AF_LOCAL)) < 0)
         return REDIS_ERR;
-    if (!blocking && redisSetNonBlock(c,s) != REDIS_OK)
+    if (redisSetBlocking(c,s,0) != REDIS_OK)
         return REDIS_ERR;
 
     sa.sun_family = AF_LOCAL;
@@ -156,12 +237,16 @@ int redisContextConnectUnix(redisContext *c, const char *path) {
         if (errno == EINPROGRESS && !blocking) {
             /* This is ok. */
         } else {
-            __redisSetError(c,REDIS_ERR_IO,NULL);
-            close(s);
-            return REDIS_ERR;
+            if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+                return REDIS_ERR;
         }
     }
 
+    /* Reset socket to be blocking after connect(2). */
+    if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
+        return REDIS_ERR;
+
     c->fd = s;
+    c->flags |= REDIS_CONNECTED;
     return REDIS_OK;
 }