diff options
-rw-r--r-- | COPYING | 31 | ||||
-rw-r--r-- | Makefile | 36 | ||||
-rw-r--r-- | adapters/libevent.h | 1 | ||||
-rw-r--r-- | async.c | 15 | ||||
-rw-r--r-- | async.h | 5 | ||||
-rw-r--r-- | hiredis.c | 606 | ||||
-rw-r--r-- | hiredis.h | 80 | ||||
-rw-r--r-- | net.c | 54 | ||||
-rw-r--r-- | net.h | 4 | ||||
-rw-r--r-- | sds.c | 47 | ||||
-rw-r--r-- | sds.h | 10 | ||||
-rw-r--r-- | test.c | 122 | ||||
-rw-r--r-- | util.h | 40 |
13 files changed, 603 insertions, 448 deletions
@@ -1,10 +1,29 @@ -Copyright (c) 2006-2009, Salvatore Sanfilippo +Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> +Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> + All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +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. - * 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. +* 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. +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. @@ -8,27 +8,27 @@ BINS = hiredis-example hiredis-test uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') OPTIMIZATION?=-O3 ifeq ($(uname_S),SunOS) - CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF) + CFLAGS?=$(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF) CCLINK?=-ldl -lnsl -lsocket -lm -lpthread - LDFLAGS?=-L. -Wl,-R,. + LDFLAGS?=-L. DYLIBNAME?=libhiredis.so DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ} STLIBNAME?=libhiredis.a STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ} else ifeq ($(uname_S),Darwin) - CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings $(ARCH) $(PROF) + CFLAGS?=$(OPTIMIZATION) -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings $(ARCH) $(PROF) CCLINK?=-lm -pthread - LDFLAGS?=-L. -Wl,-rpath,. + LDFLAGS?=-L. OBJARCH?=-arch i386 -arch x86_64 DYLIBNAME?=libhiredis.dylib DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ} STLIBNAME?=libhiredis.a STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ} else - CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings $(ARCH) $(PROF) + CFLAGS?=$(OPTIMIZATION) -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings $(ARCH) $(PROF) CCLINK?=-lm -pthread - LDFLAGS?=-L. -Wl,-rpath,. + LDFLAGS?=-L. DYLIBNAME?=libhiredis.so DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ} STLIBNAME?=libhiredis.a @@ -47,10 +47,10 @@ INSTALL= cp -a all: ${DYLIBNAME} ${BINS} # Deps (use make dep to generate this) -net.o: net.c fmacros.h net.h -async.o: async.c async.h hiredis.h sds.h util.h dict.c dict.h +net.o: net.c fmacros.h net.h hiredis.h +async.o: async.c async.h hiredis.h sds.h dict.c dict.h example.o: example.c hiredis.h -hiredis.o: hiredis.c hiredis.h net.h sds.h util.h +hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h sds.o: sds.c sds.h test.o: test.c hiredis.h @@ -64,29 +64,29 @@ dynamic: ${DYLIBNAME} static: ${STLIBNAME} # Binaries: -hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME} - $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -levent example-libevent.c +hiredis-example-libevent: example-libevent.c adapters/libevent.h $(STLIBNAME) + $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) $(STLIBNAME) example-libevent.c -levent -hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME} - $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -lev example-libev.c +hiredis-example-libev: example-libev.c adapters/libev.h $(STLIBNAME) + $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) $(STLIBNAME) example-libev.c -lev ifndef AE_DIR hiredis-example-ae: @echo "Please specify AE_DIR (e.g. <redis repository>/src)" @false else -hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME} - $(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) -lhiredis example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o +hiredis-example-ae: example-ae.c adapters/ae.h $(STLIBNAME) + $(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) $(STLIBNAME) example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o endif -hiredis-%: %.o ${DYLIBNAME} - $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $< +hiredis-%: %.o $(STLIBNAME) + $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) $(STLIBNAME) $< test: hiredis-test ./hiredis-test .c.o: - $(CC) -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $< + $(CC) -std=c99 -pedantic -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $< clean: rm -rf ${DYLIBNAME} ${STLIBNAME} $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov diff --git a/adapters/libevent.h b/adapters/libevent.h index 2cc2823..2c18480 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -1,4 +1,3 @@ -#include <sys/types.h> #include <event.h> #include "../hiredis.h" #include "../async.h" @@ -1,6 +1,6 @@ /* - * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> - * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * * All rights reserved. * @@ -36,7 +36,6 @@ #include "async.h" #include "dict.c" #include "sds.h" -#include "util.h" /* Forward declaration of function in hiredis.c */ void __redisAppendCommand(redisContext *c, char *cmd, size_t len); @@ -136,11 +135,6 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path) { return ac; } -int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn) { - redisContext *c = &(ac->c); - return redisSetReplyObjectFunctions(c,fn); -} - int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { if (ac->onConnect == NULL) { ac->onConnect = fn; @@ -168,7 +162,6 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { /* Copy callback from stack to heap */ cb = malloc(sizeof(*cb)); - if (!cb) redisOOM(); if (source != NULL) { memcpy(cb,source,sizeof(*cb)); cb->next = NULL; @@ -375,7 +368,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { if (cb.fn != NULL) { __redisRunCallback(ac,&cb,reply); - c->fn->freeObject(reply); + c->reader->fn->freeObject(reply); /* Proceed with free'ing when redisAsyncFree() was called. */ if (c->flags & REDIS_FREEING) { @@ -387,7 +380,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) { * or there were no callbacks to begin with. Either way, don't * abort with an error, but simply ignore it because the client * doesn't know what the server will spit out over the wire. */ - c->fn->freeObject(reply); + c->reader->fn->freeObject(reply); } } @@ -1,6 +1,6 @@ /* - * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> - * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * * All rights reserved. * @@ -103,7 +103,6 @@ typedef struct redisAsyncContext { /* Functions that proxy to hiredis */ redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnectUnix(const char *path); -int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); void redisAsyncDisconnect(redisAsyncContext *ac); @@ -1,6 +1,6 @@ /* - * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> - * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * * All rights reserved. * @@ -29,6 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "fmacros.h" #include <string.h> #include <stdlib.h> #include <unistd.h> @@ -39,30 +40,15 @@ #include "hiredis.h" #include "net.h" #include "sds.h" -#include "util.h" - -typedef struct redisReader { - struct redisReplyObjectFunctions *fn; - sds error; /* holds optional error */ - void *reply; /* holds temporary reply */ - - sds buf; /* read buffer */ - size_t pos; /* buffer cursor */ - size_t len; /* buffer length */ - - redisReadTask rstack[3]; /* stack of read tasks */ - int ridx; /* index of stack */ - void *privdata; /* user-settable arbitrary field */ -} redisReader; static redisReply *createReplyObject(int type); static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createArrayObject(const redisReadTask *task, int elements); static void *createIntegerObject(const redisReadTask *task, long long value); static void *createNilObject(const redisReadTask *task); -static void redisSetReplyReaderError(redisReader *r, sds err); -/* Default set of functions to build the reply. */ +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ static redisReplyObjectFunctions defaultFunctions = { createStringObject, createArrayObject, @@ -73,9 +59,11 @@ static redisReplyObjectFunctions defaultFunctions = { /* Create a reply object */ static redisReply *createReplyObject(int type) { - redisReply *r = malloc(sizeof(*r)); + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; - if (!r) redisOOM(); r->type = type; return r; } @@ -89,35 +77,49 @@ void freeReplyObject(void *reply) { case REDIS_REPLY_INTEGER: break; /* Nothing to free */ case REDIS_REPLY_ARRAY: - for (j = 0; j < r->elements; j++) - if (r->element[j]) freeReplyObject(r->element[j]); - free(r->element); + if (r->elements > 0 && r->element != NULL) { + for (j = 0; j < r->elements; j++) + if (r->element[j] != NULL) + freeReplyObject(r->element[j]); + free(r->element); + } break; case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: - free(r->str); + if (r->str != NULL) + free(r->str); break; } free(r); } static void *createStringObject(const redisReadTask *task, char *str, size_t len) { - redisReply *r = createReplyObject(task->type); - char *value = malloc(len+1); - if (!value) redisOOM(); - assert(task->type == REDIS_REPLY_ERROR || + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STRING); /* Copy string value */ - memcpy(value,str,len); - value[len] = '\0'; - r->str = value; + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; r->len = len; if (task->parent) { - redisReply *parent = task->parent->obj; + parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY); parent->element[task->idx] = r; } @@ -125,12 +127,22 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len } static void *createArrayObject(const redisReadTask *task, int elements) { - redisReply *r = createReplyObject(REDIS_REPLY_ARRAY); + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + r->elements = elements; - if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL) - redisOOM(); + if (task->parent) { - redisReply *parent = task->parent->obj; + parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY); parent->element[task->idx] = r; } @@ -138,10 +150,16 @@ static void *createArrayObject(const redisReadTask *task, int elements) { } static void *createIntegerObject(const redisReadTask *task, long long value) { - redisReply *r = createReplyObject(REDIS_REPLY_INTEGER); + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + r->integer = value; + if (task->parent) { - redisReply *parent = task->parent->obj; + parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY); parent->element[task->idx] = r; } @@ -149,15 +167,83 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { } static void *createNilObject(const redisReadTask *task) { - redisReply *r = createReplyObject(REDIS_REPLY_NIL); + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + if (task->parent) { - redisReply *parent = task->parent->obj; + parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY); parent->element[task->idx] = r; } return r; } +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + if (r->buf != NULL) { + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + } + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + static char *readBytes(redisReader *r, unsigned int bytes) { char *p; if (r->len-r->pos >= bytes) { @@ -284,12 +370,18 @@ static int processLineItem(redisReader *r) { obj = (void*)(size_t)(cur->type); } + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); - return 0; + return REDIS_OK; } - return -1; + + return REDIS_ERR; } static int processBulkItem(redisReader *r) { @@ -328,15 +420,21 @@ static int processBulkItem(redisReader *r) { /* Proceed when obj was created. */ if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + r->pos += bytelen; /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); - return 0; + return REDIS_OK; } } - return -1; + + return REDIS_ERR; } static int processMultiBulkItem(redisReader *r) { @@ -348,9 +446,9 @@ static int processMultiBulkItem(redisReader *r) { /* Set error for nested multi bulks with depth > 1 */ if (r->ridx == 2) { - redisSetReplyReaderError(r,sdscatprintf(sdsempty(), - "No support for nested multi bulk replies with depth > 1")); - return -1; + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 1"); + return REDIS_ERR; } if ((p = readLine(r,NULL)) != NULL) { @@ -362,6 +460,12 @@ static int processMultiBulkItem(redisReader *r) { obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + moveToNextTask(r); } else { if (r->fn && r->fn->createArray) @@ -369,6 +473,11 @@ static int processMultiBulkItem(redisReader *r) { else obj = (void*)REDIS_REPLY_ARRAY; + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + /* Modify task stack when there are more than 0 elements. */ if (elements > 0) { cur->elements = elements; @@ -387,15 +496,15 @@ static int processMultiBulkItem(redisReader *r) { /* Set reply if this is the root object. */ if (root) r->reply = obj; - return 0; + return REDIS_OK; } - return -1; + + return REDIS_ERR; } static int processItem(redisReader *r) { redisReadTask *cur = &(r->rstack[r->ridx]); char *p; - sds byte; /* check if we need to read type */ if (cur->type < 0) { @@ -417,15 +526,12 @@ static int processItem(redisReader *r) { cur->type = REDIS_REPLY_ARRAY; break; default: - byte = sdscatrepr(sdsempty(),p,1); - redisSetReplyReaderError(r,sdscatprintf(sdsempty(), - "Protocol error, got %s as reply type byte", byte)); - sdsfree(byte); - return -1; + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; } } else { /* could not consume 1 byte */ - return -1; + return REDIS_ERR; } } @@ -441,82 +547,44 @@ static int processItem(redisReader *r) { return processMultiBulkItem(r); default: assert(NULL); - return -1; + return REDIS_ERR; /* Avoid warning. */ } } -void *redisReplyReaderCreate(void) { - redisReader *r = calloc(sizeof(redisReader),1); - r->error = NULL; - r->fn = &defaultFunctions; - r->buf = sdsempty(); - r->ridx = -1; - return r; -} +redisReader *redisReaderCreate(void) { + redisReader *r; -/* Set the function set to build the reply. Returns REDIS_OK when there - * is no temporary object and it can be set, REDIS_ERR otherwise. */ -int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn) { - redisReader *r = reader; - if (r->reply == NULL) { - r->fn = fn; - return REDIS_OK; - } - return REDIS_ERR; -} + r = calloc(sizeof(redisReader),1); + if (r == NULL) + return NULL; -/* Set the private data field that is used in the read tasks. This argument can - * be used to curry arbitrary data to the custom reply object functions. */ -int redisReplyReaderSetPrivdata(void *reader, void *privdata) { - redisReader *r = reader; - if (r->reply == NULL) { - r->privdata = privdata; - return REDIS_OK; + r->err = 0; + r->errstr[0] = '\0'; + r->fn = &defaultFunctions; + r->buf = sdsempty(); + if (r->buf == NULL) { + free(r); + return NULL; } - return REDIS_ERR; -} -/* External libraries wrapping hiredis might need access to the temporary - * variable while the reply is built up. When the reader contains an - * object in between receiving some bytes to parse, this object might - * otherwise be free'd by garbage collection. */ -void *redisReplyReaderGetObject(void *reader) { - redisReader *r = reader; - return r->reply; + r->ridx = -1; + return r; } -void redisReplyReaderFree(void *reader) { - redisReader *r = reader; - if (r->error != NULL) - sdsfree(r->error); - if (r->reply != NULL && r->fn) +void redisReaderFree(redisReader *r) { + if (r->reply != NULL && r->fn && r->fn->freeObject) r->fn->freeObject(r->reply); if (r->buf != NULL) sdsfree(r->buf); free(r); } -static void redisSetReplyReaderError(redisReader *r, sds err) { - if (r->reply != NULL) - r->fn->freeObject(r->reply); +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; - /* Clear remaining buffer when we see a protocol error. */ - if (r->buf != NULL) { - sdsfree(r->buf); - r->buf = sdsempty(); - r->pos = r->len = 0; - } - r->ridx = -1; - r->error = err; -} - -char *redisReplyReaderGetError(void *reader) { - redisReader *r = reader; - return r->error; -} - -void redisReplyReaderFeed(void *reader, const char *buf, size_t len) { - redisReader *r = reader; + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; /* Copy the provided buffer. */ if (buf != NULL && len >= 1) { @@ -525,16 +593,32 @@ void redisReplyReaderFeed(void *reader, const char *buf, size_t len) { sdsfree(r->buf); r->buf = sdsempty(); r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; } - r->buf = sdscatlen(r->buf,buf,len); + r->buf = newbuf; r->len = sdslen(r->buf); } + + return REDIS_OK; } -int redisReplyReaderGetReply(void *reader, void **reply) { - redisReader *r = reader; - if (reply != NULL) *reply = NULL; +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; /* When the buffer is empty, there will never be a reply. */ if (r->len == 0) @@ -553,9 +637,13 @@ int redisReplyReaderGetReply(void *reader, void **reply) { /* Process items in reply. */ while (r->ridx >= 0) - if (processItem(r) < 0) + if (processItem(r) != REDIS_OK) break; + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + /* 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) { @@ -566,15 +654,9 @@ int redisReplyReaderGetReply(void *reader, void **reply) { /* Emit a reply when there is one. */ if (r->ridx == -1) { - void *aux = r->reply; + if (reply != NULL) + *reply = r->reply; r->reply = NULL; - - /* Check if there actually *is* a reply. */ - if (r->error != NULL) { - return REDIS_ERR; - } else { - if (reply != NULL) *reply = aux; - } } return REDIS_OK; } @@ -593,59 +675,74 @@ static int intlen(int i) { return len; } -/* Helper function for redisvFormatCommand(). */ -static void addArgument(sds a, char ***argv, int *argc, int *totlen) { - (*argc)++; - if ((*argv = realloc(*argv, sizeof(char*)*(*argc))) == NULL) redisOOM(); - if (totlen) *totlen = *totlen+1+intlen(sdslen(a))+2+sdslen(a)+2; - (*argv)[(*argc)-1] = a; +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+intlen(len)+2+len+2; } int redisvFormatCommand(char **target, const char *format, va_list ap) { - size_t size; - const char *arg, *c = format; + const char *c = format; char *cmd = NULL; /* final command */ int pos; /* position in final command */ - sds current; /* current argument */ + sds curarg, newarg; /* current argument */ int touched = 0; /* was the current argument touched? */ - char **argv = NULL; - int argc = 0, j; + char **curargv = NULL, **newargv = NULL; + int argc = 0; int totlen = 0; + int j; /* Abort if there is not target to set */ if (target == NULL) return -1; /* Build the command string accordingly to protocol */ - current = sdsempty(); + curarg = sdsempty(); + if (curarg == NULL) + return -1; + while(*c != '\0') { if (*c != '%' || c[1] == '\0') { if (*c == ' ') { if (touched) { - addArgument(current, &argv, &argc, &totlen); - current = sdsempty(); + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto err; touched = 0; } } else { - current = sdscatlen(current,c,1); + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto err; + curarg = newarg; touched = 1; } } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + switch(c[1]) { case 's': arg = va_arg(ap,char*); size = strlen(arg); if (size > 0) - current = sdscatlen(current,arg,size); + newarg = sdscatlen(curarg,arg,size); break; case 'b': arg = va_arg(ap,char*); size = va_arg(ap,size_t); if (size > 0) - current = sdscatlen(current,arg,size); + newarg = sdscatlen(curarg,arg,size); break; case '%': - current = sdscat(current,"%"); + newarg = sdscat(curarg,"%"); break; default: /* Try to detect printf format */ @@ -687,7 +784,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { memcpy(_format,c,_l); _format[_l] = '\0'; va_copy(_cpy,ap); - current = sdscatvprintf(current,_format,_cpy); + newarg = sdscatvprintf(curarg,_format,_cpy); va_end(_cpy); /* Update current position (note: outer blocks @@ -700,6 +797,10 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { va_arg(ap,void); } } + + if (newarg == NULL) goto err; + curarg = newarg; + touched = 1; c++; } @@ -708,31 +809,55 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { /* Add the last argument if needed */ if (touched) { - addArgument(current, &argv, &argc, &totlen); + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); } else { - sdsfree(current); + sdsfree(curarg); } + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + /* Add bytes needed to hold multi bulk count */ totlen += 1+intlen(argc)+2; /* Build the command at protocol level */ cmd = malloc(totlen+1); - if (!cmd) redisOOM(); + if (cmd == NULL) goto err; + pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(argv[j])); - memcpy(cmd+pos,argv[j],sdslen(argv[j])); - pos += sdslen(argv[j]); - sdsfree(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); - free(argv); - cmd[totlen] = '\0'; + cmd[pos] = '\0'; + + free(curargv); *target = cmd; return totlen; + +err: + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + + if (curarg != NULL) + sdsfree(curarg); + + /* No need to check cmd since it is the last statement that can fail, + * but do it anyway to be as defensive as possible. */ + if (cmd != NULL) + free(cmd); + + return -1; } /* Format a command according to the Redis protocol. This function @@ -771,12 +896,14 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz totlen = 1+intlen(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += 1+intlen(len)+2+len+2; + totlen += bulklen(len); } /* Build the command at protocol level */ cmd = malloc(totlen+1); - if (!cmd) redisOOM(); + if (cmd == NULL) + return -1; + pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); @@ -787,41 +914,49 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz cmd[pos++] = '\n'; } assert(pos == totlen); - cmd[totlen] = '\0'; + cmd[pos] = '\0'; + *target = cmd; return totlen; } -void __redisSetError(redisContext *c, int type, const sds errstr) { +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + c->err = type; - if (errstr != NULL) { - c->errstr = errstr; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; } else { /* Only REDIS_ERR_IO may lack a description! */ assert(type == REDIS_ERR_IO); - c->errstr = sdsnew(strerror(errno)); + strerror_r(errno,c->errstr,sizeof(c->errstr)); } } static redisContext *redisContextInit(void) { - redisContext *c = calloc(sizeof(redisContext),1); + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + c->err = 0; - c->errstr = NULL; + c->errstr[0] = '\0'; c->obuf = sdsempty(); - c->fn = &defaultFunctions; - c->reader = NULL; + c->reader = redisReaderCreate(); return c; } void redisFree(redisContext *c) { if (c->fd > 0) close(c->fd); - if (c->errstr != NULL) - sdsfree(c->errstr); if (c->obuf != NULL) sdsfree(c->obuf); if (c->reader != NULL) - redisReplyReaderFree(c->reader); + redisReaderFree(c->reader); free(c); } @@ -877,24 +1012,6 @@ int redisSetTimeout(redisContext *c, struct timeval tv) { return REDIS_ERR; } -/* Set the replyObjectFunctions to use. Returns REDIS_ERR when the reader - * was already initialized and the function set could not be re-set. - * Return REDIS_OK when they could be set. */ -int redisSetReplyObjectFunctions(redisContext *c, redisReplyObjectFunctions *fn) { - if (c->reader != NULL) - return REDIS_ERR; - c->fn = fn; - return REDIS_OK; -} - -/* Helper function to lazily create a reply reader. */ -static void __redisCreateReplyReader(redisContext *c) { - if (c->reader == NULL) { - c->reader = redisReplyReaderCreate(); - assert(redisReplyReaderSetReplyObjectFunctions(c->reader,c->fn) == REDIS_OK); - } -} - /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * @@ -902,7 +1019,13 @@ static void __redisCreateReplyReader(redisContext *c) { * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[2048]; - int nread = read(c->fd,buf,sizeof(buf)); + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); if (nread == -1) { if (errno == EAGAIN && !(c->flags & REDIS_BLOCK)) { /* Try again later */ @@ -911,12 +1034,13 @@ int redisBufferRead(redisContext *c) { return REDIS_ERR; } } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF, - sdsnew("Server closed the connection")); + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); return REDIS_ERR; } else { - __redisCreateReplyReader(c); - redisReplyReaderFeed(c->reader,buf,nread); + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } } return REDIS_OK; } @@ -932,6 +1056,11 @@ int redisBufferRead(redisContext *c) { */ int redisBufferWrite(redisContext *c, int *done) { int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + if (sdslen(c->obuf) > 0) { nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); if (nwritten == -1) { @@ -957,10 +1086,8 @@ int redisBufferWrite(redisContext *c, int *done) { /* Internal helper function to try and get a reply from the reader, * or set an error in the context otherwise. */ int redisGetReplyFromReader(redisContext *c, void **reply) { - __redisCreateReplyReader(c); - if (redisReplyReaderGetReply(c->reader,reply) == REDIS_ERR) { - __redisSetError(c,REDIS_ERR_PROTOCOL, - sdsnew(((redisReader*)c->reader)->error)); + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } return REDIS_OK; @@ -1003,31 +1130,65 @@ int redisGetReply(redisContext *c, void **reply) { * is used, you need to call redisGetReply yourself to retrieve * the reply (or replies in pub/sub). */ -void __redisAppendCommand(redisContext *c, char *cmd, size_t len) { - c->obuf = sdscatlen(c->obuf,cmd,len); +int __redisAppendCommand(redisContext *c, char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; } -void redisvAppendCommand(redisContext *c, const char *format, va_list ap) { +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { char *cmd; int len; + len = redisvFormatCommand(&cmd,format,ap); - __redisAppendCommand(c,cmd,len); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + free(cmd); + return REDIS_OK; } -void redisAppendCommand(redisContext *c, const char *format, ...) { +int redisAppendCommand(redisContext *c, const char *format, ...) { va_list ap; + int ret; + va_start(ap,format); - redisvAppendCommand(c,format,ap); + ret = redisvAppendCommand(c,format,ap); va_end(ap); + return ret; } -void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { char *cmd; int len; + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - __redisAppendCommand(c,cmd,len); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + free(cmd); + return REDIS_OK; } /* Helper function for the redisCommand* family of functions. @@ -1041,26 +1202,21 @@ void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const * otherwise. When NULL is returned in a blocking context, the error field * in the context will be set. */ -static void *__redisCommand(redisContext *c, char *cmd, size_t len) { - void *aux = NULL; - __redisAppendCommand(c,cmd,len); +static void *__redisBlockForReply(redisContext *c) { + void *reply; if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&aux) == REDIS_OK) - return aux; - return NULL; + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; } return NULL; } void *redisvCommand(redisContext *c, const char *format, va_list ap) { - char *cmd; - int len; - void *reply = NULL; - len = redisvFormatCommand(&cmd,format,ap); - reply = __redisCommand(c,cmd,len); - free(cmd); - return reply; + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); } void *redisCommand(redisContext *c, const char *format, ...) { @@ -1073,11 +1229,7 @@ void *redisCommand(redisContext *c, const char *format, ...) { } void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - char *cmd; - int len; - void *reply = NULL; - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); - reply = __redisCommand(c,cmd,len); - free(cmd); - return reply; + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); } @@ -1,6 +1,6 @@ /* - * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> - * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com> + * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * * All rights reserved. * @@ -46,10 +46,11 @@ * error that occured. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ -#define REDIS_ERR_IO 1 /* error in read or write */ -#define REDIS_ERR_EOF 3 /* eof */ -#define REDIS_ERR_PROTOCOL 4 /* protocol error */ -#define REDIS_ERR_OTHER 2 /* something else */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ @@ -113,36 +114,56 @@ typedef struct redisReplyObjectFunctions { void (*freeObject)(void*); } redisReplyObjectFunctions; -struct redisContext; /* need forward declaration of redisContext */ - -/* Context for a connection to Redis */ -typedef struct redisContext { - int fd; - int flags; - char *obuf; /* Write buffer */ +/* State for the protocol parser */ +typedef struct redisReader { int err; /* Error flags, 0 when there is no error */ - char *errstr; /* String representation of error when applicable */ + char errstr[128]; /* String representation of error when applicable */ - /* Function set for reply buildup and reply reader */ - redisReplyObjectFunctions *fn; - void *reader; -} redisContext; + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + + redisReadTask rstack[3]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +/* Backwards compatibility, can be removed on big version bump. */ +#define redisReplyReaderCreate redisReaderCreate +#define redisReplyReaderFree redisReaderFree +#define redisReplyReaderFeed redisReaderFeed +#define redisReplyReaderGetReply redisReaderGetReply +#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) + +/* Function to free the reply objects hiredis returns by default. */ void freeReplyObject(void *reply); -void *redisReplyReaderCreate(void); -int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn); -int redisReplyReaderSetPrivdata(void *reader, void *privdata); -void *redisReplyReaderGetObject(void *reader); -char *redisReplyReaderGetError(void *reader); -void redisReplyReaderFree(void *ptr); -void redisReplyReaderFeed(void *reader, const char *buf, size_t len); -int redisReplyReaderGetReply(void *reader, void **reply); /* Functions to format a command according to the protocol. */ int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ +} redisContext; + redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); @@ -150,7 +171,6 @@ redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); int redisSetTimeout(redisContext *c, struct timeval tv); -int redisSetReplyObjectFunctions(redisContext *c, redisReplyObjectFunctions *fn); void redisFree(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); @@ -164,9 +184,9 @@ int redisGetReplyFromReader(redisContext *c, void **reply); /* Write a command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ -void redisvAppendCommand(redisContext *c, const char *format, va_list ap); -void redisAppendCommand(redisContext *c, const char *format, ...); -void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); /* Issue a command to Redis. In a blocking context, it is identical to calling * redisAppendCommand, followed by redisGetReply. The function will return @@ -1,7 +1,7 @@ /* 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> + * Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * * All rights reserved. * @@ -49,18 +49,28 @@ #include "net.h" #include "sds.h" -/* Forward declaration */ -void __redisSetError(redisContext *c, int type, sds err); +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + char buf[128]; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + strerror_r(errno,buf+len,sizeof(buf)-len); + __redisSetError(c,type,buf); +} static int redisCreateSocket(redisContext *c, int type) { int s, on = 1; if ((s = socket(type, SOCK_STREAM, 0)) == -1) { - __redisSetError(c,REDIS_ERR_IO,NULL); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } if (type == AF_INET) { if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { - __redisSetError(c,REDIS_ERR_IO,NULL); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); close(s); return REDIS_ERR; } @@ -75,8 +85,7 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) { * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(fd, F_GETFL)) == -1) { - __redisSetError(c,REDIS_ERR_IO, - sdscatprintf(sdsempty(), "fcntl(F_GETFL): %s", strerror(errno))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); close(fd); return REDIS_ERR; } @@ -87,8 +96,7 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) { flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) { - __redisSetError(c,REDIS_ERR_IO, - sdscatprintf(sdsempty(), "fcntl(F_SETFL): %s", strerror(errno))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); close(fd); return REDIS_ERR; } @@ -98,8 +106,7 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) { static int redisSetTcpNoDelay(redisContext *c, int fd) { int yes = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { - __redisSetError(c,REDIS_ERR_IO, - sdscatprintf(sdsempty(), "setsockopt(TCP_NODELAY): %s", strerror(errno))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); close(fd); return REDIS_ERR; } @@ -124,15 +131,14 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * 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))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"select(2)"); close(fd); return REDIS_ERR; } if (!FD_ISSET(fd, &wfd)) { errno = ETIMEDOUT; - __redisSetError(c,REDIS_ERR_IO,NULL); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); close(fd); return REDIS_ERR; } @@ -140,15 +146,14 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * 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))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); close(fd); return REDIS_ERR; } if (err) { errno = err; - __redisSetError(c,REDIS_ERR_IO,NULL); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); close(fd); return REDIS_ERR; } @@ -156,20 +161,18 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * return REDIS_OK; } - __redisSetError(c,REDIS_ERR_IO,NULL); + __redisSetErrorFromErrno(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))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); 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))); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); return REDIS_ERR; } return REDIS_OK; @@ -192,8 +195,9 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct t he = gethostbyname(addr); if (he == NULL) { - __redisSetError(c,REDIS_ERR_OTHER, - sdscatprintf(sdsempty(),"Can't resolve: %s",addr)); + char buf[128]; + snprintf(buf,sizeof(buf),"Can't resolve: %s", addr); + __redisSetError(c,REDIS_ERR_OTHER,buf); close(s); return REDIS_ERR; } @@ -1,7 +1,7 @@ /* 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> + * Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> + * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * * All rights reserved. * @@ -28,18 +28,18 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define SDS_ABORT_ON_OOM - -#include "sds.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> +#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 sds sdsnewlen(const void *init, size_t initlen) { struct sdshdr *sh; @@ -69,11 +69,6 @@ sds sdsnew(const char *init) { return sdsnewlen(init, initlen); } -size_t sdslen(const sds s) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - return sh->len; -} - sds sdsdup(const sds s) { return sdsnewlen(s, sdslen(s)); } @@ -83,11 +78,6 @@ void sdsfree(sds s) { free(s-sizeof(struct sdshdr)); } -size_t sdsavail(sds s) { - struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); - return sh->free; -} - void sdsupdatelen(sds s) { struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); int reallen = strlen(s); @@ -388,17 +378,19 @@ sds sdsfromlonglong(long long value) { sds sdscatrepr(sds s, char *p, size_t len) { s = sdscatlen(s,"\"",1); + if (s == NULL) return NULL; + while(len--) { switch(*p) { case '\\': case '"': s = sdscatprintf(s,"\\%c",*p); break; - case '\n': s = sdscatlen(s,"\\n",1); break; - case '\r': s = sdscatlen(s,"\\r",1); break; - case '\t': s = sdscatlen(s,"\\t",1); break; - case '\a': s = sdscatlen(s,"\\a",1); break; - case '\b': s = sdscatlen(s,"\\b",1); break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; default: if (isprint(*p)) s = sdscatprintf(s,"%c",*p); @@ -407,6 +399,7 @@ sds sdscatrepr(sds s, char *p, size_t len) { break; } p++; + if (s == NULL) return NULL; } return sdscatlen(s,"\"",1); } @@ -426,7 +419,7 @@ sds sdscatrepr(sds s, char *p, size_t len) { sds *sdssplitargs(char *line, int *argc) { char *p = line; char *current = NULL; - char **vector = NULL; + char **vector = NULL, **_vector = NULL; *argc = 0; while(1) { @@ -437,7 +430,11 @@ sds *sdssplitargs(char *line, int *argc) { int inq=0; /* set to 1 if we are in "quotes" */ int done=0; - if (current == NULL) current = sdsempty(); + if (current == NULL) { + current = sdsempty(); + if (current == NULL) goto err; + } + while(!done) { if (inq) { if (*p == '\\' && *(p+1)) { @@ -481,9 +478,13 @@ sds *sdssplitargs(char *line, int *argc) { } } if (*p) p++; + if (current == NULL) goto err; } /* add the token to the vector */ - vector = realloc(vector,((*argc)+1)*sizeof(char*)); + _vector = realloc(vector,((*argc)+1)*sizeof(char*)); + if (_vector == NULL) goto err; + + vector = _vector; vector[*argc] = current; (*argc)++; current = NULL; @@ -495,8 +496,8 @@ sds *sdssplitargs(char *line, int *argc) { err: while((*argc)--) sdsfree(vector[*argc]); - free(vector); - if (current) sdsfree(current); + if (vector != NULL) free(vector); + if (current != NULL) sdsfree(current); return NULL; } @@ -42,6 +42,16 @@ struct sdshdr { char buf[]; }; +static inline size_t sdslen(const sds s) { + struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + return sh->len; +} + +static inline size_t sdsavail(const sds s) { + struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + return sh->free; +} + sds sdsnewlen(const void *init, size_t initlen); sds sdsnew(const char *init); sds sdsempty(void); @@ -268,75 +268,71 @@ static void test_blocking_connection(void) { } static void test_reply_reader(void) { - void *reader; + redisReader *reader; void *reply; - char *err; int ret; test("Error handling in reply parser: "); - reader = redisReplyReaderCreate(); - redisReplyReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReplyReaderGetReply(reader,NULL); - err = redisReplyReaderGetError(reader); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); test_cond(ret == REDIS_ERR && - strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0); - redisReplyReaderFree(reader); + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); /* when the reply already contains multiple items, they must be free'd * on an error. valgrind will bark when this doesn't happen. */ test("Memory cleanup in reply parser: "); - reader = redisReplyReaderCreate(); - redisReplyReaderFeed(reader,(char*)"*2\r\n",4); - redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); - redisReplyReaderFeed(reader,(char*)"@foo\r\n",6); - ret = redisReplyReaderGetReply(reader,NULL); - err = redisReplyReaderGetError(reader); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*2\r\n",4); + redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); test_cond(ret == REDIS_ERR && - strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0); - redisReplyReaderFree(reader); + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); test("Set error on nested multi bulks with depth > 1: "); - reader = redisReplyReaderCreate(); - redisReplyReaderFeed(reader,(char*)"*1\r\n",4); - redisReplyReaderFeed(reader,(char*)"*1\r\n",4); - redisReplyReaderFeed(reader,(char*)"*1\r\n",4); - ret = redisReplyReaderGetReply(reader,NULL); - err = redisReplyReaderGetError(reader); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*1\r\n",4); + redisReaderFeed(reader,(char*)"*1\r\n",4); + redisReaderFeed(reader,(char*)"*1\r\n",4); + ret = redisReaderGetReply(reader,NULL); test_cond(ret == REDIS_ERR && - strncasecmp(err,"No support for",14) == 0); - redisReplyReaderFree(reader); + strncasecmp(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); test("Works with NULL functions for reply: "); - reader = redisReplyReaderCreate(); - redisReplyReaderSetReplyObjectFunctions(reader,NULL); - redisReplyReaderFeed(reader,(char*)"+OK\r\n",5); - ret = redisReplyReaderGetReply(reader,&reply); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r\n",5); + ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReplyReaderFree(reader); + redisReaderFree(reader); test("Works when a single newline (\\r\\n) covers two calls to feed: "); - reader = redisReplyReaderCreate(); - redisReplyReaderSetReplyObjectFunctions(reader,NULL); - redisReplyReaderFeed(reader,(char*)"+OK\r",4); - ret = redisReplyReaderGetReply(reader,&reply); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); assert(ret == REDIS_OK && reply == NULL); - redisReplyReaderFeed(reader,(char*)"\n",1); - ret = redisReplyReaderGetReply(reader,&reply); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReplyReaderFree(reader); + redisReaderFree(reader); - test("Properly reset state after protocol error: "); - reader = redisReplyReaderCreate(); - redisReplyReaderSetReplyObjectFunctions(reader,NULL); - redisReplyReaderFeed(reader,(char*)"x",1); - ret = redisReplyReaderGetReply(reader,&reply); + test("Don't reset state after protocol error: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"x",1); + ret = redisReaderGetReply(reader,&reply); assert(ret == REDIS_ERR); - ret = redisReplyReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == NULL) + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); } static void test_throughput(void) { - int i; + int i, num; long long t1, t2; redisContext *c = blocking_context; redisReply **replies; @@ -345,55 +341,57 @@ static void test_throughput(void) { for (i = 0; i < 500; i++) freeReplyObject(redisCommand(c,"LPUSH mylist foo")); - replies = malloc(sizeof(redisReply*)*1000); + num = 1000; + replies = malloc(sizeof(redisReply*)*num); t1 = usec(); - for (i = 0; i < 1000; i++) { + for (i = 0; i < num; i++) { replies[i] = redisCommand(c,"PING"); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); } t2 = usec(); - for (i = 0; i < 1000; i++) freeReplyObject(replies[i]); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); free(replies); - printf("\t(1000x PING: %.3fs)\n", (t2-t1)/1000000.0); + printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); - replies = malloc(sizeof(redisReply*)*1000); + replies = malloc(sizeof(redisReply*)*num); t1 = usec(); - for (i = 0; i < 1000; i++) { + for (i = 0; i < num; i++) { replies[i] = redisCommand(c,"LRANGE mylist 0 499"); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); assert(replies[i] != NULL && replies[i]->elements == 500); } t2 = usec(); - for (i = 0; i < 1000; i++) freeReplyObject(replies[i]); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); free(replies); - printf("\t(1000x LRANGE with 500 elements: %.3fs)\n", (t2-t1)/1000000.0); + printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); - replies = malloc(sizeof(redisReply*)*10000); - for (i = 0; i < 10000; i++) + num = 10000; + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) redisAppendCommand(c,"PING"); t1 = usec(); - for (i = 0; i < 10000; i++) { + for (i = 0; i < num; i++) { assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); } t2 = usec(); - for (i = 0; i < 10000; i++) freeReplyObject(replies[i]); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); free(replies); - printf("\t(10000x PING (pipelined): %.3fs)\n", (t2-t1)/1000000.0); + printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); - replies = malloc(sizeof(redisReply*)*10000); - for (i = 0; i < 10000; i++) + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) redisAppendCommand(c,"LRANGE mylist 0 499"); t1 = usec(); - for (i = 0; i < 10000; i++) { + for (i = 0; i < num; i++) { assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); assert(replies[i] != NULL && replies[i]->elements == 500); } t2 = usec(); - for (i = 0; i < 10000; i++) freeReplyObject(replies[i]); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); free(replies); - printf("\t(10000x LRANGE with 500 elements: %.3fs)\n", (t2-t1)/1000000.0); + printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); } static void cleanup(void) { @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> - * 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 __UTIL_H -#define __UTIL_H -#include <stdlib.h> - -/* Abort on out of memory */ -static void redisOOM(void) { - fprintf(stderr,"Out of memory in hiredis"); - exit(1); -} - -#endif |