diff --git a/Makefile b/Makefile index c8632b4..969115b 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,18 @@ LIBNAME=libhiredis HIREDIS_MAJOR=0 HIREDIS_MINOR=10 +# redis-server configuration used for testing +REDIS_PORT=56379 +REDIS_SERVER=redis-server +define REDIS_TEST_CONFIG + daemonize yes + pidfile /tmp/hiredis-test-redis.pid + port $(REDIS_PORT) + bind 127.0.0.1 + unixsocket /tmp/hiredis-test-redis.sock +endef +export REDIS_TEST_CONFIG + # Fallback to gcc when $CC is not in $PATH. CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') OPTIMIZATION?=-O3 @@ -97,14 +109,8 @@ test: hiredis-test ./hiredis-test check: hiredis-test - echo \ - "daemonize yes\n" \ - "pidfile /tmp/hiredis-test-redis.pid\n" \ - "port 56379\n" \ - "bind 127.0.0.1\n" \ - "unixsocket /tmp/hiredis-test-redis.sock" \ - | redis-server - - ./hiredis-test -h 127.0.0.1 -p 56379 -s /tmp/hiredis-test-redis.sock || \ + @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - + ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ ( kill `cat /tmp/hiredis-test-redis.pid` && false ) kill `cat /tmp/hiredis-test-redis.pid` diff --git a/adapters/ae.h b/adapters/ae.h index 65235f8..5c551c2 100644 --- a/adapters/ae.h +++ b/adapters/ae.h @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + #ifndef __HIREDIS_AE_H__ #define __HIREDIS_AE_H__ #include diff --git a/adapters/libev.h b/adapters/libev.h index 534d743..2bf8d52 100644 --- a/adapters/libev.h +++ b/adapters/libev.h @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + #ifndef __HIREDIS_LIBEV_H__ #define __HIREDIS_LIBEV_H__ #include diff --git a/adapters/libevent.h b/adapters/libevent.h index 4055ec0..1c2b271 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + #ifndef __HIREDIS_LIBEVENT_H__ #define __HIREDIS_LIBEVENT_H__ #include diff --git a/adapters/libuv.h b/adapters/libuv.h index e61d5ca..a1967f4 100644 --- a/adapters/libuv.h +++ b/adapters/libuv.h @@ -3,7 +3,7 @@ #include #include "../hiredis.h" #include "../async.h" - +#include typedef struct redisLibuvEvents { redisAsyncContext* context; @@ -11,6 +11,7 @@ typedef struct redisLibuvEvents { int events; } redisLibuvEvents; +int redisLibuvAttach(redisAsyncContext*, uv_loop_t*); static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { redisLibuvEvents* p = (redisLibuvEvents*)handle->data; @@ -86,7 +87,7 @@ static void redisLibuvCleanup(void *privdata) { } -int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { +static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { redisContext *c = &(ac->c); if (ac->ev.data != NULL) { @@ -99,7 +100,7 @@ int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { ac->ev.delWrite = redisLibuvDelWrite; ac->ev.cleanup = redisLibuvCleanup; - redisLibuvEvents* p = malloc(sizeof(*p)); + redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); if (!p) { return REDIS_ERR; diff --git a/async.c b/async.c index 923a329..7fd17f2 100644 --- a/async.c +++ b/async.c @@ -165,6 +165,14 @@ redisAsyncContext *redisAsyncConnect(const char *ip, int port) { return ac; } +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisContext *c; redisAsyncContext *ac; @@ -391,7 +399,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, void redisProcessCallbacks(redisAsyncContext *ac) { redisContext *c = &(ac->c); - redisCallback cb; + redisCallback cb = {NULL, NULL, NULL}; void *reply = NULL; int status; @@ -473,7 +481,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); - if (redisCheckSocketError(c,c->fd) == REDIS_ERR) { + if (redisCheckSocketError(c) == REDIS_ERR) { /* Try again later when connect(2) is still in progress. */ if (errno == EINPROGRESS) return REDIS_OK; diff --git a/async.h b/async.h index 268274e..1df2ff0 100644 --- a/async.h +++ b/async.h @@ -102,6 +102,7 @@ typedef struct redisAsyncContext { /* Functions that proxy to hiredis */ redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,char *source); redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); diff --git a/fmacros.h b/fmacros.h index 799c12c..9e5fec0 100644 --- a/fmacros.h +++ b/fmacros.h @@ -7,7 +7,7 @@ #if defined(__sun__) #define _POSIX_C_SOURCE 200112L -#elif defined(__linux__) +#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) #define _XOPEN_SOURCE 600 #else #define _XOPEN_SOURCE diff --git a/hiredis.c b/hiredis.c index 9b74b5b..4054de6 100644 --- a/hiredis.c +++ b/hiredis.c @@ -650,7 +650,7 @@ int redisReaderGetReply(redisReader *r, void **reply) { /* Discard part of the buffer when we've consumed at least 1k, to avoid * doing unnecessary calls to memmove() in sds.c. */ if (r->pos >= 1024) { - r->buf = sdsrange(r->buf,r->pos,-1); + sdsrange(r->buf,r->pos,-1); r->pos = 0; r->len = sdslen(r->buf); } @@ -1010,6 +1010,13 @@ void redisFree(redisContext *c) { free(c); } +int redisFreeKeepFd(redisContext *c) { + int fd = c->fd; + c->fd = -1; + redisFree(c); + return fd; +} + /* Connect to a Redis instance. On error the field error in the returned * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ @@ -1049,6 +1056,14 @@ redisContext *redisConnectNonBlock(const char *ip, int port) { return c; } +redisContext *redisConnectBindNonBlock(const char *ip, int port, + char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + redisContext *redisConnectUnix(const char *path) { redisContext *c; @@ -1085,6 +1100,18 @@ redisContext *redisConnectUnixNonBlock(const char *path) { return c; } +redisContext *redisConnectFd(int fd) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->fd = fd; + c->flags |= REDIS_BLOCK | REDIS_CONNECTED; + return c; +} + /* Set read/write timeout on a blocking socket. */ int redisSetTimeout(redisContext *c, const struct timeval tv) { if (c->flags & REDIS_BLOCK) @@ -1162,7 +1189,7 @@ int redisBufferWrite(redisContext *c, int *done) { sdsfree(c->obuf); c->obuf = sdsempty(); } else { - c->obuf = sdsrange(c->obuf,nwritten,-1); + sdsrange(c->obuf,nwritten,-1); } } } @@ -1230,6 +1257,15 @@ int __redisAppendCommand(redisContext *c, char *cmd, size_t len) { return REDIS_OK; } +int redisAppendFormattedCommand(redisContext *c, char *format, size_t len) { + + if (__redisAppendCommand(c, format, len) != REDIS_OK) { + return REDIS_ERR; + } + + return REDIS_OK; +} + int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { char *cmd; int len; diff --git a/hiredis.h b/hiredis.h index c65098b..05bfc47 100644 --- a/hiredis.h +++ b/hiredis.h @@ -175,12 +175,15 @@ typedef struct redisContext { redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, char *source); redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); +redisContext *redisConnectFd(int fd); int redisSetTimeout(redisContext *c, const struct timeval tv); int redisEnableKeepAlive(redisContext *c); void redisFree(redisContext *c); +int redisFreeKeepFd(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); @@ -191,6 +194,10 @@ int redisBufferWrite(redisContext *c, int *done); int redisGetReply(redisContext *c, void **reply); int redisGetReplyFromReader(redisContext *c, void **reply); +/* Write a formatted command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisAppendFormattedCommand(redisContext *c, char *format, size_t len); + /* Write a command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redisvAppendCommand(redisContext *c, const char *format, va_list ap); diff --git a/net.c b/net.c index 603699c..7c787df 100644 --- a/net.c +++ b/net.c @@ -54,6 +54,13 @@ /* Defined in hiredis.c */ void __redisSetError(redisContext *c, int type, const char *str); +static void redisContextCloseFd(redisContext *c) { + if (c && c->fd > 0) { + close(c->fd); + c->fd = -1; + } +} + static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { char buf[128] = { 0 }; size_t len = 0; @@ -64,11 +71,11 @@ static void __redisSetErrorFromErrno(redisContext *c, int type, const char *pref __redisSetError(c,type,buf); } -static int redisSetReuseAddr(redisContext *c, int fd) { +static int redisSetReuseAddr(redisContext *c) { int on = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } return REDIS_OK; @@ -80,23 +87,24 @@ static int redisCreateSocket(redisContext *c, int type) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } + c->fd = s; if (type == AF_INET) { - if (redisSetReuseAddr(c,s) == REDIS_ERR) { + if (redisSetReuseAddr(c) == REDIS_ERR) { return REDIS_ERR; } } - return s; + return REDIS_OK; } -static int redisSetBlocking(redisContext *c, int fd, int blocking) { +static int redisSetBlocking(redisContext *c, int blocking) { int flags; /* Set the socket nonblocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ - if ((flags = fcntl(fd, F_GETFL)) == -1) { + if ((flags = fcntl(c->fd, F_GETFL)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } @@ -105,9 +113,9 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) { else flags |= O_NONBLOCK; - if (fcntl(fd, F_SETFL, flags) == -1) { + if (fcntl(c->fd, F_SETFL, flags) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } return REDIS_OK; @@ -122,13 +130,15 @@ int redisKeepAlive(redisContext *c, int interval) { return REDIS_ERR; } -#ifdef _OSX val = interval; + +#ifdef _OSX if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } #else +#ifndef __sun val = interval; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); @@ -147,16 +157,17 @@ int redisKeepAlive(redisContext *c, int interval) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } +#endif #endif return REDIS_OK; } -static int redisSetTcpNoDelay(redisContext *c, int fd) { +static int redisSetTcpNoDelay(redisContext *c) { int yes = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } return REDIS_OK; @@ -164,19 +175,19 @@ static int redisSetTcpNoDelay(redisContext *c, int fd) { #define __MAX_MSEC (((LONG_MAX) - 999) / 1000) -static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) { +static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { struct pollfd wfd[1]; long msec; msec = -1; - wfd[0].fd = fd; + wfd[0].fd = c->fd; wfd[0].events = POLLOUT; /* Only use timeout when not NULL. */ if (timeout != NULL) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } @@ -192,40 +203,38 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * if ((res = poll(wfd, 1, msec)) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } else if (res == 0) { errno = ETIMEDOUT; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } - if (redisCheckSocketError(c, fd) != REDIS_OK) + if (redisCheckSocketError(c) != REDIS_OK) return REDIS_ERR; return REDIS_OK; } __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); + redisContextCloseFd(c); return REDIS_ERR; } -int redisCheckSocketError(redisContext *c, int fd) { +int redisCheckSocketError(redisContext *c) { int err = 0; socklen_t errlen = sizeof(err); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); - close(fd); return REDIS_ERR; } if (err) { errno = err; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - close(fd); return REDIS_ERR; } @@ -244,10 +253,12 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { return REDIS_OK; } -int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) { +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + char *source_addr) { int s, rv; char _port[6]; /* strlen("65535"); */ - struct addrinfo hints, *servinfo, *p; + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDIS_BLOCK); snprintf(_port, 6, "%d", port); @@ -271,25 +282,47 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port, const st if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) continue; - if (redisSetBlocking(c,s,0) != REDIS_OK) + c->fd = s; + if (redisSetBlocking(c,0) != REDIS_OK) goto error; + if (source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { - close(s); + redisContextCloseFd(c); continue; } else if (errno == EINPROGRESS && !blocking) { /* This is ok. */ } else { - if (redisContextWaitReady(c,s,timeout) != REDIS_OK) + if (redisContextWaitReady(c,timeout) != REDIS_OK) goto error; } } - if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) + if (blocking && redisSetBlocking(c,1) != REDIS_OK) goto error; - if (redisSetTcpNoDelay(c,s) != REDIS_OK) + if (redisSetTcpNoDelay(c) != REDIS_OK) goto error; - c->fd = s; c->flags |= REDIS_CONNECTED; rv = REDIS_OK; goto end; @@ -308,32 +341,40 @@ end: return rv; // Need to return REDIS_OK if alright } +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { - int s; int blocking = (c->flags & REDIS_BLOCK); struct sockaddr_un sa; - if ((s = redisCreateSocket(c,AF_LOCAL)) < 0) + if (redisCreateSocket(c,AF_LOCAL) < 0) return REDIS_ERR; - if (redisSetBlocking(c,s,0) != REDIS_OK) + if (redisSetBlocking(c,0) != REDIS_OK) return REDIS_ERR; sa.sun_family = AF_LOCAL; strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); - if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ } else { - if (redisContextWaitReady(c,s,timeout) != REDIS_OK) + if (redisContextWaitReady(c,timeout) != REDIS_OK) return REDIS_ERR; } } /* Reset socket to be blocking after connect(2). */ - if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) + if (blocking && redisSetBlocking(c,1) != REDIS_OK) return REDIS_ERR; - c->fd = s; c->flags |= REDIS_CONNECTED; return REDIS_OK; } diff --git a/net.h b/net.h index 94b76f5..370a1ff 100644 --- a/net.h +++ b/net.h @@ -39,9 +39,11 @@ #define AF_LOCAL AF_UNIX #endif -int redisCheckSocketError(redisContext *c, int fd); +int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); diff --git a/sds.c b/sds.c index 998564d..5306f1f 100644 --- a/sds.c +++ b/sds.c @@ -1,6 +1,6 @@ -/* SDSLib, A C dynamic strings library +/* SDS (Simple Dynamic Strings), A C dynamic strings library. * - * Copyright (c) 2006-2010, Salvatore Sanfilippo + * Copyright (c) 2006-2014, Salvatore Sanfilippo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,83 +32,188 @@ #include #include #include +#include + #include "sds.h" -#ifdef SDS_ABORT_ON_OOM -static void sdsOomAbort(void) { - fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n"); - abort(); -} -#endif - +/* Create a new sds string with the content specified by the 'init' pointer + * and 'initlen'. + * If NULL is used for 'init' the string is initialized with zero bytes. + * + * The string is always null-termined (all the sds strings are, always) so + * even if you create an sds string with: + * + * mystring = sdsnewlen("abc",3"); + * + * You can print the string with printf() as there is an implicit \0 at the + * end of the string. However the string is binary safe and can contain + * \0 characters in the middle, as the length is stored in the sds header. */ sds sdsnewlen(const void *init, size_t initlen) { struct sdshdr *sh; - sh = malloc(sizeof(struct sdshdr)+initlen+1); -#ifdef SDS_ABORT_ON_OOM - if (sh == NULL) sdsOomAbort(); -#else + if (init) { + sh = malloc(sizeof *sh+initlen+1); + } else { + sh = calloc(sizeof *sh+initlen+1,1); + } if (sh == NULL) return NULL; -#endif sh->len = initlen; sh->free = 0; - if (initlen) { - if (init) memcpy(sh->buf, init, initlen); - else memset(sh->buf,0,initlen); - } + if (initlen && init) + memcpy(sh->buf, init, initlen); sh->buf[initlen] = '\0'; return (char*)sh->buf; } +/* Create an empty (zero length) sds string. Even in this case the string + * always has an implicit null term. */ sds sdsempty(void) { return sdsnewlen("",0); } +/* Create a new sds string starting from a null termined C string. */ sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); } +/* Duplicate an sds string. */ sds sdsdup(const sds s) { return sdsnewlen(s, sdslen(s)); } +/* Free an sds string. No operation is performed if 's' is NULL. */ void sdsfree(sds s) { if (s == NULL) return; free(s-sizeof(struct sdshdr)); } +/* Set the sds string length to the length as obtained with strlen(), so + * considering as content only up to the first null term character. + * + * This function is useful when the sds string is hacked manually in some + * way, like in the following example: + * + * s = sdsnew("foobar"); + * s[2] = '\0'; + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); + * + * The output will be "2", but if we comment out the call to sdsupdatelen() + * the output will be "6" as the string was modified but the logical length + * remains 6 bytes. */ void sdsupdatelen(sds s) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + struct sdshdr *sh = (void*) (s-sizeof *sh);; int reallen = strlen(s); sh->free += (sh->len-reallen); sh->len = reallen; } -static sds sdsMakeRoomFor(sds s, size_t addlen) { +/* Modify an sds string on-place to make it empty (zero length). + * However all the existing buffer is not discarded but set as free space + * so that next append operations will not require allocations up to the + * number of bytes previously available. */ +void sdsclear(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh);; + sh->free += sh->len; + sh->len = 0; + sh->buf[0] = '\0'; +} + +/* Enlarge the free space at the end of the sds string so that the caller + * is sure that after calling this function can overwrite up to addlen + * bytes after the end of the string, plus one more byte for nul term. + * + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { struct sdshdr *sh, *newsh; size_t free = sdsavail(s); size_t len, newlen; if (free >= addlen) return s; len = sdslen(s); - sh = (void*) (s-(sizeof(struct sdshdr))); - newlen = (len+addlen)*2; - newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1); -#ifdef SDS_ABORT_ON_OOM - if (newsh == NULL) sdsOomAbort(); -#else + sh = (void*) (s-sizeof *sh);; + newlen = (len+addlen); + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + else + newlen += SDS_MAX_PREALLOC; + newsh = realloc(sh, sizeof *newsh+newlen+1); if (newsh == NULL) return NULL; -#endif newsh->free = newlen - len; return newsh->buf; } +/* Reallocate the sds string so that it has no free space at the end. The + * contained string remains not altered, but next concatenation operations + * will require a reallocation. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdsRemoveFreeSpace(sds s) { + struct sdshdr *sh; + + sh = (void*) (s-sizeof *sh);; + sh = realloc(sh, sizeof *sh+sh->len+1); + sh->free = 0; + return sh->buf; +} + +/* Return the total size of the allocation of the specifed sds string, + * including: + * 1) The sds header before the pointer. + * 2) The string. + * 3) The free buffer at the end if any. + * 4) The implicit null term. + */ +size_t sdsAllocSize(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh);; + + return sizeof(*sh)+sh->len+sh->free+1; +} + +/* Increment the sds length and decrements the left free space at the + * end of the string according to 'incr'. Also set the null term + * in the new end of the string. + * + * This function is used in order to fix the string length after the + * user calls sdsMakeRoomFor(), writes something after the end of + * the current string, and finally needs to set the new length. + * + * Note: it is possible to use a negative increment in order to + * right-trim the string. + * + * Usage example: + * + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the + * following schema, to cat bytes coming from the kernel to the end of an + * sds string without copying into an intermediate buffer: + * + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); + * nread = read(fd, s+oldlen, BUFFER_SIZE); + * ... check for nread <= 0 and handle it ... + * sdsIncrLen(s, nread); + */ +void sdsIncrLen(sds s, int incr) { + struct sdshdr *sh = (void*) (s-sizeof *sh);; + + assert(sh->free >= incr); + sh->len += incr; + sh->free -= incr; + assert(sh->free >= 0); + s[sh->len] = '\0'; +} + /* Grow the sds to have the specified length. Bytes that were not part of - * the original length of the sds will be set to zero. */ + * the original length of the sds will be set to zero. + * + * if the specified length is smaller than the current length, no operation + * is performed. */ sds sdsgrowzero(sds s, size_t len) { - struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + struct sdshdr *sh = (void*) (s-sizeof *sh); size_t totlen, curlen = sh->len; if (len <= curlen) return s; @@ -116,7 +221,7 @@ sds sdsgrowzero(sds s, size_t len) { if (s == NULL) return NULL; /* Make sure added region doesn't contain garbage */ - sh = (void*)(s-(sizeof(struct sdshdr))); + sh = (void*)(s-sizeof *sh); memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ totlen = sh->len+sh->free; sh->len = len; @@ -124,13 +229,18 @@ sds sdsgrowzero(sds s, size_t len) { return s; } +/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the + * end of the specified sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ sds sdscatlen(sds s, const void *t, size_t len) { struct sdshdr *sh; size_t curlen = sdslen(s); s = sdsMakeRoomFor(s,len); if (s == NULL) return NULL; - sh = (void*) (s-(sizeof(struct sdshdr))); + sh = (void*) (s-sizeof *sh);; memcpy(s+curlen, t, len); sh->len = curlen+len; sh->free = sh->free-len; @@ -138,18 +248,32 @@ sds sdscatlen(sds s, const void *t, size_t len) { return s; } +/* Append the specified null termianted C string to the sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ sds sdscat(sds s, const char *t) { return sdscatlen(s, t, strlen(t)); } -sds sdscpylen(sds s, char *t, size_t len) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); +/* Append the specified sds 't' to the existing sds 's'. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} + +/* Destructively modify the sds string 's' to hold the specified binary + * safe string pointed by 't' of length 'len' bytes. */ +sds sdscpylen(sds s, const char *t, size_t len) { + struct sdshdr *sh = (void*) (s-sizeof *sh);; size_t totlen = sh->free+sh->len; if (totlen < len) { s = sdsMakeRoomFor(s,len-sh->len); if (s == NULL) return NULL; - sh = (void*) (s-(sizeof(struct sdshdr))); + sh = (void*) (s-sizeof *sh);; totlen = sh->free+sh->len; } memcpy(s, t, len); @@ -159,10 +283,13 @@ sds sdscpylen(sds s, char *t, size_t len) { return s; } -sds sdscpy(sds s, char *t) { +/* Like sdscpylen() but 't' must be a null-termined string so that the length + * of the string is obtained with strlen(). */ +sds sdscpy(sds s, const char *t) { return sdscpylen(s, t, strlen(t)); } +/* Like sdscatpritf() but gets va_list instead of being variadic. */ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { va_list cpy; char *buf, *t; @@ -170,15 +297,10 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { while(1) { buf = malloc(buflen); -#ifdef SDS_ABORT_ON_OOM - if (buf == NULL) sdsOomAbort(); -#else if (buf == NULL) return NULL; -#endif buf[buflen-2] = '\0'; va_copy(cpy,ap); vsnprintf(buf, buflen, fmt, cpy); - va_end(cpy); if (buf[buflen-2] != '\0') { free(buf); buflen *= 2; @@ -191,6 +313,22 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { return t; } +/* Append to the sds string 's' a string obtained using printf-alike format + * specifier. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsempty("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). + * + * Often you need to create a string from scratch with the printf-alike + * format. When this is the need, just use sdsempty() as the target string: + * + * s = sdscatprintf(sdsempty(), "... your format ...", args); + */ sds sdscatprintf(sds s, const char *fmt, ...) { va_list ap; char *t; @@ -200,8 +338,22 @@ sds sdscatprintf(sds s, const char *fmt, ...) { return t; } -sds sdstrim(sds s, const char *cset) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); +/* Remove the part of the string from left and from right composed just of + * contiguous characters found in 'cset', that is a null terminted C string. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"A. :"); + * printf("%s\n", s); + * + * Output will be just "Hello World". + */ +void sdstrim(sds s, const char *cset) { + struct sdshdr *sh = (void*) (s-sizeof *sh);; char *start, *end, *sp, *ep; size_t len; @@ -214,14 +366,29 @@ sds sdstrim(sds s, const char *cset) { sh->buf[len] = '\0'; sh->free = sh->free+(sh->len-len); sh->len = len; - return s; } -sds sdsrange(sds s, int start, int end) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); +/* Turn the string into a smaller (or equal) string containing only the + * substring specified by the 'start' and 'end' indexes. + * + * start and end can be negative, where -1 means the last character of the + * string, -2 the penultimate character, and so forth. + * + * The interval is inclusive, so the start and end characters will be part + * of the resulting string. + * + * The string is modified in-place. + * + * Example: + * + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" + */ +void sdsrange(sds s, int start, int end) { + struct sdshdr *sh = (void*) (s-sizeof *sh);; size_t newlen, len = sdslen(s); - if (len == 0) return s; + if (len == 0) return; if (start < 0) { start = len+start; if (start < 0) start = 0; @@ -245,22 +412,34 @@ sds sdsrange(sds s, int start, int end) { sh->buf[newlen] = 0; sh->free = sh->free+(sh->len-newlen); sh->len = newlen; - return s; } +/* Apply tolower() to every character of the sds string 's'. */ void sdstolower(sds s) { int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = tolower(s[j]); } +/* Apply toupper() to every character of the sds string 's'. */ void sdstoupper(sds s) { int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = toupper(s[j]); } -int sdscmp(sds s1, sds s2) { +/* Compare two sds strings s1 and s2 with memcmp(). + * + * Return value: + * + * 1 if s1 > s2. + * -1 if s1 < s2. + * 0 if s1 and s2 are exactly the same binary string. + * + * If two strings share exactly the same prefix, but one of the two has + * additional characters, the longer string is considered to be greater than + * the smaller one. */ +int sdscmp(const sds s1, const sds s2) { size_t l1, l2, minlen; int cmp; @@ -288,18 +467,15 @@ int sdscmp(sds s1, sds s2) { * requires length arguments. sdssplit() is just the * same function but for zero-terminated strings. */ -sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) { +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { int elements = 0, slots = 5, start = 0, j; + sds *tokens; - sds *tokens = malloc(sizeof(sds)*slots); -#ifdef SDS_ABORT_ON_OOM - if (tokens == NULL) sdsOomAbort(); -#endif + if (seplen < 1 || len < 0) return NULL; + + tokens = malloc(sizeof(sds)*slots); if (tokens == NULL) return NULL; - if (seplen < 1 || len < 0) { - free(tokens); - return NULL; - } + if (len == 0) { *count = 0; return tokens; @@ -311,25 +487,13 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) { slots *= 2; newtokens = realloc(tokens,sizeof(sds)*slots); - if (newtokens == NULL) { -#ifdef SDS_ABORT_ON_OOM - sdsOomAbort(); -#else - goto cleanup; -#endif - } + if (newtokens == NULL) goto cleanup; tokens = newtokens; } /* search the separator */ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { tokens[elements] = sdsnewlen(s+start,j-start); - if (tokens[elements] == NULL) { -#ifdef SDS_ABORT_ON_OOM - sdsOomAbort(); -#else - goto cleanup; -#endif - } + if (tokens[elements] == NULL) goto cleanup; elements++; start = j+seplen; j = j+seplen-1; /* skip the separator */ @@ -337,28 +501,22 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) { } /* Add the final element. We are sure there is room in the tokens array. */ tokens[elements] = sdsnewlen(s+start,len-start); - if (tokens[elements] == NULL) { -#ifdef SDS_ABORT_ON_OOM - sdsOomAbort(); -#else - goto cleanup; -#endif - } + if (tokens[elements] == NULL) goto cleanup; elements++; *count = elements; return tokens; -#ifndef SDS_ABORT_ON_OOM cleanup: { int i; for (i = 0; i < elements; i++) sdsfree(tokens[i]); free(tokens); + *count = 0; return NULL; } -#endif } +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ void sdsfreesplitres(sds *tokens, int count) { if (!tokens) return; while(count--) @@ -366,6 +524,10 @@ void sdsfreesplitres(sds *tokens, int count) { free(tokens); } +/* Create an sds string from a long long value. It is much faster than: + * + * sdscatprintf(sdsempty(),"%lld\n", value); + */ sds sdsfromlonglong(long long value) { char buf[32], *p; unsigned long long v; @@ -381,10 +543,14 @@ sds sdsfromlonglong(long long value) { return sdsnewlen(p,32-(p-buf)); } -sds sdscatrepr(sds s, char *p, size_t len) { +/* Append to the sds string "s" an escaped string representation where + * all the non-printable characters (tested with isprint()) are turned into + * escapes in the form "\n\r\a...." or "\x". + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatrepr(sds s, const char *p, size_t len) { s = sdscatlen(s,"\"",1); - if (s == NULL) return NULL; - while(len--) { switch(*p) { case '\\': @@ -404,27 +570,64 @@ sds sdscatrepr(sds s, char *p, size_t len) { break; } p++; - if (s == NULL) return NULL; } return sdscatlen(s,"\"",1); } +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts a hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + /* Split a line into arguments, where every argument can be in the * following programming-language REPL-alike form: * * foo bar "newline are supported\n" and "\xff\x00otherstuff" * * The number of arguments is stored into *argc, and an array - * of sds is returned. The caller should sdsfree() all the returned - * strings and finally free() the array itself. + * of sds is returned. + * + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). * * Note that sdscatrepr() is able to convert back a string into * a quoted string in the same format sdssplitargs() is able to parse. + * + * The function returns the allocated tokens on success, even when the + * input string is empty, or NULL if the input contains unbalanced + * quotes or closed quotes followed by non space characters + * as in: "foo"bar or "foo' */ -sds *sdssplitargs(char *line, int *argc) { - char *p = line; +sds *sdssplitargs(const char *line, int *argc) { + const char *p = line; char *current = NULL; - char **vector = NULL, **_vector = NULL; + char **vector = NULL; *argc = 0; while(1) { @@ -432,17 +635,24 @@ sds *sdssplitargs(char *line, int *argc) { while(*p && isspace(*p)) p++; if (*p) { /* get a token */ - int inq=0; /* set to 1 if we are in "quotes" */ + int inq=0; /* set to 1 if we are in "quotes" */ + int insq=0; /* set to 1 if we are in 'single quotes' */ int done=0; - if (current == NULL) { - current = sdsempty(); - if (current == NULL) goto err; - } - + if (current == NULL) current = sdsempty(); while(!done) { if (inq) { - if (*p == '\\' && *(p+1)) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { char c; p++; @@ -456,7 +666,23 @@ sds *sdssplitargs(char *line, int *argc) { } current = sdscatlen(current,&c,1); } else if (*p == '"') { - /* closing quote must be followed by a space */ + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else if (insq) { + if (*p == '\\' && *(p+1) == '\'') { + p++; + current = sdscatlen(current,"'",1); + } else if (*p == '\'') { + /* closing quote must be followed by a space or + * nothing at all. */ if (*(p+1) && !isspace(*(p+1))) goto err; done=1; } else if (!*p) { @@ -477,23 +703,24 @@ sds *sdssplitargs(char *line, int *argc) { case '"': inq=1; break; + case '\'': + insq=1; + break; default: current = sdscatlen(current,p,1); break; } } if (*p) p++; - if (current == NULL) goto err; } /* add the token to the vector */ - _vector = realloc(vector,((*argc)+1)*sizeof(char*)); - if (_vector == NULL) goto err; - - vector = _vector; + vector = realloc(vector,((*argc)+1)*sizeof(char*)); vector[*argc] = current; (*argc)++; current = NULL; } else { + /* Even on empty input string return something not NULL. */ + if (vector == NULL) vector = malloc(sizeof(void*)); return vector; } } @@ -501,30 +728,67 @@ sds *sdssplitargs(char *line, int *argc) { err: while((*argc)--) sdsfree(vector[*argc]); - if (vector != NULL) free(vector); - if (current != NULL) sdsfree(current); + free(vector); + if (current) sdsfree(current); + *argc = 0; return NULL; } +/* Modify the string substituting all the occurrences of the set of + * characters specified in the 'from' string to the corresponding character + * in the 'to' array. + * + * For instance: sdsmapchars(mystring, "ho", "01", 2) + * will have the effect of turning the string "hello" into "0ell1". + * + * The function returns the sds string pointer, that is always the same + * as the input pointer since no resize is needed. */ +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); + + for (j = 0; j < l; j++) { + for (i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Join an array of C strings using the specified separator (also a C string). + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + #ifdef SDS_TEST_MAIN #include - -int __failed_tests = 0; -int __test_num = 0; -#define test_cond(descr,_c) do { \ - __test_num++; printf("%d - %s: ", __test_num, descr); \ - if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \ -} while(0); -#define test_report() do { \ - printf("%d tests, %d passed, %d failed\n", __test_num, \ - __test_num-__failed_tests, __failed_tests); \ - if (__failed_tests) { \ - printf("=== WARNING === We have failed tests here...\n"); \ - } \ -} while(0); +#include "testhelp.h" int main(void) { { + struct sdshdr *sh; sds x = sdsnew("foo"), y; test_cond("Create a string and obtain the length", @@ -554,36 +818,43 @@ int main(void) { sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) sdsfree(x); - x = sdstrim(sdsnew("xxciaoyyy"),"xy"); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); test_cond("sdstrim() correctly trims characters", sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - y = sdsrange(sdsdup(x),1,1); + y = sdsdup(x); + sdsrange(y,1,1); test_cond("sdsrange(...,1,1)", sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) sdsfree(y); - y = sdsrange(sdsdup(x),1,-1); + y = sdsdup(x); + sdsrange(y,1,-1); test_cond("sdsrange(...,1,-1)", sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) sdsfree(y); - y = sdsrange(sdsdup(x),-2,-1); + y = sdsdup(x); + sdsrange(y,-2,-1); test_cond("sdsrange(...,-2,-1)", sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) sdsfree(y); - y = sdsrange(sdsdup(x),2,1); + y = sdsdup(x); + sdsrange(y,2,1); test_cond("sdsrange(...,2,1)", sdslen(y) == 0 && memcmp(y,"\0",1) == 0) sdsfree(y); - y = sdsrange(sdsdup(x),1,100); + y = sdsdup(x); + sdsrange(y,1,100); test_cond("sdsrange(...,1,100)", sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) sdsfree(y); - y = sdsrange(sdsdup(x),100,100); + y = sdsdup(x); + sdsrange(y,100,100); test_cond("sdsrange(...,100,100)", sdslen(y) == 0 && memcmp(y,"\0",1) == 0) @@ -604,7 +875,33 @@ int main(void) { x = sdsnew("aar"); y = sdsnew("bar"); test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", + memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) + + { + int oldfree; + + sdsfree(x); + x = sdsnew("0"); + sh = (void*) (x-(sizeof(struct sdshdr))); + test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); + x = sdsMakeRoomFor(x,1); + sh = (void*) (x-(sizeof(struct sdshdr))); + test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); + oldfree = sh->free; + x[1] = '1'; + sdsIncrLen(x,1); + test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); + test_cond("sdsIncrLen() -- len", sh->len == 2); + test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); + } } test_report() + return 0; } #endif diff --git a/sds.h b/sds.h index 94f5871..ab6fc9c 100644 --- a/sds.h +++ b/sds.h @@ -1,6 +1,6 @@ -/* SDSLib, A C dynamic strings library +/* SDS (Simple Dynamic Strings), A C dynamic strings library. * - * Copyright (c) 2006-2010, Salvatore Sanfilippo + * Copyright (c) 2006-2014, Salvatore Sanfilippo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,8 @@ #ifndef __SDS_H #define __SDS_H +#define SDS_MAX_PREALLOC (1024*1024) + #include #include @@ -43,12 +45,12 @@ struct sdshdr { }; static inline size_t sdslen(const sds s) { - struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + struct sdshdr *sh = (void*)(s-sizeof *sh); return sh->len; } static inline size_t sdsavail(const sds s) { - struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + struct sdshdr *sh = (void*)(s-sizeof *sh); return sh->free; } @@ -58,12 +60,13 @@ sds sdsempty(void); size_t sdslen(const sds s); sds sdsdup(const sds s); void sdsfree(sds s); -size_t sdsavail(sds s); +size_t sdsavail(const sds s); sds sdsgrowzero(sds s, size_t len); sds sdscatlen(sds s, const void *t, size_t len); sds sdscat(sds s, const char *t); -sds sdscpylen(sds s, char *t, size_t len); -sds sdscpy(sds s, char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); sds sdscatvprintf(sds s, const char *fmt, va_list ap); #ifdef __GNUC__ @@ -73,16 +76,26 @@ sds sdscatprintf(sds s, const char *fmt, ...) sds sdscatprintf(sds s, const char *fmt, ...); #endif -sds sdstrim(sds s, const char *cset); -sds sdsrange(sds s, int start, int end); +void sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); void sdsupdatelen(sds s); -int sdscmp(sds s1, sds s2); -sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); void sdsfreesplitres(sds *tokens, int count); void sdstolower(sds s); void sdstoupper(sds s); sds sdsfromlonglong(long long value); -sds sdscatrepr(sds s, char *p, size_t len); -sds *sdssplitargs(char *line, int *argc); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); #endif diff --git a/test.c b/test.c index b3b806f..713cc06 100644 --- a/test.c +++ b/test.c @@ -14,7 +14,8 @@ enum connection_type { CONN_TCP, - CONN_UNIX + CONN_UNIX, + CONN_FD }; struct config { @@ -64,7 +65,7 @@ static redisContext *select_database(redisContext *c) { return c; } -static void disconnect(redisContext *c) { +static int disconnect(redisContext *c, int keep_fd) { redisReply *reply; /* Make sure we're on DB 9. */ @@ -75,8 +76,11 @@ static void disconnect(redisContext *c) { assert(reply != NULL); freeReplyObject(reply); - /* Free the context as well. */ + /* Free the context as well, but keep the fd if requested. */ + if (keep_fd) + return redisFreeKeepFd(c); redisFree(c); + return -1; } static redisContext *connect(struct config config) { @@ -86,6 +90,14 @@ static redisContext *connect(struct config config) { c = redisConnect(config.tcp.host, config.tcp.port); } else if (config.type == CONN_UNIX) { c = redisConnectUnix(config.unix.path); + } else if (config.type == CONN_FD) { + /* Create a dummy connection just to get an fd to inherit */ + redisContext *dummy_ctx = redisConnectUnix(config.unix.path); + if (dummy_ctx) { + int fd = disconnect(dummy_ctx, 1); + printf("Connecting to inherited fd %d\n", fd); + c = redisConnectFd(fd); + } } else { assert(NULL); } @@ -205,6 +217,28 @@ static void test_format_commands(void) { free(cmd); } +static void test_append_formatted_commands(struct config config) { + redisContext *c; + redisReply *reply; + char *cmd; + int len; + + c = connect(config); + + test("Append format command: "); + + len = redisFormatCommand(&cmd, "SET foo bar"); + + test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); + + assert(redisGetReply(c, (void*)&reply) == REDIS_OK); + + free(cmd); + freeReplyObject(reply); + + disconnect(c, 0); +} + static void test_reply_reader(void) { redisReader *reader; void *reply; @@ -293,6 +327,7 @@ static void test_blocking_connection_errors(void) { (strcmp(c->errstr,"Name or service not known") == 0 || strcmp(c->errstr,"Can't resolve: idontexist.local") == 0 || strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"No address associated with hostname") == 0 || strcmp(c->errstr,"no address associated with name") == 0)); redisFree(c); @@ -383,7 +418,7 @@ static void test_blocking_connection(struct config config) { strcasecmp(reply->element[1]->str,"pong") == 0); freeReplyObject(reply); - disconnect(c); + disconnect(c, 0); } static void test_blocking_io_errors(struct config config) { @@ -523,7 +558,7 @@ static void test_throughput(struct config config) { free(replies); printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - disconnect(c); + disconnect(c, 0); } // static long __test_callback_flags = 0; @@ -636,6 +671,7 @@ int main(int argc, char **argv) { } }; int throughput = 1; + int test_inherit_fd = 1; /* Ignore broken pipe signal (for I/O error tests). */ signal(SIGPIPE, SIG_IGN); @@ -654,6 +690,8 @@ int main(int argc, char **argv) { cfg.unix.path = argv[0]; } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { throughput = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { + test_inherit_fd = 0; } else { fprintf(stderr, "Invalid argument: %s\n", argv[0]); exit(1); @@ -670,6 +708,7 @@ int main(int argc, char **argv) { test_blocking_connection(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); + test_append_formatted_commands(cfg); if (throughput) test_throughput(cfg); printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); @@ -678,6 +717,12 @@ int main(int argc, char **argv) { test_blocking_io_errors(cfg); if (throughput) test_throughput(cfg); + if (test_inherit_fd) { + printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } + if (fails) { printf("*** %d TESTS FAILED ***\n", fails); return 1;