diff options
author | antirez <antirez@gmail.com> | 2012-02-19 20:26:36 +0100 |
---|---|---|
committer | antirez <antirez@gmail.com> | 2012-02-19 20:26:50 +0100 |
commit | b66e5add82bf6785421111f505f41ed8bf09a6f7 (patch) | |
tree | ed34f7924ed6d46fbd0f46bf14e0410575fa9355 /deps | |
parent | cb598cdd59f48ffa1cb2f3d82d95b8b9eaee7195 (diff) | |
download | redis-b66e5add82bf6785421111f505f41ed8bf09a6f7.tar.gz |
The hiredis lib shipped with Redis was updated to latest version.
Diffstat (limited to 'deps')
-rw-r--r-- | deps/hiredis/CHANGELOG.md | 16 | ||||
-rw-r--r-- | deps/hiredis/COPYING | 31 | ||||
-rw-r--r-- | deps/hiredis/Makefile | 163 | ||||
-rw-r--r-- | deps/hiredis/README.md | 45 | ||||
-rw-r--r-- | deps/hiredis/TODO | 2 | ||||
-rw-r--r-- | deps/hiredis/adapters/ae.h | 34 | ||||
-rw-r--r-- | deps/hiredis/adapters/libev.h | 34 | ||||
-rw-r--r-- | deps/hiredis/adapters/libevent.h | 34 | ||||
-rw-r--r-- | deps/hiredis/async.c | 115 | ||||
-rw-r--r-- | deps/hiredis/async.h | 7 | ||||
-rw-r--r-- | deps/hiredis/example-ae.c | 13 | ||||
-rw-r--r-- | deps/hiredis/example-libev.c | 12 | ||||
-rw-r--r-- | deps/hiredis/example-libevent.c | 12 | ||||
-rw-r--r-- | deps/hiredis/fmacros.h | 8 | ||||
-rw-r--r-- | deps/hiredis/hiredis.c | 708 | ||||
-rw-r--r-- | deps/hiredis/hiredis.h | 84 | ||||
-rw-r--r-- | deps/hiredis/net.c | 177 | ||||
-rw-r--r-- | deps/hiredis/net.h | 5 | ||||
-rw-r--r-- | deps/hiredis/sds.c | 35 | ||||
-rw-r--r-- | deps/hiredis/test.c | 420 | ||||
-rw-r--r-- | deps/hiredis/util.h | 40 |
21 files changed, 1261 insertions, 734 deletions
diff --git a/deps/hiredis/CHANGELOG.md b/deps/hiredis/CHANGELOG.md new file mode 100644 index 000000000..d41db8a60 --- /dev/null +++ b/deps/hiredis/CHANGELOG.md @@ -0,0 +1,16 @@ +### 0.10.1 + +* Makefile overhaul. Important to check out if you override one or more + variables using environment variables or via arguments to the "make" tool. + +* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements + being created by the default reply object functions. + +* Issue #43: Don't crash in an asynchronous context when Redis returns an error + reply after the connection has been made (this happens when the maximum + number of connections is reached). + +### 0.10.0 + +* See commit log. + diff --git a/deps/hiredis/COPYING b/deps/hiredis/COPYING index 3e704e3eb..a5fc97395 100644 --- a/deps/hiredis/COPYING +++ b/deps/hiredis/COPYING @@ -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. diff --git a/deps/hiredis/Makefile b/deps/hiredis/Makefile index 2a84b9b3f..57f057ee1 100644 --- a/deps/hiredis/Makefile +++ b/deps/hiredis/Makefile @@ -1,115 +1,148 @@ # Hiredis Makefile -# Copyright (C) 2010 Salvatore Sanfilippo <antirez at gmail dot com> +# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com> +# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> # This file is released under the BSD license, see the COPYING file -OBJ = net.o hiredis.o sds.o async.o -BINS = hiredis-example hiredis-test +OBJ=net.o hiredis.o sds.o async.o +BINS=hiredis-example hiredis-test +LIBNAME=libhiredis -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +HIREDIS_MAJOR=0 +HIREDIS_MINOR=10 + +# 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 +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +DEBUG?= -g -ggdb +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) +REAL_LDFLAGS=$(LDFLAGS) + +DYLIBSUFFIX=so +STLIBSUFFIX=a +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) +DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) +STLIB_MAKE_CMD=ar rcs $(STLIBNAME) + +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ifeq ($(uname_S),SunOS) - CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF) - CCLINK?=-ldl -lnsl -lsocket -lm -lpthread - LDFLAGS?=-L. -Wl,-R,. - 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) - CCLINK?=-lm -pthread - LDFLAGS?=-L. -Wl,-rpath,. - 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) - CCLINK?=-lm -pthread - LDFLAGS?=-L. -Wl,-rpath,. - DYLIBNAME?=libhiredis.so - DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ} - STLIBNAME?=libhiredis.a - STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ} + REAL_LDFLAGS+= -ldl -lnsl -lsocket + DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) + INSTALL= cp -r endif +ifeq ($(uname_S),Darwin) + DYLIBSUFFIX=dylib + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX) + DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX) + DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) endif -CCOPT= $(CFLAGS) $(CCLINK) -DEBUG?= -g -ggdb - -PREFIX?= /usr/local -INSTALL_INC= $(PREFIX)/include/hiredis -INSTALL_LIB= $(PREFIX)/lib -INSTALL= cp -a - -all: ${DYLIBNAME} ${BINS} +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 -${DYLIBNAME}: ${OBJ} - ${DYLIB_MAKE_CMD} +$(DYLIBNAME): $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) -${STLIBNAME}: ${OBJ} - ${STLIB_MAKE_CMD} +$(STLIBNAME): $(OBJ) + $(STLIB_MAKE_CMD) $(OBJ) -dynamic: ${DYLIBNAME} -static: ${STLIBNAME} +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 $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -levent example-libevent.c $(STLIBNAME) -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 $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -lev example-libev.c $(STLIBNAME) 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 $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I$(AE_DIR) $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o example-ae.c $(STLIBNAME) endif -hiredis-%: %.o ${DYLIBNAME} - $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $< +hiredis-%: %.o $(STLIBNAME) + $(CC) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) 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 || \ + ( kill `cat /tmp/hiredis-test-redis.pid` && false ) + kill `cat /tmp/hiredis-test-redis.pid` + .c.o: - $(CC) -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $< + $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< clean: - rm -rf ${DYLIBNAME} ${STLIBNAME} $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov + rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov dep: $(CC) -MM *.c -install: ${DYLIBNAME} ${STLIBNAME} - mkdir -p $(INSTALL_INC) $(INSTALL_LIB) - $(INSTALL) hiredis.h async.h adapters $(INSTALL_INC) - $(INSTALL) ${DYLIBNAME} ${STLIBNAME} $(INSTALL_LIB) +# Installation related variables and target +PREFIX?=/usr/local +INCLUDE_PATH?=include/hiredis +LIBRARY_PATH?=lib +INSTALL_INCLUDE_PATH= $(PREFIX)/$(INCLUDE_PATH) +INSTALL_LIBRARY_PATH= $(PREFIX)/$(LIBRARY_PATH) + +ifeq ($(uname_S),SunOS) + INSTALL?= cp -r +endif + +INSTALL?= cp -a + +install: $(DYLIBNAME) $(STLIBNAME) + mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) + $(INSTALL) hiredis.h async.h adapters $(INSTALL_INCLUDE_PATH) + $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) + $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) 32bit: @echo "" - @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386" + @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" @echo "" - $(MAKE) ARCH="-m32" + $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" gprof: - $(MAKE) PROF="-pg" + $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" gcov: - $(MAKE) PROF="-fprofile-arcs -ftest-coverage" + $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + +coverage: gcov + make check + mkdir -p tmp/lcov + lcov -d . -c -o tmp/lcov/hiredis.info + genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info noopt: $(MAKE) OPTIMIZATION="" + +.PHONY: all test check clean dep install 32bit gprof gcov noopt diff --git a/deps/hiredis/README.md b/deps/hiredis/README.md index 5a77cd381..a58101cc6 100644 --- a/deps/hiredis/README.md +++ b/deps/hiredis/README.md @@ -116,6 +116,12 @@ Note that this function will take care of freeing sub-replies objects contained in arrays and nested arrays, so there is no need for the user to free the sub replies (it is actually harmful and will corrupt the memory). +**Important:** the current version of hiredis (0.10.0) free's replies when the +asynchronous API is used. This means you should not call `freeReplyObject` when +you use this API. The reply is cleaned up by hiredis _after_ the callback +returns. This behavior will probably change in future releases, so make sure to +keep an eye on the changelog when upgrading (see issue #39). + ### Cleaning up To disconnect and free the context the following function can be used: @@ -280,7 +286,8 @@ is being disconnected per user-request, no new commands may be added to the outp returned on calls to the `redisAsyncCommand` family. If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback -for a command is non-`NULL`, it is responsible for cleaning up the reply. +for a command is non-`NULL`, the memory is free'd immediately following the callback: the reply is only +valid for the duration of the callback. All pending callbacks are called with a `NULL` reply when the context encountered an error. @@ -303,7 +310,41 @@ See the `adapters/` directory for bindings to *libev* and *libevent*. ## Reply parsing API -To be done. +Hiredis comes with a reply parsing API that makes it easy for writing higher +level language bindings. + +The reply parsing API consists of the following functions: + + redisReader *redisReaderCreate(void); + void redisReaderFree(redisReader *reader); + int redisReaderFeed(redisReader *reader, const char *buf, size_t len); + int redisReaderGetReply(redisReader *reader, void **reply); + +### Usage + +The function `redisReaderCreate` creates a `redisReader` structure that holds a +buffer with unparsed data and state for the protocol parser. + +Incoming data -- most likely from a socket -- can be placed in the internal +buffer of the `redisReader` using `redisReaderFeed`. This function will make a +copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed +when `redisReaderGetReply` is called. This function returns an integer status +and a reply object (as described above) via `void **reply`. The returned status +can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went +wrong (either a protocol error, or an out of memory error). + +### Customizing replies + +The function `redisReaderGetReply` creates `redisReply` and makes the function +argument `reply` point to the created `redisReply` variable. For instance, if +the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` +will hold the status as a vanilla C string. However, the functions that are +responsible for creating instances of the `redisReply` can be customized by +setting the `fn` field on the `redisReader` struct. This should be done +immediately after creating the `redisReader`. + +For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) +uses customized reply object functions to create Ruby objects. ## AUTHORS diff --git a/deps/hiredis/TODO b/deps/hiredis/TODO deleted file mode 100644 index de70b947b..000000000 --- a/deps/hiredis/TODO +++ /dev/null @@ -1,2 +0,0 @@ -- add redisCommandVector() -- add support for pipelining diff --git a/deps/hiredis/adapters/ae.h b/deps/hiredis/adapters/ae.h index b8b2228ed..65235f802 100644 --- a/deps/hiredis/adapters/ae.h +++ b/deps/hiredis/adapters/ae.h @@ -1,3 +1,5 @@ +#ifndef __HIREDIS_AE_H__ +#define __HIREDIS_AE_H__ #include <sys/types.h> #include <ae.h> #include "../hiredis.h" @@ -10,21 +12,21 @@ typedef struct redisAeEvents { int reading, writing; } redisAeEvents; -void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { +static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { ((void)el); ((void)fd); ((void)mask); redisAeEvents *e = (redisAeEvents*)privdata; redisAsyncHandleRead(e->context); } -void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { +static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { ((void)el); ((void)fd); ((void)mask); redisAeEvents *e = (redisAeEvents*)privdata; redisAsyncHandleWrite(e->context); } -void redisAeAddRead(void *privdata) { +static void redisAeAddRead(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (!e->reading) { @@ -33,7 +35,7 @@ void redisAeAddRead(void *privdata) { } } -void redisAeDelRead(void *privdata) { +static void redisAeDelRead(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (e->reading) { @@ -42,7 +44,7 @@ void redisAeDelRead(void *privdata) { } } -void redisAeAddWrite(void *privdata) { +static void redisAeAddWrite(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (!e->writing) { @@ -51,7 +53,7 @@ void redisAeAddWrite(void *privdata) { } } -void redisAeDelWrite(void *privdata) { +static void redisAeDelWrite(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (e->writing) { @@ -60,19 +62,19 @@ void redisAeDelWrite(void *privdata) { } } -void redisAeCleanup(void *privdata) { +static void redisAeCleanup(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; redisAeDelRead(privdata); redisAeDelWrite(privdata); free(e); } -int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { +static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { redisContext *c = &(ac->c); redisAeEvents *e; /* Nothing should be attached when something is already attached */ - if (ac->_adapter_data != NULL) + if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ @@ -83,13 +85,13 @@ int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { e->reading = e->writing = 0; /* Register functions to start/stop listening for events */ - ac->evAddRead = redisAeAddRead; - ac->evDelRead = redisAeDelRead; - ac->evAddWrite = redisAeAddWrite; - ac->evDelWrite = redisAeDelWrite; - ac->evCleanup = redisAeCleanup; - ac->_adapter_data = e; + ac->ev.addRead = redisAeAddRead; + ac->ev.delRead = redisAeDelRead; + ac->ev.addWrite = redisAeAddWrite; + ac->ev.delWrite = redisAeDelWrite; + ac->ev.cleanup = redisAeCleanup; + ac->ev.data = e; return REDIS_OK; } - +#endif diff --git a/deps/hiredis/adapters/libev.h b/deps/hiredis/adapters/libev.h index 3b9ed6560..534d74360 100644 --- a/deps/hiredis/adapters/libev.h +++ b/deps/hiredis/adapters/libev.h @@ -1,3 +1,6 @@ +#ifndef __HIREDIS_LIBEV_H__ +#define __HIREDIS_LIBEV_H__ +#include <stdlib.h> #include <sys/types.h> #include <ev.h> #include "../hiredis.h" @@ -10,7 +13,7 @@ typedef struct redisLibevEvents { ev_io rev, wev; } redisLibevEvents; -void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { +static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY ((void)loop); #endif @@ -20,7 +23,7 @@ void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { redisAsyncHandleRead(e->context); } -void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { +static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY ((void)loop); #endif @@ -30,7 +33,7 @@ void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { redisAsyncHandleWrite(e->context); } -void redisLibevAddRead(void *privdata) { +static void redisLibevAddRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; struct ev_loop *loop = e->loop; ((void)loop); @@ -40,7 +43,7 @@ void redisLibevAddRead(void *privdata) { } } -void redisLibevDelRead(void *privdata) { +static void redisLibevDelRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; struct ev_loop *loop = e->loop; ((void)loop); @@ -50,7 +53,7 @@ void redisLibevDelRead(void *privdata) { } } -void redisLibevAddWrite(void *privdata) { +static void redisLibevAddWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; struct ev_loop *loop = e->loop; ((void)loop); @@ -60,7 +63,7 @@ void redisLibevAddWrite(void *privdata) { } } -void redisLibevDelWrite(void *privdata) { +static void redisLibevDelWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; struct ev_loop *loop = e->loop; ((void)loop); @@ -70,19 +73,19 @@ void redisLibevDelWrite(void *privdata) { } } -void redisLibevCleanup(void *privdata) { +static void redisLibevCleanup(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevDelRead(privdata); redisLibevDelWrite(privdata); free(e); } -int redisLibevAttach(EV_P_ redisAsyncContext *ac) { +static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { redisContext *c = &(ac->c); redisLibevEvents *e; /* Nothing should be attached when something is already attached */ - if (ac->_adapter_data != NULL) + if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ @@ -98,12 +101,12 @@ int redisLibevAttach(EV_P_ redisAsyncContext *ac) { e->wev.data = e; /* Register functions to start/stop listening for events */ - ac->evAddRead = redisLibevAddRead; - ac->evDelRead = redisLibevDelRead; - ac->evAddWrite = redisLibevAddWrite; - ac->evDelWrite = redisLibevDelWrite; - ac->evCleanup = redisLibevCleanup; - ac->_adapter_data = e; + ac->ev.addRead = redisLibevAddRead; + ac->ev.delRead = redisLibevDelRead; + ac->ev.addWrite = redisLibevAddWrite; + ac->ev.delWrite = redisLibevDelWrite; + ac->ev.cleanup = redisLibevCleanup; + ac->ev.data = e; /* Initialize read/write events */ ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); @@ -111,3 +114,4 @@ int redisLibevAttach(EV_P_ redisAsyncContext *ac) { return REDIS_OK; } +#endif diff --git a/deps/hiredis/adapters/libevent.h b/deps/hiredis/adapters/libevent.h index dc1f5c739..4055ec0f1 100644 --- a/deps/hiredis/adapters/libevent.h +++ b/deps/hiredis/adapters/libevent.h @@ -1,4 +1,5 @@ -#include <sys/types.h> +#ifndef __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ #include <event.h> #include "../hiredis.h" #include "../async.h" @@ -8,51 +9,51 @@ typedef struct redisLibeventEvents { struct event rev, wev; } redisLibeventEvents; -void redisLibeventReadEvent(int fd, short event, void *arg) { +static void redisLibeventReadEvent(int fd, short event, void *arg) { ((void)fd); ((void)event); redisLibeventEvents *e = (redisLibeventEvents*)arg; redisAsyncHandleRead(e->context); } -void redisLibeventWriteEvent(int fd, short event, void *arg) { +static void redisLibeventWriteEvent(int fd, short event, void *arg) { ((void)fd); ((void)event); redisLibeventEvents *e = (redisLibeventEvents*)arg; redisAsyncHandleWrite(e->context); } -void redisLibeventAddRead(void *privdata) { +static void redisLibeventAddRead(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; event_add(&e->rev,NULL); } -void redisLibeventDelRead(void *privdata) { +static void redisLibeventDelRead(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; event_del(&e->rev); } -void redisLibeventAddWrite(void *privdata) { +static void redisLibeventAddWrite(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; event_add(&e->wev,NULL); } -void redisLibeventDelWrite(void *privdata) { +static void redisLibeventDelWrite(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; event_del(&e->wev); } -void redisLibeventCleanup(void *privdata) { +static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; event_del(&e->rev); event_del(&e->wev); free(e); } -int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { redisContext *c = &(ac->c); redisLibeventEvents *e; /* Nothing should be attached when something is already attached */ - if (ac->_adapter_data != NULL) + if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ @@ -60,12 +61,12 @@ int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { e->context = ac; /* Register functions to start/stop listening for events */ - ac->evAddRead = redisLibeventAddRead; - ac->evDelRead = redisLibeventDelRead; - ac->evAddWrite = redisLibeventAddWrite; - ac->evDelWrite = redisLibeventDelWrite; - ac->evCleanup = redisLibeventCleanup; - ac->_adapter_data = e; + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.data = e; /* Initialize and install read/write events */ event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); @@ -74,3 +75,4 @@ int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { event_base_set(base,&e->wev); return REDIS_OK; } +#endif diff --git a/deps/hiredis/async.c b/deps/hiredis/async.c index 76c4cc3ad..f83e2f51a 100644 --- a/deps/hiredis/async.c +++ b/deps/hiredis/async.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. * @@ -29,14 +29,33 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "fmacros.h" +#include <stdlib.h> #include <string.h> #include <strings.h> #include <assert.h> #include <ctype.h> +#include <errno.h> #include "async.h" +#include "net.h" #include "dict.c" #include "sds.h" -#include "util.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); /* Forward declaration of function in hiredis.c */ void __redisAppendCommand(redisContext *c, char *cmd, size_t len); @@ -136,11 +155,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; @@ -148,7 +162,7 @@ int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn /* The common way to detect an established connection is to wait for * the first write event to be fired. This assumes the related event * library functions are already set. */ - if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); + _EL_ADD_WRITE(ac); return REDIS_OK; } return REDIS_ERR; @@ -168,7 +182,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; @@ -237,7 +250,7 @@ static void __redisAsyncFree(redisAsyncContext *ac) { dictRelease(ac->sub.patterns); /* Signal event lib to clean up */ - if (ac->ev.cleanup) ac->ev.cleanup(ac->ev.data); + _EL_CLEANUP(ac); /* Execute disconnect callback. When redisAsyncFree() initiated destroying * this context, the status will always be REDIS_OK. */ @@ -368,14 +381,27 @@ void redisProcessCallbacks(redisAsyncContext *ac) { /* Even if the context is subscribed, pending regular callbacks will * get a reply before pub/sub messages arrive. */ if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { - /* No more regular callbacks, the context *must* be subscribed. */ + /* A spontaneous reply in a not-subscribed context can only be the + * error reply that is sent when a new connection exceeds the + * maximum number of allowed connections on the server side. This + * is seen as an error instead of a regular reply because the + * server closes the connection after sending it. To prevent the + * error from being overwritten by an EOF error the connection is + * closed here. See issue #43. */ + if ( !(c->flags & REDIS_SUBSCRIBED) && ((redisReply*)reply)->type == REDIS_REPLY_ERROR ) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed. */ assert(c->flags & REDIS_SUBSCRIBED); __redisGetSubscribeCallback(ac,reply,&cb); } 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 +413,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); } } @@ -396,17 +422,48 @@ void redisProcessCallbacks(redisAsyncContext *ac) { __redisAsyncDisconnect(ac); } +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not succesful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c,c->fd) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. */ void redisAsyncHandleRead(redisAsyncContext *ac) { redisContext *c = &(ac->c); + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + if (redisBufferRead(c) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Always re-schedule reads */ - if (ac->ev.addRead) ac->ev.addRead(ac->ev.data); + _EL_ADD_READ(ac); redisProcessCallbacks(ac); } } @@ -415,24 +472,26 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); int done = 0; + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + if (redisBufferWrite(c,&done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Continue writing when not done, stop writing otherwise */ - if (!done) { - if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); - } else { - if (ac->ev.delWrite) ac->ev.delWrite(ac->ev.data); - } + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); /* Always schedule reads after writes */ - if (ac->ev.addRead) ac->ev.addRead(ac->ev.data); - - /* Fire onConnect when this is the first write event. */ - if (!(c->flags & REDIS_CONNECTED)) { - c->flags |= REDIS_CONNECTED; - if (ac->onConnect) ac->onConnect(ac); - } + _EL_ADD_READ(ac); } } @@ -510,7 +569,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void __redisAppendCommand(c,cmd,len); /* Always schedule a write when the write buffer is non-empty */ - if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); + _EL_ADD_WRITE(ac); return REDIS_OK; } diff --git a/deps/hiredis/async.h b/deps/hiredis/async.h index ba2b6f549..268274e8e 100644 --- a/deps/hiredis/async.h +++ b/deps/hiredis/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. * @@ -55,7 +55,7 @@ typedef struct redisCallbackList { /* Connection callback prototypes */ typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); -typedef void (redisConnectCallback)(const struct redisAsyncContext*); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); /* Context for an async connection to Redis */ typedef struct redisAsyncContext { @@ -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); diff --git a/deps/hiredis/example-ae.c b/deps/hiredis/example-ae.c index 28c34dc9f..5ed34a3a6 100644 --- a/deps/hiredis/example-ae.c +++ b/deps/hiredis/example-ae.c @@ -18,17 +18,20 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisAsyncDisconnect(c); } -void connectCallback(const redisAsyncContext *c) { - ((void)c); - printf("connected...\n"); +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); + return; } - printf("disconnected...\n"); - aeStop(loop); + printf("Disconnected...\n"); } int main (int argc, char **argv) { diff --git a/deps/hiredis/example-libev.c b/deps/hiredis/example-libev.c index 8efa1e39b..7894f1f48 100644 --- a/deps/hiredis/example-libev.c +++ b/deps/hiredis/example-libev.c @@ -15,16 +15,20 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisAsyncDisconnect(c); } -void connectCallback(const redisAsyncContext *c) { - ((void)c); - printf("connected...\n"); +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); + return; } - printf("disconnected...\n"); + printf("Disconnected...\n"); } int main (int argc, char **argv) { diff --git a/deps/hiredis/example-libevent.c b/deps/hiredis/example-libevent.c index f6f8c8325..9da8e02bf 100644 --- a/deps/hiredis/example-libevent.c +++ b/deps/hiredis/example-libevent.c @@ -15,16 +15,20 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisAsyncDisconnect(c); } -void connectCallback(const redisAsyncContext *c) { - ((void)c); - printf("connected...\n"); +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); + return; } - printf("disconnected...\n"); + printf("Disconnected...\n"); } int main (int argc, char **argv) { diff --git a/deps/hiredis/fmacros.h b/deps/hiredis/fmacros.h index 65f9692ce..21cd9cfee 100644 --- a/deps/hiredis/fmacros.h +++ b/deps/hiredis/fmacros.h @@ -1,12 +1,14 @@ #ifndef __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H -#ifndef _BSD_SOURCE +#if !defined(_BSD_SOURCE) #define _BSD_SOURCE #endif -#ifdef __linux__ -#define _XOPEN_SOURCE 700 +#if defined(__sun__) +#define _POSIX_C_SOURCE 200112L +#elif defined(__linux__) +#define _XOPEN_SOURCE 600 #else #define _XOPEN_SOURCE #endif diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 976e94f9c..1a57adb1b 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.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. * @@ -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[9]; /* 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->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,24 @@ 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; + + if (elements > 0) { + 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 +152,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 +169,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 +372,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 +422,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) { @@ -346,11 +446,11 @@ static int processMultiBulkItem(redisReader *r) { long elements; int root = 0; - /* Set error for nested multi bulks with depth > 1 */ - if (r->ridx == 8) { - redisSetReplyReaderError(r,sdscatprintf(sdsempty(), - "No support for nested multi bulk replies with depth > 7")); - return -1; + /* Set error for nested multi bulks with depth > 2 */ + if (r->ridx == 3) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 2"); + return REDIS_ERR; } if ((p = readLine(r,NULL)) != NULL) { @@ -362,6 +462,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 +475,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 +498,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 +528,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,101 +549,78 @@ 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); - - /* 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; -} +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; -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) { -#if 0 /* Destroy internal buffer when it is empty and is quite large. */ if (r->len == 0 && sdsavail(r->buf) > 16*1024) { 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; } -#endif - 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) @@ -554,9 +639,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) { @@ -567,15 +656,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; } @@ -594,63 +677,79 @@ 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 */ { + static const char intfmts[] = "diouxX"; char _format[16]; const char *_p = c+1; size_t _l = 0; @@ -672,35 +771,85 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { while (*_p != '\0' && isdigit(*_p)) _p++; } - /* Modifiers */ - if (*_p != '\0') { - if (*_p == 'h' || *_p == 'l') { - /* Allow a single repetition for these modifiers */ - if (_p[0] == _p[1]) _p++; - _p++; + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; } + goto fmt_invalid; } - /* Conversion specifier */ - if (*_p != '\0' && strchr("diouxXeEfFgGaA",*_p) != NULL) { - _l = (_p+1)-c; - if (_l < sizeof(_format)-2) { - memcpy(_format,c,_l); - _format[_l] = '\0'; - va_copy(_cpy,ap); - current = sdscatvprintf(current,_format,_cpy); - va_end(_cpy); - - /* Update current position (note: outer blocks - * increment c twice so compensate here) */ - c = _p-1; + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; } + goto fmt_invalid; } - /* Consume and discard vararg */ - va_arg(ap,void); + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; } } + + if (newarg == NULL) goto err; + curarg = newarg; + touched = 1; c++; } @@ -709,31 +858,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 @@ -772,12 +945,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]); @@ -788,41 +963,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); } @@ -878,32 +1061,20 @@ 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. * * After this function is called, you may use redisContextReadReply to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { - char buf[1024*16]; - int nread = read(c->fd,buf,sizeof(buf)); + char buf[2048]; + 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 */ @@ -912,12 +1083,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; } @@ -926,13 +1098,18 @@ int redisBufferRead(redisContext *c) { * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * succesfully written to the socket. When the buffer is empty after the - * write operation, "wdone" is set to 1 (if given). + * write operation, "done" is set to 1 (if given). * * Returns REDIS_ERR if an error occured trying to write and sets - * c->error to hold the appropriate error string. + * c->errstr to hold the appropriate error string. */ 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) { @@ -958,10 +1135,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; @@ -1004,31 +1179,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. @@ -1042,26 +1251,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, ...) { @@ -1074,11 +1278,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); } diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h index f4452091e..835837548 100644 --- a/deps/hiredis/hiredis.h +++ b/deps/hiredis/hiredis.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,8 +36,8 @@ #include <sys/time.h> /* for struct timeval */ #define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 9 -#define HIREDIS_PATCH 2 +#define HIREDIS_MINOR 10 +#define HIREDIS_PATCH 1 #define REDIS_ERR -1 #define REDIS_OK 0 @@ -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[4]; + 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 diff --git a/deps/hiredis/net.c b/deps/hiredis/net.c index 438a129ba..158e1dd8a 100644 --- a/deps/hiredis/net.c +++ b/deps/hiredis/net.c @@ -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,19 +49,37 @@ #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 redisSetReuseAddr(redisContext *c, int fd) { + int on = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + close(fd); + return REDIS_ERR; + } + return REDIS_OK; +} static int redisCreateSocket(redisContext *c, int type) { - int s, on = 1; + int s; 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); - close(s); + if (redisSetReuseAddr(c,s) == REDIS_ERR) { return REDIS_ERR; } } @@ -75,8 +93,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 +104,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 +114,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; } @@ -110,8 +125,6 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * struct timeval to; struct timeval *toptr = NULL; fd_set wfd; - int err; - socklen_t errlen; /* Only use timeout when not NULL. */ if (timeout != NULL) { @@ -124,101 +137,115 @@ 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; } - err = 0; - errlen = sizeof(err); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { - __redisSetError(c,REDIS_ERR_IO, - sdscatprintf(sdsempty(), "getsockopt(SO_ERROR): %s", strerror(errno))); - close(fd); + if (redisCheckSocketError(c, fd) != REDIS_OK) return REDIS_ERR; - } - - if (err) { - errno = err; - __redisSetError(c,REDIS_ERR_IO,NULL); - close(fd); - return REDIS_ERR; - } return REDIS_OK; } - __redisSetError(c,REDIS_ERR_IO,NULL); + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); close(fd); return REDIS_ERR; } +int redisCheckSocketError(redisContext *c, int fd) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(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; + } + + return REDIS_OK; +} + 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; } int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) { - int s; + int s, rv; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *p; int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_in sa; - if ((s = redisCreateSocket(c,AF_INET)) < 0) - return REDIS_ERR; - if (redisSetBlocking(c,s,0) != REDIS_OK) - return REDIS_ERR; - - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - if (inet_aton(addr, &sa.sin_addr) == 0) { - struct hostent *he; + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; - he = gethostbyname(addr); - if (he == NULL) { - __redisSetError(c,REDIS_ERR_OTHER, - sdscatprintf(sdsempty(),"Can't resolve: %s",addr)); - close(s); - return REDIS_ERR; - } - memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr)); + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; } - - if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { - if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ - } else { - if (redisContextWaitReady(c,s,timeout) != REDIS_OK) - return REDIS_ERR; + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + if (redisSetBlocking(c,s,0) != REDIS_OK) + goto error; + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + close(s); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,s,timeout) != REDIS_OK) + goto error; + } } + if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c,s) != REDIS_OK) + goto error; + + c->fd = s; + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; } - /* Reset socket to be blocking after connect(2). */ - if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) - return REDIS_ERR; - - if (redisSetTcpNoDelay(c,s) != REDIS_OK) - return REDIS_ERR; - - c->fd = s; - c->flags |= REDIS_CONNECTED; - return REDIS_OK; +error: + rv = REDIS_ERR; +end: + freeaddrinfo(servinfo); + return rv; // Need to return REDIS_OK if alright } int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) { diff --git a/deps/hiredis/net.h b/deps/hiredis/net.h index e149ceba6..eb8a0a1cf 100644 --- a/deps/hiredis/net.h +++ b/deps/hiredis/net.h @@ -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. * @@ -39,6 +39,7 @@ #define AF_LOCAL AF_UNIX #endif +int redisCheckSocketError(redisContext *c, int fd); int redisContextSetTimeout(redisContext *c, struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout); int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout); diff --git a/deps/hiredis/sds.c b/deps/hiredis/sds.c index 2bd5a81cf..0af9c6720 100644 --- a/deps/hiredis/sds.c +++ b/deps/hiredis/sds.c @@ -28,18 +28,18 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define SDS_ABORT_ON_OOM - #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; @@ -378,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); @@ -397,6 +399,7 @@ sds sdscatrepr(sds s, char *p, size_t len) { break; } p++; + if (s == NULL) return NULL; } return sdscatlen(s,"\"",1); } @@ -416,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) { @@ -427,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)) { @@ -471,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; @@ -485,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; } diff --git a/deps/hiredis/test.c b/deps/hiredis/test.c index 5724a3ea9..5945b6552 100644 --- a/deps/hiredis/test.c +++ b/deps/hiredis/test.c @@ -1,3 +1,4 @@ +#include "fmacros.h" #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -10,10 +11,28 @@ #include "hiredis.h" +enum connection_type { + CONN_TCP, + CONN_UNIX +}; + +struct config { + enum connection_type type; + + struct { + const char *host; + int port; + } tcp; + + struct { + const char *path; + } unix; +}; + /* The following lines make up our testing "framework" :) */ static int tests = 0, fails = 0; #define test(_s) { printf("#%02d ", ++tests); printf(_s); } -#define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;} +#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} static long long usec(void) { struct timeval tv; @@ -21,15 +40,60 @@ static long long usec(void) { return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; } -static int use_unix = 0; -static redisContext *blocking_context = NULL; -static void __connect(redisContext **target) { - *target = blocking_context = (use_unix ? - redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379)); - if (blocking_context->err) { - printf("Connection error: %s\n", blocking_context->errstr); +static redisContext *select_database(redisContext *c) { + redisReply *reply; + + /* Switch to DB 9 for testing, now that we know we can chat. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Make sure the DB is emtpy */ + reply = redisCommand(c,"DBSIZE"); + assert(reply != NULL); + if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { + /* Awesome, DB 9 is empty and we can continue. */ + freeReplyObject(reply); + } else { + printf("Database #9 is not empty, test can not continue\n"); + exit(1); + } + + return c; +} + +static void disconnect(redisContext *c) { + redisReply *reply; + + /* Make sure we're on DB 9. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c,"FLUSHDB"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Free the context as well. */ + redisFree(c); +} + +static redisContext *connect(struct config config) { + redisContext *c = NULL; + + if (config.type == CONN_TCP) { + c = redisConnect(config.tcp.host, config.tcp.port); + } else if (config.type == CONN_UNIX) { + c = redisConnectUnix(config.unix.path); + } else { + assert(NULL); + } + + if (c->err) { + printf("Connection error: %s\n", c->errstr); exit(1); } + + return select_database(c); } static void test_format_commands(void) { @@ -78,29 +142,43 @@ static void test_format_commands(void) { len == 4+4+(3+2)+4+(1+2)+4+(1+2)); free(cmd); - test("Format command with printf-delegation (long long): "); - len = redisFormatCommand(&cmd,"key:%08lld",1234ll); - test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 && - len == 4+5+(12+2)); - free(cmd); - - test("Format command with printf-delegation (float): "); - len = redisFormatCommand(&cmd,"v:%06.1f",12.34f); - test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 && - len == 4+4+(8+2)); - free(cmd); - - test("Format command with printf-delegation and extra interpolation: "); - len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3); - test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 && - len == 4+4+(8+2)+4+(3+2)); - free(cmd); - - test("Format command with wrong printf format and extra interpolation: "); - len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3); - test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 && - len == 4+4+(6+2)+4+(3+2)); - free(cmd); + /* Vararg width depends on the type. These tests make sure that the + * width is correctly determined using the format and subsequent varargs + * can correctly be interpolated. */ +#define INTEGER_WIDTH_TEST(fmt, type) do { \ + type value = 123; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + +#define FLOAT_WIDTH_TEST(type) do { \ + type value = 123.0; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + + INTEGER_WIDTH_TEST("d", int); + INTEGER_WIDTH_TEST("hhd", char); + INTEGER_WIDTH_TEST("hd", short); + INTEGER_WIDTH_TEST("ld", long); + INTEGER_WIDTH_TEST("lld", long long); + INTEGER_WIDTH_TEST("u", unsigned int); + INTEGER_WIDTH_TEST("hhu", unsigned char); + INTEGER_WIDTH_TEST("hu", unsigned short); + INTEGER_WIDTH_TEST("lu", unsigned long); + INTEGER_WIDTH_TEST("llu", unsigned long long); + FLOAT_WIDTH_TEST(float); + FLOAT_WIDTH_TEST(double); + + test("Format command with invalid printf format: "); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",3); + test_cond(len == -1); const char *argv[3]; argv[0] = "SET"; @@ -122,42 +200,117 @@ static void test_format_commands(void) { free(cmd); } -static void test_blocking_connection(void) { +static void test_reply_reader(void) { + redisReader *reader; + void *reply; + int ret; + + test("Error handling in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + 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 = 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(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + test("Set error on nested multi bulks with depth > 2: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*1\r\n",4); + 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(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); + + test("Works with NULL functions for 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); + redisReaderFree(reader); + + test("Works when a single newline (\\r\\n) covers two calls to feed: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_OK && reply == NULL); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + 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 = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); + redisReaderFree(reader); + + /* Regression test for issue #45 on GitHub. */ + test("Don't do empty allocation for empty multi bulk: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*0\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && + ((redisReply*)reply)->elements == 0); + freeReplyObject(reply); + redisReaderFree(reader); +} + +static void test_blocking_connection_errors(void) { redisContext *c; - redisReply *reply; - int major, minor; test("Returns error when host cannot be resolved: "); c = redisConnect((char*)"idontexist.local", 6379); test_cond(c->err == REDIS_ERR_OTHER && - strcmp(c->errstr,"Can't resolve: idontexist.local") == 0); + (strcmp(c->errstr,"Name or service not known") == 0 || + strcmp(c->errstr,"Can't resolve: idontexist.local") == 0)); redisFree(c); test("Returns error when the port is not open: "); - c = redisConnect((char*)"localhost", 56380); + c = redisConnect((char*)"localhost", 1); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr,"Connection refused") == 0); redisFree(c); - __connect(&c); + test("Returns error when the unix socket path doesn't accept connections: "); + c = redisConnectUnix((char*)"/tmp/idontexist.sock"); + test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ + redisFree(c); +} + +static void test_blocking_connection(struct config config) { + redisContext *c; + redisReply *reply; + + c = connect(config); + test("Is able to deliver commands: "); reply = redisCommand(c,"PING"); test_cond(reply->type == REDIS_REPLY_STATUS && strcasecmp(reply->str,"pong") == 0) freeReplyObject(reply); - /* Switch to DB 9 for testing, now that we know we can chat. */ - reply = redisCommand(c,"SELECT 9"); - freeReplyObject(reply); - - /* Make sure the DB is emtpy */ - reply = redisCommand(c,"DBSIZE"); - if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) { - printf("Database #9 is not empty, test can not continue\n"); - exit(1); - } - freeReplyObject(reply); - test("Is a able to send commands verbatim: "); reply = redisCommand(c,"SET foo bar"); test_cond (reply->type == REDIS_REPLY_STATUS && @@ -221,6 +374,17 @@ static void test_blocking_connection(void) { strcasecmp(reply->element[1]->str,"pong") == 0); freeReplyObject(reply); + disconnect(c); +} + +static void test_blocking_io_errors(struct config config) { + redisContext *c; + redisReply *reply; + void *_reply; + int major, minor; + + /* Connect to target given by config. */ + c = connect(config); { /* Find out Redis version to determine the path for the next test */ const char *field = "redis_version:"; @@ -240,7 +404,7 @@ static void test_blocking_connection(void) { /* > 2.0 returns OK on QUIT and read() should be issued once more * to know the descriptor is at EOF. */ test_cond(strcasecmp(reply->str,"OK") == 0 && - redisGetReply(c,(void**)&reply) == REDIS_ERR); + redisGetReply(c,&_reply) == REDIS_ERR); freeReplyObject(reply); } else { test_cond(reply == NULL); @@ -255,91 +419,20 @@ static void test_blocking_connection(void) { strcmp(c->errstr,"Server closed the connection") == 0); redisFree(c); - __connect(&c); + c = connect(config); test("Returns I/O error on socket timeout: "); struct timeval tv = { 0, 1000 }; assert(redisSetTimeout(c,tv) == REDIS_OK); - test_cond(redisGetReply(c,(void**)&reply) == REDIS_ERR && + test_cond(redisGetReply(c,&_reply) == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN); redisFree(c); - - /* Context should be connected */ - __connect(&c); } -static void test_reply_reader(void) { - void *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); - test_cond(ret == REDIS_ERR && - strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0); - redisReplyReaderFree(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); - test_cond(ret == REDIS_ERR && - strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0); - redisReplyReaderFree(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); - test_cond(ret == REDIS_ERR && - strncasecmp(err,"No support for",14) == 0); - redisReplyReaderFree(reader); - - test("Works with NULL functions for reply: "); - reader = redisReplyReaderCreate(); - redisReplyReaderSetReplyObjectFunctions(reader,NULL); - redisReplyReaderFeed(reader,(char*)"+OK\r\n",5); - ret = redisReplyReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReplyReaderFree(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); - assert(ret == REDIS_OK && reply == NULL); - redisReplyReaderFeed(reader,(char*)"\n",1); - ret = redisReplyReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); - redisReplyReaderFree(reader); - - test("Properly reset state after protocol error: "); - reader = redisReplyReaderCreate(); - redisReplyReaderSetReplyObjectFunctions(reader,NULL); - redisReplyReaderFeed(reader,(char*)"x",1); - ret = redisReplyReaderGetReply(reader,&reply); - assert(ret == REDIS_ERR); - ret = redisReplyReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && reply == NULL) -} - -static void test_throughput(void) { +static void test_throughput(struct config config) { + redisContext *c = connect(config); + redisReply **replies; int i, num; long long t1, t2; - redisContext *c = blocking_context; - redisReply **replies; test("Throughput:\n"); for (i = 0; i < 500; i++) @@ -396,18 +489,8 @@ static void test_throughput(void) { for (i = 0; i < num; i++) freeReplyObject(replies[i]); free(replies); printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); -} -static void cleanup(void) { - redisContext *c = blocking_context; - redisReply *reply; - - /* Make sure we're on DB 9 */ - reply = redisCommand(c,"SELECT 9"); - assert(reply != NULL); freeReplyObject(reply); - reply = redisCommand(c,"FLUSHDB"); - assert(reply != NULL); freeReplyObject(reply); - redisFree(c); + disconnect(c); } // static long __test_callback_flags = 0; @@ -429,7 +512,7 @@ static void cleanup(void) { // static redisContext *__connect_nonblock() { // /* Reset callback flags */ // __test_callback_flags = 0; -// return redisConnectNonBlock("127.0.0.1", 6379, NULL); +// return redisConnectNonBlock("127.0.0.1", port, NULL); // } // // static void test_nonblocking_connection() { @@ -510,23 +593,62 @@ static void cleanup(void) { // } int main(int argc, char **argv) { - if (argc > 1) { - if (strcmp(argv[1],"-s") == 0) - use_unix = 1; + struct config cfg = { + .tcp = { + .host = "127.0.0.1", + .port = 6379 + }, + .unix = { + .path = "/tmp/redis.sock" + } + }; + int throughput = 1; + + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options. */ + argv++; argc--; + while (argc) { + if (argc >= 2 && !strcmp(argv[0],"-h")) { + argv++; argc--; + cfg.tcp.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"-p")) { + argv++; argc--; + cfg.tcp.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"-s")) { + argv++; argc--; + cfg.unix.path = argv[0]; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { + throughput = 0; + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[0]); + exit(1); + } + argv++; argc--; } - signal(SIGPIPE, SIG_IGN); test_format_commands(); - test_blocking_connection(); test_reply_reader(); - // test_nonblocking_connection(); - test_throughput(); - cleanup(); + test_blocking_connection_errors(); - if (fails == 0) { - printf("ALL TESTS PASSED\n"); - } else { + printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + test_blocking_connection(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + if (fails) { printf("*** %d TESTS FAILED ***\n", fails); + return 1; } + + printf("ALL TESTS PASSED\n"); return 0; } diff --git a/deps/hiredis/util.h b/deps/hiredis/util.h deleted file mode 100644 index f19299005..000000000 --- a/deps/hiredis/util.h +++ /dev/null @@ -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 |