Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add hiredis dependency for redis-cli, redis-benchmark, etc

  • Loading branch information...
commit 24f753a8b996a364273019ea791fe89f87cff678 1 parent 8d3e063
@pietern pietern authored
View
6 deps/hiredis/.gitignore
@@ -0,0 +1,6 @@
+/hiredis-test
+/hiredis-example*
+/*.o
+/*.so
+/*.dylib
+/*.a
View
10 deps/hiredis/COPYING
@@ -0,0 +1,10 @@
+Copyright (c) 2006-2009, Salvatore Sanfilippo
+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.
View
102 deps/hiredis/Makefile
@@ -0,0 +1,102 @@
+# Hiredis Makefile
+# Copyright (C) 2010 Salvatore Sanfilippo <antirez 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
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+OPTIMIZATION?=-O2
+ifeq ($(uname_S),SunOS)
+ CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
+ CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
+ DYLIBNAME?=libhiredis.so
+ DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -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 -Wwrite-strings $(ARCH) $(PROF)
+ CCLINK?= -lm -pthread
+ 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 -Wwrite-strings $(ARCH) $(PROF)
+ CCLINK?= -lm -pthread
+ DYLIBNAME?=libhiredis.so
+ DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
+ STLIBNAME?=libhiredis.a
+ STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
+endif
+CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
+DEBUG?= -g -ggdb
+
+PREFIX?= /usr/local
+INSTALL_INC= $(PREFIX)/include/hiredis
+INSTALL_LIB= $(PREFIX)/lib
+INSTALL= cp -a
+
+all: ${DYLIBNAME} ${BINS}
+
+# Deps (use make dep to generate this)
+net.o: net.c fmacros.h net.h
+async.o: async.c async.h hiredis.h sds.h util.h
+example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
+example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
+example.o: example.c hiredis.h
+hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
+sds.o: sds.c sds.h
+test.o: test.c hiredis.h
+
+${DYLIBNAME}: ${OBJ}
+ ${DYLIB_MAKE_CMD}
+
+${STLIBNAME}: ${OBJ}
+ ${STLIB_MAKE_CMD}
+
+dynamic: ${DYLIBNAME}
+static: ${STLIBNAME}
+
+# Binaries:
+hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
+ $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
+
+hiredis-example-libev: example-libev.o ${DYLIBNAME}
+ $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
+
+hiredis-%: %.o ${DYLIBNAME}
+ $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
+
+test: hiredis-test
+ ./hiredis-test
+
+.c.o:
+ $(CC) -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $<
+
+clean:
+ 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)
+
+32bit:
+ @echo ""
+ @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
+ @echo ""
+ make ARCH="-m32"
+
+gprof:
+ make PROF="-pg"
+
+gcov:
+ make PROF="-fprofile-arcs -ftest-coverage"
+
+noopt:
+ make OPTIMIZATION=""
View
284 deps/hiredis/README.md
@@ -0,0 +1,284 @@
+# HIREDIS
+
+Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
+
+It is minimalistic because it just adds minimal support for the protocol, but
+at the same time it uses an high level printf-alike API in order to make it
+much higher level than otherwise suggested by its minimal code base and the
+lack of explicit bindings for every Redis command.
+
+Apart from supporting sending commands and receiving replies, it comes with
+a reply parser that is decoupled from the I/O layer. It
+is a stream parser designed for easy reusability, which can for instance be used
+in higher level language bindings for efficient reply parsing.
+
+Hiredis only supports the binary-safe Redis protocol, so you can use it with any
+Redis version >= 1.2.0.
+
+The library comes with multiple APIs. There is the
+*synchronous API*, the *asynchronous API* and the *reply parsing API*.
+
+## UPGRADING
+
+Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
+code using hiredis should not be a big pain. The key thing to keep in mind when
+upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to
+the stateless 0.0.1 that only has a file descriptor to work with.
+
+## Synchronous API
+
+To consume the synchronous API, there are only a few function calls that need to be introduced:
+
+ redisContext *redisConnect(const char *ip, int port);
+ void *redisCommand(redisContext *c, const char *format, ...);
+ void freeReplyObject(void *reply);
+
+### Connecting
+
+The function `redisConnect` is used to create a so-called `redisContext`. The context is where
+Hiredis holds state for a connection. The `redisContext` struct has an `error` field that is
+non-NULL when the connection is in an error state. It contains a string with a textual
+representation of the error. After trying to connect to Redis using `redisConnect` you should
+check the `error` field to see if establishing the connection was successful:
+
+ redisContext *c = redisConnect("127.0.0.1", 6379);
+ if (c->error != NULL) {
+ printf("Error: %s\n", c->error);
+ // handle error
+ }
+
+### Sending commands
+
+There are several ways to issue commands to Redis. The first that will be introduced is
+`redisCommand`. This function takes a format similar to printf. In the simplest form,
+it is used like this:
+
+ reply = redisCommand(context, "SET foo bar");
+
+The specifier `%s` interpolates a string in the command, and uses `strlen` to
+determine the length of the string:
+
+ reply = redisCommand(context, "SET foo %s", value);
+
+When you need to pass binary safe strings in a command, the `%b` specifier can be
+used. Together with a pointer to the string, it requires a `size_t` length argument
+of the string:
+
+ reply = redisCommand(context, "SET foo %b", value, valuelen);
+
+Internally, Hiredis splits the command in different arguments and will
+convert it to the protocol used to communicate with Redis.
+One or more spaces separates arguments, so you can use the specifiers
+anywhere in an argument:
+
+ reply = redisCommand("SET key:%s %s", myid, value);
+
+### Using replies
+
+The return value of `redisCommand` holds a reply when the command was
+successfully executed. When the return value is `NULL`, the `error` field
+in the context can be used to find out what was the cause of failure.
+Once an error is returned the context cannot be reused and you should set up
+a new connection.
+
+The standard replies that `redisCommand` are of the type `redisReply`. The
+`type` field in the `redisReply` should be used to test what kind of reply
+was received:
+
+* **`REDIS_REPLY_STATUS`**:
+ * The command replied with a status reply. The status string can be accessed using `reply->str`.
+ The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ERROR`**:
+ * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
+
+* **`REDIS_REPLY_INTEGER`**:
+ * The command replied with an integer. The integer value can be accessed using the
+ `reply->integer` field of type `long long`.
+
+* **`REDIS_REPLY_NIL`**:
+ * The command replied with a **nil** object. There is no data to access.
+
+* **`REDIS_REPLY_STRING`**:
+ * A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
+ The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ARRAY`**:
+ * A multi bulk reply. The number of elements in the multi bulk reply is stored in
+ `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
+ and can be accessed via `reply->elements[..index..]`.
+ Redis may reply with nested arrays but this is fully supported.
+
+Replies should be freed using the `freeReplyObject()` function.
+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).
+
+### Cleaning up
+
+To disconnect and free the context the following function can be used:
+
+ void redisFree(redisContext *c);
+
+This function immediately closes the socket and then free's the allocations done in
+creating the context.
+
+### Sending commands (cont'd)
+
+Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
+It has the following prototype:
+
+ void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
+arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
+use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
+need to be binary safe, the entire array of lengths `argvlen` should be provided.
+
+The return value has the same semantic as `redisCommand`.
+
+### Pipelining
+
+To explain how Hiredis supports pipelining in a blocking connection, there needs to be
+understanding of the internal execution flow.
+
+When any of the functions in the `redisCommand` family is called, Hiredis first formats the
+command according to the Redis protocol. The formatted command is then put in the output buffer
+of the context. This output buffer is dynamic, so it can hold any number of commands.
+After the command is put in the output buffer, `redisGetReply` is called. This function has the
+following two execution paths:
+
+1. The input buffer is non-empty:
+ * Try to parse a single reply from the input buffer and return it
+ * If no reply could be parsed, continue at *2*
+2. The input buffer is empty:
+ * Write the **entire** output buffer to the socket
+ * Read from the socket until a single reply could be parsed
+
+The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
+is expected on the socket. To pipeline commands, the only things that needs to be done is
+filling up the output buffer. For this cause, two commands can be used that are identical
+to the `redisCommand` family, apart from not returning a reply:
+
+ void redisAppendCommand(redisContext *c, const char *format, ...);
+ void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+After calling either function one or more times, `redisGetReply` can be used to receive the
+subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
+the latter means an error occurred while reading a reply. Just as with the other commands,
+the `error` field in the context can be used to find out what the cause of this error is.
+
+The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
+a single call to `write(2)`):
+
+ redisReply *reply;
+ redisAppendCommand(context,"SET foo bar");
+ redisAppendCommand(context,"GET foo");
+ redisGetReply(context,&reply); // reply for SET
+ freeReplyObject(reply);
+ redisGetReply(context,&reply); // reply for GET
+ freeReplyObject(reply);
+
+This API can also be used to implement a blocking subscriber:
+
+ reply = redisCommand(context,"SUBSCRIBE foo");
+ freeReplyObject(reply);
+ while(redisGetReply(context,&reply) == REDIS_OK) {
+ // consume message
+ freeReplyObject(reply);
+ }
+
+## Asynchronous API
+
+Hiredis comes with an asynchronous API that works easily with any event library.
+Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
+and [libevent](http://monkey.org/~provos/libevent/).
+
+### Connecting
+
+The function `redisAsyncConnect` can be used to establish a non-blocking connection to
+Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `error` field
+should be checked after creation to see if there were errors creating the connection.
+Because the connection that will be created is non-blocking, the kernel is not able to
+instantly return if the specified host and port is able to accept a connection.
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->error != NULL) {
+ printf("Error: %s\n", c->error);
+ // handle error
+ }
+
+The asynchronous context can hold a disconnect callback function that is called when the
+connection is disconnected (either because of an error or per user request). This function should
+have the following prototype:
+
+ void(const redisAsyncContext *c, int status);
+
+On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
+user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `error`
+field in the context can be accessed to find out the cause of the error.
+
+The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
+the disconnect callback is a good point to do so.
+
+Setting the disconnect callback can only be done once per context. For subsequent calls it will
+return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
+
+ int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+
+### Sending commands and their callbacks
+
+In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
+Therefore, unlike the synchronous API, there is only a single way to send commands.
+Because commands are sent to Redis asynchronously, issuing a command requires a callback function
+that is called when the reply is received. Reply callbacks should have the following prototype:
+
+ void(redisAsyncContext *c, void *reply, void *privdata);
+
+The `privdata` argument can be used to curry arbitrary data to the callback from the point where
+the command is initially queued for execution.
+
+The functions that can be used to issue commands in an asynchronous context are:
+
+ int redisAsyncCommand(
+ redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+ const char *format, ...);
+ int redisAsyncCommandArgv(
+ redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+ int argc, const char **argv, const size_t *argvlen);
+
+Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
+was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
+is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
+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.
+
+All pending callbacks are called with a `NULL` reply when the context encountered an error.
+
+### Disconnecting
+
+An asynchronous connection can be terminated using:
+
+ void redisAsyncDisconnect(redisAsyncContext *ac);
+
+When this function is called, the connection is **not** immediately terminated. Instead, new
+commands are no longer accepted and the connection is only terminated when all pending commands
+have been written to the socket, their respective replies have been read and their respective
+callbacks have been executed. After this, the disconnection callback is executed with the
+`REDIS_OK` status and the context object is free'd.
+
+### Hooking it up to event library *X*
+
+There are a few hooks that need to be set on the context object after it is created.
+See the `adapters/` directory for bindings to *libev* and *libevent*.
+
+## Reply parsing API
+
+To be done.
+
+## AUTHORS
+
+Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
+Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
View
2  deps/hiredis/TODO
@@ -0,0 +1,2 @@
+- add redisCommandVector()
+- add support for pipelining
View
92 deps/hiredis/adapters/libev.h
@@ -0,0 +1,92 @@
+#include <sys/types.h>
+#include <ev.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisLibevEvents {
+ redisAsyncContext *context;
+ struct ev_loop *loop;
+ int reading, writing;
+ ev_io rev, wev;
+} redisLibevEvents;
+
+void redisLibevReadEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
+ ((void)loop); ((void)revents);
+ redisLibevEvents *e = watcher->data;
+ redisAsyncHandleRead(e->context);
+}
+
+void redisLibevWriteEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
+ ((void)loop); ((void)revents);
+ redisLibevEvents *e = watcher->data;
+ redisAsyncHandleWrite(e->context);
+}
+
+void redisLibevAddRead(void *privdata) {
+ redisLibevEvents *e = privdata;
+ if (!e->reading) {
+ e->reading = 1;
+ ev_io_start(e->loop,&e->rev);
+ }
+}
+
+void redisLibevDelRead(void *privdata) {
+ redisLibevEvents *e = privdata;
+ if (e->reading) {
+ e->reading = 0;
+ ev_io_stop(e->loop,&e->rev);
+ }
+}
+
+void redisLibevAddWrite(void *privdata) {
+ redisLibevEvents *e = privdata;
+ if (!e->writing) {
+ e->writing = 1;
+ ev_io_start(e->loop,&e->wev);
+ }
+}
+
+void redisLibevDelWrite(void *privdata) {
+ redisLibevEvents *e = privdata;
+ if (e->writing) {
+ e->writing = 0;
+ ev_io_stop(e->loop,&e->wev);
+ }
+}
+
+void redisLibevCleanup(void *privdata) {
+ redisLibevEvents *e = privdata;
+ redisLibevDelRead(privdata);
+ redisLibevDelWrite(privdata);
+ free(e);
+}
+
+int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
+ redisContext *c = &(ac->c);
+ redisLibevEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = malloc(sizeof(*e));
+ e->context = ac;
+ e->loop = loop;
+ e->reading = e->writing = 0;
+ e->rev.data = e;
+ 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->data = e;
+
+ /* Initialize read/write events */
+ ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
+ ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
+ return REDIS_OK;
+}
View
76 deps/hiredis/adapters/libevent.h
@@ -0,0 +1,76 @@
+#include <sys/types.h>
+#include <event.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisLibeventEvents {
+ redisAsyncContext *context;
+ struct event rev, wev;
+} redisLibeventEvents;
+
+void redisLibeventReadEvent(int fd, short event, void *arg) {
+ ((void)fd); ((void)event);
+ redisLibeventEvents *e = arg;
+ redisAsyncHandleRead(e->context);
+}
+
+void redisLibeventWriteEvent(int fd, short event, void *arg) {
+ ((void)fd); ((void)event);
+ redisLibeventEvents *e = arg;
+ redisAsyncHandleWrite(e->context);
+}
+
+void redisLibeventAddRead(void *privdata) {
+ redisLibeventEvents *e = privdata;
+ event_add(&e->rev,NULL);
+}
+
+void redisLibeventDelRead(void *privdata) {
+ redisLibeventEvents *e = privdata;
+ event_del(&e->rev);
+}
+
+void redisLibeventAddWrite(void *privdata) {
+ redisLibeventEvents *e = privdata;
+ event_add(&e->wev,NULL);
+}
+
+void redisLibeventDelWrite(void *privdata) {
+ redisLibeventEvents *e = privdata;
+ event_del(&e->wev);
+}
+
+void redisLibeventCleanup(void *privdata) {
+ redisLibeventEvents *e = privdata;
+ event_del(&e->rev);
+ event_del(&e->wev);
+ free(e);
+}
+
+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->data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = malloc(sizeof(*e));
+ 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->data = e;
+
+ /* Initialize and install read/write events */
+ event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
+ event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
+ event_base_set(base,&e->rev);
+ event_base_set(base,&e->wev);
+ return REDIS_OK;
+}
View
284 deps/hiredis/async.c
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+#include <assert.h>
+#include "async.h"
+#include "sds.h"
+#include "util.h"
+
+/* Forward declaration of function in hiredis.c */
+void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
+
+static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
+ redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
+ /* Set all bytes in the async part of the context to 0 */
+ memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
+ return ac;
+}
+
+/* We want the error field to be accessible directly instead of requiring
+ * an indirection to the redisContext struct. */
+static void __redisAsyncCopyError(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ ac->err = c->err;
+ ac->errstr = c->errstr;
+}
+
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+ redisContext *c = redisConnectNonBlock(ip,port);
+ redisAsyncContext *ac = redisAsyncInitialize(c);
+ __redisAsyncCopyError(ac);
+ return ac;
+}
+
+redisAsyncContext *redisAsyncConnectUnix(const char *path) {
+ redisContext *c = redisConnectUnixNonBlock(path);
+ redisAsyncContext *ac = redisAsyncInitialize(c);
+ __redisAsyncCopyError(ac);
+ return ac;
+}
+
+int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn) {
+ redisContext *c = &(ac->c);
+ return redisSetReplyObjectFunctions(c,fn);
+}
+
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
+ if (ac->onDisconnect == NULL) {
+ ac->onDisconnect = fn;
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+/* Helper functions to push/shift callbacks */
+static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
+ redisCallback *cb;
+
+ /* Copy callback from stack to heap */
+ cb = calloc(1,sizeof(*cb));
+ if (!cb) redisOOM();
+ if (source != NULL) {
+ cb->fn = source->fn;
+ cb->privdata = source->privdata;
+ }
+
+ /* Store callback in list */
+ if (list->head == NULL)
+ list->head = cb;
+ if (list->tail != NULL)
+ list->tail->next = cb;
+ list->tail = cb;
+ return REDIS_OK;
+}
+
+static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
+ redisCallback *cb = list->head;
+ if (cb != NULL) {
+ list->head = cb->next;
+ if (cb == list->tail)
+ list->tail = NULL;
+
+ /* Copy callback from heap to stack */
+ if (target != NULL)
+ memcpy(target,cb,sizeof(*cb));
+ free(cb);
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+/* Tries to do a clean disconnect from Redis, meaning it stops new commands
+ * from being issued, but tries to flush the output buffer and execute
+ * callbacks for all remaining replies.
+ *
+ * This functions is generally called from within a callback, so the
+ * processCallbacks function will pick up the flag when there are no
+ * more replies. */
+void redisAsyncDisconnect(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ c->flags |= REDIS_DISCONNECTING;
+}
+
+/* Helper function to make the disconnect happen and clean up. */
+static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+ int status;
+
+ /* Make sure error is accessible if there is any */
+ __redisAsyncCopyError(ac);
+ status = (ac->err == 0) ? REDIS_OK : REDIS_ERR;
+
+ if (status == REDIS_OK) {
+ /* When the connection is cleanly disconnected, there should not
+ * be pending callbacks. */
+ assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+ } else {
+ /* Callbacks should not be able to issue new commands. */
+ c->flags |= REDIS_DISCONNECTING;
+
+ /* Execute pending callbacks with NULL reply. */
+ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) {
+ if (cb.fn != NULL)
+ cb.fn(ac,NULL,cb.privdata);
+ }
+ }
+
+ /* Signal event lib to clean up */
+ if (ac->evCleanup) ac->evCleanup(ac->data);
+
+ /* Execute callback with proper status */
+ if (ac->onDisconnect) ac->onDisconnect(ac,status);
+
+ /* Cleanup self */
+ redisFree(c);
+}
+
+void redisProcessCallbacks(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+ void *reply = NULL;
+ int status;
+
+ while((status = redisGetReply(c,&reply)) == REDIS_OK) {
+ if (reply == NULL) {
+ /* When the connection is being disconnected and there are
+ * no more replies, this is the cue to really disconnect. */
+ if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ /* When the connection is not being disconnected, simply stop
+ * trying to get replies and wait for the next loop tick. */
+ break;
+ }
+
+ /* Shift callback and execute it */
+ assert(__redisShiftCallback(&ac->replies,&cb) == REDIS_OK);
+ if (cb.fn != NULL) {
+ cb.fn(ac,reply,cb.privdata);
+ } else {
+ c->fn->freeObject(reply);
+ }
+ }
+
+ /* Disconnect when there was an error reading the reply */
+ if (status != REDIS_OK)
+ __redisAsyncDisconnect(ac);
+}
+
+/* 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 (redisBufferRead(c) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Always re-schedule reads */
+ if (ac->evAddRead) ac->evAddRead(ac->data);
+ redisProcessCallbacks(ac);
+ }
+}
+
+void redisAsyncHandleWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ int done = 0;
+
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Continue writing when not done, stop writing otherwise */
+ if (!done) {
+ if (ac->evAddWrite) ac->evAddWrite(ac->data);
+ } else {
+ if (ac->evDelWrite) ac->evDelWrite(ac->data);
+ }
+
+ /* Always schedule reads when something was written */
+ if (ac->evAddRead) ac->evAddRead(ac->data);
+ }
+}
+
+/* Helper function for the redisAsyncCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer and register the provided
+ * callback function with the context.
+ */
+static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+
+ /* Don't accept new commands when the connection is lazily closed. */
+ if (c->flags & REDIS_DISCONNECTING) return REDIS_ERR;
+ __redisAppendCommand(c,cmd,len);
+
+ /* Store callback */
+ cb.fn = fn;
+ cb.privdata = privdata;
+ __redisPushCallback(&ac->replies,&cb);
+
+ /* Always schedule a write when the write buffer is non-empty */
+ if (ac->evAddWrite) ac->evAddWrite(ac->data);
+
+ return REDIS_OK;
+}
+
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
+ char *cmd;
+ int len;
+ int status;
+ len = redisvFormatCommand(&cmd,format,ap);
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+ free(cmd);
+ return status;
+}
+
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
+ va_list ap;
+ int status;
+ va_start(ap,format);
+ status = redisvAsyncCommand(ac,fn,privdata,format,ap);
+ va_end(ap);
+ return status;
+}
+
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
+ char *cmd;
+ int len;
+ int status;
+ len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+ status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+ free(cmd);
+ return status;
+}
View
94 deps/hiredis/async.h
@@ -0,0 +1,94 @@
+/*
+ * 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 __HIREDIS_ASYNC_H
+#define __HIREDIS_ASYNC_H
+#include "hiredis.h"
+
+struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
+
+/* Reply callback prototype and container */
+typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
+typedef struct redisCallback {
+ struct redisCallback *next; /* simple singly linked list */
+ redisCallbackFn *fn;
+ void *privdata;
+} redisCallback;
+
+/* List of callbacks for either regular replies or pub/sub */
+typedef struct redisCallbackList {
+ redisCallback *head, *tail;
+} redisCallbackList;
+
+/* Disconnect callback prototype */
+typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+
+/* Context for an async connection to Redis */
+typedef struct redisAsyncContext {
+ /* Hold the regular context, so it can be realloc'ed. */
+ redisContext c;
+
+ /* Setup error flags so they can be used directly. */
+ int err;
+ char *errstr;
+
+ /* Called when the library expects to start reading/writing.
+ * The supplied functions should be idempotent. */
+ void (*evAddRead)(void *privdata);
+ void (*evDelRead)(void *privdata);
+ void (*evAddWrite)(void *privdata);
+ void (*evDelWrite)(void *privdata);
+ void (*evCleanup)(void *privdata);
+ void *data;
+
+ /* Called when either the connection is terminated due to an error or per
+ * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
+ redisDisconnectCallback *onDisconnect;
+
+ /* Reply callbacks */
+ redisCallbackList replies;
+} redisAsyncContext;
+
+/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnect(const char *ip, int port);
+int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+void redisAsyncDisconnect(redisAsyncContext *ac);
+
+/* Handle read/write events */
+void redisAsyncHandleRead(redisAsyncContext *ac);
+void redisAsyncHandleWrite(redisAsyncContext *ac);
+
+/* Command functions for an async context. Write the command to the
+ * output buffer and register the provided callback. */
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+
+#endif
View
41 deps/hiredis/example-libev.c
@@ -0,0 +1,41 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/libev.h"
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ }
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+ struct ev_loop *loop = ev_default_loop(0);
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ redisLibevAttach(c,loop);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ ev_loop(loop, 0);
+ return 0;
+}
View
41 deps/hiredis/example-libevent.c
@@ -0,0 +1,41 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/libevent.h"
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ }
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+ struct event_base *base = event_base_new();
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ redisLibeventAttach(c,base);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ event_base_dispatch(base);
+ return 0;
+}
View
67 deps/hiredis/example.c
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "hiredis.h"
+
+int main(void) {
+ unsigned int j;
+ redisContext *c;
+ redisReply *reply;
+
+ c = redisConnect((char*)"127.0.0.1", 6379);
+ if (c->err) {
+ printf("Connection error: %s\n", c->errstr);
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(c,"PING");
+ printf("PONG: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key */
+ reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+ printf("SET: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key using binary safe API */
+ reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5);
+ printf("SET (binary API): %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Try a GET and two INCR */
+ reply = redisCommand(c,"GET foo");
+ printf("GET foo: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+ /* again ... */
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+
+ /* Create a list of numbers, from 0 to 9 */
+ reply = redisCommand(c,"DEL mylist");
+ freeReplyObject(reply);
+ for (j = 0; j < 10; j++) {
+ char buf[64];
+
+ snprintf(buf,64,"%d",j);
+ reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+ freeReplyObject(reply);
+ }
+
+ /* Let's check what we have inside the list */
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ if (reply->type == REDIS_REPLY_ARRAY) {
+ for (j = 0; j < reply->elements; j++) {
+ printf("%u) %s\n", j, reply->element[j]->str);
+ }
+ }
+ freeReplyObject(reply);
+
+ return 0;
+}
View
15 deps/hiredis/fmacros.h
@@ -0,0 +1,15 @@
+#ifndef _REDIS_FMACRO_H
+#define _REDIS_FMACRO_H
+
+#define _BSD_SOURCE
+
+#ifdef __linux__
+#define _XOPEN_SOURCE 700
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#define _LARGEFILE_SOURCE
+#define _FILE_OFFSET_BITS 64
+
+#endif
View
887 deps/hiredis/hiredis.c
@@ -0,0 +1,887 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+
+#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 */
+ unsigned int pos; /* buffer cursor */
+
+ redisReadTask rstack[3]; /* stack of read tasks */
+ int ridx; /* index of stack */
+} 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. */
+static redisReplyObjectFunctions defaultFunctions = {
+ createStringObject,
+ createArrayObject,
+ createIntegerObject,
+ createNilObject,
+ freeReplyObject
+};
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type) {
+ redisReply *r = calloc(sizeof(*r),1);
+
+ if (!r) redisOOM();
+ r->type = type;
+ return r;
+}
+
+/* Free a reply object */
+void freeReplyObject(void *reply) {
+ redisReply *r = reply;
+ size_t j;
+
+ switch(r->type) {
+ 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);
+ break;
+ default:
+ 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 ||
+ task->type == REDIS_REPLY_STATUS ||
+ task->type == REDIS_REPLY_STRING);
+
+ /* Copy string value */
+ memcpy(value,str,len);
+ value[len] = '\0';
+ r->str = value;
+ r->len = len;
+
+ if (task->parent) {
+ redisReply *parent = task->parent;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createArrayObject(const redisReadTask *task, int elements) {
+ redisReply *r = createReplyObject(REDIS_REPLY_ARRAY);
+ r->elements = elements;
+ if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
+ redisOOM();
+ if (task->parent) {
+ redisReply *parent = task->parent;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createIntegerObject(const redisReadTask *task, long long value) {
+ redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
+ r->integer = value;
+ if (task->parent) {
+ redisReply *parent = task->parent;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createNilObject(const redisReadTask *task) {
+ redisReply *r = createReplyObject(REDIS_REPLY_NIL);
+ if (task->parent) {
+ redisReply *parent = task->parent;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static char *readBytes(redisReader *r, unsigned int bytes) {
+ char *p;
+ if (sdslen(r->buf)-r->pos >= bytes) {
+ p = r->buf+r->pos;
+ r->pos += bytes;
+ return p;
+ }
+ return NULL;
+}
+
+static char *readLine(redisReader *r, int *_len) {
+ char *p, *s = strstr(r->buf+r->pos,"\r\n");
+ int len;
+ if (s != NULL) {
+ p = r->buf+r->pos;
+ len = s-(r->buf+r->pos);
+ r->pos += len+2; /* skip \r\n */
+ if (_len) *_len = len;
+ return p;
+ }
+ return NULL;
+}
+
+static void moveToNextTask(redisReader *r) {
+ redisReadTask *cur, *prv;
+ assert(r->ridx >= 0);
+
+ /* Return a.s.a.p. when the stack is now empty. */
+ if (r->ridx == 0) {
+ r->ridx--;
+ return;
+ }
+
+ cur = &(r->rstack[r->ridx]);
+ prv = &(r->rstack[r->ridx-1]);
+ assert(prv->type == REDIS_REPLY_ARRAY);
+ if (cur->idx == prv->elements-1) {
+ r->ridx--;
+ moveToNextTask(r);
+ } else {
+ /* Reset the type because the next item can be anything */
+ assert(cur->idx < prv->elements);
+ cur->type = -1;
+ cur->elements = -1;
+ cur->idx++;
+ }
+}
+
+static int processLineItem(redisReader *r) {
+ redisReadTask *cur = &(r->rstack[r->ridx]);
+ void *obj;
+ char *p;
+ int len;
+
+ if ((p = readLine(r,&len)) != NULL) {
+ if (cur->type == REDIS_REPLY_INTEGER) {
+ obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
+ } else {
+ obj = r->fn->createString(cur,p,len);
+ }
+
+ /* If there is no root yet, register this object as root. */
+ if (r->reply == NULL)
+ r->reply = obj;
+ moveToNextTask(r);
+ return 0;
+ }
+ return -1;
+}
+
+static int processBulkItem(redisReader *r) {
+ redisReadTask *cur = &(r->rstack[r->ridx]);
+ void *obj = NULL;
+ char *p, *s;
+ long len;
+ unsigned long bytelen;
+
+ p = r->buf+r->pos;
+ s = strstr(p,"\r\n");
+ if (s != NULL) {
+ p = r->buf+r->pos;
+ bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
+ len = strtol(p,NULL,10);
+
+ if (len < 0) {
+ /* The nil object can always be created. */
+ obj = r->fn->createNil(cur);
+ } else {
+ /* Only continue when the buffer contains the entire bulk item. */
+ bytelen += len+2; /* include \r\n */
+ if (r->pos+bytelen <= sdslen(r->buf)) {
+ obj = r->fn->createString(cur,s+2,len);
+ }
+ }
+
+ /* Proceed when obj was created. */
+ if (obj != NULL) {
+ r->pos += bytelen;
+ if (r->reply == NULL)
+ r->reply = obj;
+ moveToNextTask(r);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int processMultiBulkItem(redisReader *r) {
+ redisReadTask *cur = &(r->rstack[r->ridx]);
+ void *obj;
+ char *p;
+ long elements;
+
+ if ((p = readLine(r,NULL)) != NULL) {
+ elements = strtol(p,NULL,10);
+ if (elements == -1) {
+ obj = r->fn->createNil(cur);
+ moveToNextTask(r);
+ } else {
+ obj = r->fn->createArray(cur,elements);
+
+ /* Modify task stack when there are more than 0 elements. */
+ if (elements > 0) {
+ cur->elements = elements;
+ r->ridx++;
+ r->rstack[r->ridx].type = -1;
+ r->rstack[r->ridx].elements = -1;
+ r->rstack[r->ridx].parent = obj;
+ r->rstack[r->ridx].idx = 0;
+ } else {
+ moveToNextTask(r);
+ }
+ }
+
+ /* Object was created, so we can always continue. */
+ if (r->reply == NULL)
+ r->reply = obj;
+ return 0;
+ }
+ return -1;
+}
+
+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) {
+ if ((p = readBytes(r,1)) != NULL) {
+ switch (p[0]) {
+ case '-':
+ cur->type = REDIS_REPLY_ERROR;
+ break;
+ case '+':
+ cur->type = REDIS_REPLY_STATUS;
+ break;
+ case ':':
+ cur->type = REDIS_REPLY_INTEGER;
+ break;
+ case '$':
+ cur->type = REDIS_REPLY_STRING;
+ break;
+ case '*':
+ 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;
+ }
+ } else {
+ /* could not consume 1 byte */
+ return -1;
+ }
+ }
+
+ /* process typed item */
+ switch(cur->type) {
+ case REDIS_REPLY_ERROR:
+ case REDIS_REPLY_STATUS:
+ case REDIS_REPLY_INTEGER:
+ return processLineItem(r);
+ case REDIS_REPLY_STRING:
+ return processBulkItem(r);
+ case REDIS_REPLY_ARRAY:
+ return processMultiBulkItem(r);
+ default:
+ redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
+ "unknown item type '%d'", cur->type));
+ return -1;
+ }
+}
+
+void *redisReplyReaderCreate(redisReplyObjectFunctions *fn) {
+ redisReader *r = calloc(sizeof(redisReader),1);
+ r->error = NULL;
+ r->fn = fn == NULL ? &defaultFunctions : fn;
+ r->buf = sdsempty();
+ r->ridx = -1;
+ return r;
+}
+
+/* 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;
+}
+
+void redisReplyReaderFree(void *reader) {
+ redisReader *r = reader;
+ if (r->error != NULL)
+ sdsfree(r->error);
+ if (r->reply != NULL)
+ 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 = 0;
+ }
+ r->ridx = -1;
+ r->error = err;
+}
+
+char *redisReplyReaderGetError(void *reader) {
+ redisReader *r = reader;
+ return r->error;
+}
+
+void redisReplyReaderFeed(void *reader, char *buf, int len) {
+ redisReader *r = reader;
+
+ /* Copy the provided buffer. */
+ if (buf != NULL && len >= 1)
+ r->buf = sdscatlen(r->buf,buf,len);
+}
+
+int redisReplyReaderGetReply(void *reader, void **reply) {
+ redisReader *r = reader;
+ if (reply != NULL) *reply = NULL;
+
+ /* When the buffer is empty, there will never be a reply. */
+ if (sdslen(r->buf) == 0)
+ return REDIS_OK;
+
+ /* Set first item to process when the stack is empty. */
+ if (r->ridx == -1) {
+ r->rstack[0].type = -1;
+ r->rstack[0].elements = -1;
+ r->rstack[0].parent = NULL;
+ r->rstack[0].idx = -1;
+ r->ridx = 0;
+ }
+
+ /* Process items in reply. */
+ while (r->ridx >= 0)
+ if (processItem(r) < 0)
+ break;
+
+ /* Discard the consumed part of the buffer. */
+ if (r->pos > 0) {
+ if (r->pos == sdslen(r->buf)) {
+ /* sdsrange has a quirck on this edge case. */
+ sdsfree(r->buf);
+ r->buf = sdsempty();
+ } else {
+ r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
+ }
+ r->pos = 0;
+ }
+
+ /* Emit a reply when there is one. */
+ if (r->ridx == -1) {
+ void *aux = r->reply;
+ r->reply = NULL;
+
+ /* Destroy the buffer when it is empty and is quite large. */
+ if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
+ sdsfree(r->buf);
+ r->buf = sdsempty();
+ r->pos = 0;
+ }
+
+ /* Check if there actually *is* a reply. */
+ if (r->error != NULL) {
+ return REDIS_ERR;
+ } else {
+ if (reply != NULL) *reply = aux;
+ }
+ }
+ return REDIS_OK;
+}
+
+/* Calculate the number of bytes needed to represent an integer as string. */
+static int intlen(int i) {
+ int len = 0;
+ if (i < 0) {
+ len++;
+ i = -i;
+ }
+ do {
+ len++;
+ i /= 10;
+ } while(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;
+}
+
+int redisvFormatCommand(char **target, const char *format, va_list ap) {
+ size_t size;
+ const char *arg, *c = format;
+ char *cmd = NULL; /* final command */
+ int pos; /* position in final command */
+ sds current; /* current argument */
+ char **argv = NULL;
+ int argc = 0, j;
+ int totlen = 0;
+
+ /* Abort if there is not target to set */
+ if (target == NULL)
+ return -1;
+
+ /* Build the command string accordingly to protocol */
+ current = sdsempty();
+ while(*c != '\0') {
+ if (*c != '%' || c[1] == '\0') {
+ if (*c == ' ') {
+ if (sdslen(current) != 0) {
+ addArgument(current, &argv, &argc, &totlen);
+ current = sdsempty();
+ }
+ } else {
+ current = sdscatlen(current,c,1);
+ }
+ } else {
+ switch(c[1]) {
+ case 's':
+ arg = va_arg(ap,char*);
+ current = sdscat(current,arg);
+ break;
+ case 'b':
+ arg = va_arg(ap,char*);
+ size = va_arg(ap,size_t);
+ current = sdscatlen(current,arg,size);
+ break;
+ case '%':
+ cmd = sdscat(cmd,"%");
+ break;
+ }
+ c++;
+ }
+ c++;
+ }
+
+ /* Add the last argument if needed */
+ if (sdslen(current) != 0) {
+ addArgument(current, &argv, &argc, &totlen);
+ } else {
+ sdsfree(current);
+ }
+
+ /* 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();
+ 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]);
+ cmd[pos++] = '\r';
+ cmd[pos++] = '\n';
+ }
+ assert(pos == totlen);
+ free(argv);
+ cmd[totlen] = '\0';
+ *target = cmd;
+ return totlen;
+}
+
+/* Format a command according to the Redis protocol. This function
+ * takes a format similar to printf:
+ *
+ * %s represents a C null terminated string you want to interpolate
+ * %b represents a binary safe string
+ *
+ * When using %b you need to provide both the pointer to the string
+ * and the length in bytes. Examples:
+ *
+ * len = redisFormatCommand(target, "GET %s", mykey);
+ * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
+ */
+int redisFormatCommand(char **target, const char *format, ...) {
+ va_list ap;
+ int len;
+ va_start(ap,format);
+ len = redisvFormatCommand(target,format,ap);
+ va_end(ap);
+ return len;
+}
+
+/* Format a command according to the Redis protocol. This function takes the
+ * number of arguments, an array with arguments and an array with their
+ * lengths. If the latter is set to NULL, strlen will be used to compute the
+ * argument lengths.
+ */
+int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
+ char *cmd = NULL; /* final command */
+ int pos; /* position in final command */
+ size_t len;
+ int totlen, j;
+
+ /* Calculate number of bytes needed for the command */
+ 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;
+ }
+
+ /* Build the command at protocol level */
+ cmd = malloc(totlen+1);
+ if (!cmd) redisOOM();
+ pos = sprintf(cmd,"*%d\r\n",argc);
+ for (j = 0; j < argc; j++) {
+ len = argvlen ? argvlen[j] : strlen(argv[j]);
+ pos += sprintf(cmd+pos,"$%zu\r\n",len);
+ memcpy(cmd+pos,argv[j],len);
+ pos += len;
+ cmd[pos++] = '\r';
+ cmd[pos++] = '\n';
+ }
+ assert(pos == totlen);
+ cmd[totlen] = '\0';
+ *target = cmd;
+ return totlen;
+}
+
+void __redisSetError(redisContext *c, int type, const sds errstr) {
+ c->err = type;
+ if (errstr != NULL) {
+ c->errstr = errstr;
+ } else {
+ /* Only REDIS_ERR_IO may lack a description! */
+ assert(type == REDIS_ERR_IO);
+ c->errstr = sdsnew(strerror(errno));
+ }
+}
+
+static redisContext *redisContextInit() {
+ redisContext *c = calloc(sizeof(redisContext),1);
+ c->err = 0;
+ c->errstr = NULL;
+ c->obuf = sdsempty();
+ c->fn = &defaultFunctions;
+ c->reader = NULL;
+ return c;
+}
+
+void redisFree(redisContext *c) {
+ /* Disconnect before free'ing if not yet disconnected. */
+ if (c->flags & REDIS_CONNECTED)
+ close(c->fd);
+ if (c->errstr != NULL)
+ sdsfree(c->errstr);
+ if (c->obuf != NULL)
+ sdsfree(c->obuf);
+ if (c->reader != NULL)
+ redisReplyReaderFree(c->reader);
+ free(c);
+}
+
+/* Connect to a Redis instance. On error the field error in the returned
+ * context will be set to the return value of the error function.
+ * When no set of reply functions is given, the default set will be used. */
+redisContext *redisConnect(const char *ip, int port) {
+ redisContext *c = redisContextInit();
+ c->flags |= REDIS_BLOCK;
+ c->flags |= REDIS_CONNECTED;
+ redisContextConnectTcp(c,ip,port);
+ return c;
+}
+
+redisContext *redisConnectNonBlock(const char *ip, int port) {
+ redisContext *c = redisContextInit();
+ c->flags &= ~REDIS_BLOCK;
+ c->flags |= REDIS_CONNECTED;
+ redisContextConnectTcp(c,ip,port);
+ return c;
+}
+
+redisContext *redisConnectUnix(const char *path) {
+ redisContext *c = redisContextInit();
+ c->flags |= REDIS_BLOCK;
+ c->flags |= REDIS_CONNECTED;
+ redisContextConnectUnix(c,path);
+ return c;
+}
+
+redisContext *redisConnectUnixNonBlock(const char *path) {
+ redisContext *c = redisContextInit();
+ c->flags &= ~REDIS_BLOCK;
+ c->flags |= REDIS_CONNECTED;
+ redisContextConnectUnix(c,path);
+ return c;
+}
+
+/* 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(c->fn);
+}
+
+/* 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[2048];
+ int nread = read(c->fd,buf,sizeof(buf));
+ if (nread == -1) {
+ if (errno == EAGAIN) {
+ /* Try again later */
+ } else {
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ return REDIS_ERR;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c,REDIS_ERR_EOF,
+ sdsnew("Server closed the connection"));
+ return REDIS_ERR;
+ } else {
+ __redisCreateReplyReader(c);
+ redisReplyReaderFeed(c->reader,buf,nread);
+ }
+ return REDIS_OK;
+}
+
+/* Write the output buffer to the socket.
+ *
+ * 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).
+ *
+ * Returns REDIS_ERR if an error occured trying to write and sets
+ * c->error to hold the appropriate error string.
+ */
+int redisBufferWrite(redisContext *c, int *done) {
+ int nwritten;
+ if (sdslen(c->obuf) > 0) {
+ nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
+ if (nwritten == -1) {
+ if (errno == EAGAIN) {
+ /* Try again later */
+ } else {
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ return REDIS_ERR;
+ }
+ } else if (nwritten > 0) {
+ if (nwritten == (signed)sdslen(c->obuf)) {
+ sdsfree(c->obuf);
+ c->obuf = sdsempty();
+ } else {
+ c->obuf = sdsrange(c->obuf,nwritten,-1);
+ }
+ }
+ }
+ if (done != NULL) *done = (sdslen(c->obuf) == 0);
+ return REDIS_OK;
+}
+
+/* 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));
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+int redisGetReply(redisContext *c, void **reply) {
+ int wdone = 0;
+ void *aux = NULL;
+
+ /* Try to read pending replies */
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+ return REDIS_ERR;
+
+ /* For the blocking context, flush output buffer and read reply */
+ if (aux == NULL && c->flags & REDIS_BLOCK) {
+ /* Write until done */
+ do {
+ if (redisBufferWrite(c,&wdone) == REDIS_ERR)
+ return REDIS_ERR;
+ } while (!wdone);
+
+ /* Read until there is a reply */
+ do {
+ if (redisBufferRead(c) == REDIS_ERR)
+ return REDIS_ERR;
+ if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+ return REDIS_ERR;
+ } while (aux == NULL);
+ }
+
+ /* Set reply object */
+ if (reply != NULL) *reply = aux;
+ return REDIS_OK;
+}
+
+
+/* Helper function for the redisAppendCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. When this family
+ * 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);
+}
+
+void redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
+ char *cmd;
+ int len;
+ len = redisvFormatCommand(&cmd,format,ap);
+ __redisAppendCommand(c,cmd,len);
+ free(cmd);
+}
+
+void redisAppendCommand(redisContext *c, const char *format, ...) {
+ va_list ap;
+ va_start(ap,format);
+ redisvAppendCommand(c,format,ap);
+ va_end(ap);
+}
+
+void 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);
+ free(cmd);
+}
+
+/* Helper function for the redisCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. If the given context is
+ * blocking, immediately read the reply into the "reply" pointer. When the
+ * context is non-blocking, the "reply" pointer will not be used and the
+ * command is simply appended to the write buffer.
+ *
+ * Returns the reply when a reply was succesfully retrieved. Returns NULL
+ * 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);
+
+ if (c->flags & REDIS_BLOCK) {
+ if (redisGetReply(c,&aux) == REDIS_OK)
+ return aux;
+ return NULL;
+ }
+ 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;
+}
+
+void *redisCommand(redisContext *c, const char *format, ...) {
+ va_list ap;
+ void *reply = NULL;
+ va_start(ap,format);
+ reply = redisvCommand(c,format,ap);
+ va_end(ap);
+ return reply;
+}
+
+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;
+}
View
156 deps/hiredis/hiredis.h
@@ -0,0 +1,156 @@
+/*
+ * 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 __HIREDIS_H
+#define __HIREDIS_H
+#include <stdio.h> /* for size_t */
+#include <stdarg.h> /* for va_list */
+
+#define HIREDIS_MAJOR 0
+#define HIREDIS_MINOR 9
+#define HIREDIS_PATCH 0
+
+#define REDIS_ERR -1
+#define REDIS_OK 0
+
+/* When an error occurs, the err flag in a context is set to hold the type of
+ * 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 */
+
+/* Connection type can be blocking or non-blocking and is set in the
+ * least significant bit of the flags field in redisContext. */
+#define REDIS_BLOCK 0x1
+
+/* Connection may be disconnected before being free'd. The second bit
+ * in the flags field is set when the context is connected. */
+#define REDIS_CONNECTED 0x2
+
+/* The async API might try to disconnect cleanly and flush the output
+ * buffer and read all subsequent replies before disconnecting.
+ * This flag means no new commands can come in and the connection
+ * should be terminated once all replies have been read. */
+#define REDIS_DISCONNECTING 0x4
+
+#define REDIS_REPLY_ERROR 0
+#define REDIS_REPLY_STRING 1
+#define REDIS_REPLY_ARRAY 2
+#define REDIS_REPLY_INTEGER 3
+#define REDIS_REPLY_NIL 4
+#define REDIS_REPLY_STATUS 5
+
+/* This is the reply object returned by redisCommand() */
+typedef struct redisReply {
+ int type; /* REDIS_REPLY_* */
+ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
+ int len; /* Length of string */
+ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
+ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
+ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
+} redisReply;
+
+typedef struct redisReadTask {
+ int type;
+ int elements; /* number of elements in multibulk container */
+ void *parent; /* optional pointer to parent object */
+ int idx; /* index in parent (array) object */
+} redisReadTask;
+
+typedef struct redisReplyObjectFunctions {
+ void *(*createString)(const redisReadTask*, char*, size_t);
+ void *(*createArray)(const redisReadTask*, int);
+ void *(*createInteger)(const redisReadTask*, long long);
+ void *(*createNil)(const redisReadTask*);
+ 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 */
+ int err; /* Error flags, 0 when there is no error */
+ char *errstr; /* String representation of error when applicable */
+
+ /* Function set for reply buildup and reply reader */
+ redisReplyObjectFunctions *fn;
+ void *reader;
+} redisContext;
+
+void freeReplyObject(void *reply);
+void *redisReplyReaderCreate(redisReplyObjectFunctions *fn);
+void *redisReplyReaderGetObject(void *reader);
+char *redisReplyReaderGetError(void *reader);
+void redisReplyReaderFree(void *ptr);
+void redisReplyReaderFeed(void *reader, char *buf, int 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);
+
+redisContext *redisConnect(const char *ip, int port);
+redisContext *redisConnectNonBlock(const char *ip, int port);
+redisContext *redisConnectUnix(const char *path);
+redisContext *redisConnectUnixNonBlock(const char *path);
+int redisSetReplyObjectFunctions(redisContext *c, redisReplyObjectFunctions *fn);
+void redisFree(redisContext *c);
+int redisBufferRead(redisContext *c);
+int redisBufferWrite(redisContext *c, int *done);
+
+/* In a blocking context, this function first checks if there are unconsumed
+ * replies to return and returns one if so. Otherwise, it flushes the output
+ * buffer to the socket and reads until it has a reply. In a non-blocking
+ * context, it will return unconsumed replies until there are no more. */
+int redisGetReply(redisContext *c, void **reply);
+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);
+
+/* Issue a command to Redis. In a blocking context, it is identical to calling
+ * redisAppendCommand, followed by redisGetReply. The function will return
+ * NULL if there was an error in performing the request, otherwise it will
+ * return the reply. In a non-blocking context, it is identical to calling
+ * only redisAppendCommand and will always return NULL. */
+void *redisvCommand(redisContext *c, const char *format, va_list ap);
+void *redisCommand(redisContext *c, const char *format, ...);
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+#endif
View
167 deps/hiredis/net.c
@@ -0,0 +1,167 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2006-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.
+ */
+
+#include "fmacros.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "hiredis.h"
+#include "sds.h"
+
+/* Forward declaration */
+void __redisSetError(redisContext *c, int type, sds err);
+
+static int redisCreateSocket(redisContext *c, int type) {
+ int s, on = 1;
+ if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ 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);
+ return REDIS_ERR;
+ }
+ }
+ return s;
+}
+
+static int redisSetNonBlock(redisContext *c, int fd) {
+ int flags;
+
+ /* Set the socket nonblocking.
+ * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+ * interrupted by a signal. */
+ if ((flags = fcntl(fd, F_GETFL)) == -1) {
+ __redisSetError(c,REDIS_ERR_IO,
+ sdscatprintf(sdsempty(), "fcntl(F_GETFL): %s", strerror(errno)));
+ close(fd);
+ return REDIS_ERR;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ __redisSetError(c,REDIS_ERR_IO,
+ sdscatprintf(sdsempty(), "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)));
+ close(fd);
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+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)));
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
+ int s;
+ int blocking = (c->flags & REDIS_BLOCK);
+ struct sockaddr_in sa;
+
+ if ((s = redisCreateSocket(c,AF_INET)) == REDIS_ERR)
+ return REDIS_ERR;
+ if (!blocking && redisSetNonBlock(c,s) == REDIS_ERR)
+ return REDIS_ERR;
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_aton(addr, &sa.sin_addr) == 0) {
+ struct hostent *he;
+
+ 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 (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ if (errno == EINPROGRESS && !blocking) {
+ /* This is ok. */
+ } else {
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ close(s);
+ return REDIS_ERR;
+ }
+ }
+
+ if (redisSetTcpNoDelay(c,s) != REDIS_OK) {
+ close(s);
+ return REDIS_ERR;
+ }
+
+ c->fd = s;
+ return REDIS_OK;
+}
+
+int redisContextConnectUnix(redisContext *c, const char *path) {
+ int s;
+ int blocking = (c->flags & REDIS_BLOCK);
+ struct sockaddr_un sa;
+
+ if ((s = redisCreateSocket(c,AF_LOCAL)) == REDIS_ERR)
+ return REDIS_ERR;
+ if (!blocking && redisSetNonBlock(c,s) != REDIS_OK)
+ return REDIS_ERR;
+
+ sa.sun_family = AF_LOCAL;
+ strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
+ if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ if (errno == EINPROGRESS && !blocking) {
+ /* This is ok. */
+ } else {
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ close(s);
+ return REDIS_ERR;
+ }
+ }
+
+ c->fd = s;
+ return REDIS_OK;
+}
View
37 deps/hiredis/net.h
@@ -0,0 +1,37 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2006-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 __NET_H
+#define __NET_H
+
+int redisContextConnectTcp(redisContext *c, const char *addr, int port);
+int redisContextConnectUnix(redisContext *c, const char *path);
+
+#endif
View
479 deps/hiredis/sds.c
@@ -0,0 +1,479 @@
+/* SDSLib, A C dynamic strings library
+ *
+ * Copyright (c) 2006-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.
+ */
+
+#define SDS_ABORT_ON_OOM
+
+#include "sds.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+static void sdsOomAbort(void) {
+ fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
+ abort();
+}
+
+sds sdsnewlen(const void *init, size_t initlen) {
+ struct sdshdr *sh;
+
+ sh = malloc(sizeof(struct sdshdr)+initlen+1);
+#ifdef SDS_ABORT_ON_OOM
+ if (sh == NULL) sdsOomAbort();
+#else
+ if (sh == NULL) return NULL;
+#endif
+ sh->len = initlen;
+ sh->free = 0;
+ if (initlen) {
+ if (init) memcpy(sh->buf, init, initlen);
+ else memset(sh->buf,0,initlen);
+ }
+ sh->buf[initlen] = '\0';
+ return (char*)sh->buf;
+}
+
+sds sdsempty(void) {
+ return sdsnewlen("",0);
+}
+
+sds sdsnew(const char *init) {
+ size_t initlen = (init == NULL) ? 0 : strlen(init);
+ return sdsnewlen(init, initlen);
+}
+
+size_t sdslen(const sds s) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ return sh->len;
+}
+
+sds sdsdup(const sds s) {
+ return sdsnewlen(s, sdslen(s));
+}
+
+void sdsfree(sds s) {
+ if (s == NULL) return;
+ free(s-sizeof(struct sdshdr));
+}
+
+size_t sdsavail(sds s) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ return sh->free;
+}
+
+void sdsupdatelen(sds s) {