Add internal hiredis if it is not found in system.
ENDIF(ENABLE_STATIC MATCHES "ON")
pkg_check_modules(GMIME2 gmime-2.0)
+# Try to find hiredis library
+pkg_check_modules(HIREDIS libhiredis)
+IF(HIREDIS_INCLUDE_DIRS)
+ INCLUDE_DIRECTORIES("${HIREDIS_INCLUDE_DIRS}")
+ENDIF(HIREDIS_INCLUDE_DIRS)
+IF(HIREDIS_LIBRARY_DIRS)
+ LINK_DIRECTORIES("${HIREDIS_LIBRARY_DIRS}")
+ENDIF(HIREDIS_LIBRARY_DIRS)
+
# Try to link with gmime24
IF(NOT GMIME2_FOUND OR FORCE_GMIME24)
pkg_check_modules(GMIME24 REQUIRED gmime-2.4)
src/plugins/fuzzy_check.c
src/plugins/spf.c)
+
+################################ SUBDIRS SECTION ###########################
+
ADD_CUSTOM_COMMAND(OUTPUT src/modules.c
COMMAND ${CMAKE_SOURCE_DIR}/utils/gen-modules.sh ${PLUGINSSRC}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src)
ADD_SUBDIRECTORY(utils)
ADD_SUBDIRECTORY(test)
+IF(NOT HIREDIS_FOUND)
+ ADD_SUBDIRECTORY(contrib/hiredis)
+ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/contrib/hiredis")
+ENDIF(NOT HIREDIS_FOUND)
+
LIST(LENGTH PLUGINSSRC RSPAMD_MODULES_NUM)
############################ TARGETS SECTION ###############################
TARGET_LINK_LIBRARIES(rspamd ${PCRE_LIBRARIES})
ENDIF(ENABLE_STATIC MATCHES "ON")
+TARGET_LINK_LIBRARIES(rspamd hiredis)
+
IF(ENABLE_LUAJIT MATCHES "ON")
TARGET_LINK_LIBRARIES(rspamd "${LUAJIT_LIBRARY}")
ELSE(ENABLE_LUAJIT MATCHES "ON")
--- /dev/null
+/hiredis-test
+/hiredis-example*
+/*.o
+/*.so
+/*.dylib
+/*.a
--- /dev/null
+### 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.
+
--- /dev/null
+# Hiredis compilation target
+
+SET(LIBHIREDISSRC async.c
+ dict.c
+ hiredis.c
+ net.c
+ sds.c)
+
+ADD_LIBRARY(hiredis STATIC ${LIBHIREDISSRC})
+IF(CMAKE_COMPILER_IS_GNUCC)
+SET_TARGET_PROPERTIES(hiredis PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing")
+ENDIF(CMAKE_COMPILER_IS_GNUCC)
+
+TARGET_LINK_LIBRARIES(hiredis ${CMAKE_REQUIRED_LIBRARIES})
\ No newline at end of file
--- /dev/null
+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:
+
+* 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.
--- /dev/null
+# 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 integer `err` field that is non-zero when an the connection is in
+an error state. The field `errstr` will contain a string with a description of
+the error. More information on errors can be found in the **Errors** section.
+After trying to connect to Redis using `redisConnect` you should
+check the `err` field to see if establishing the connection was successful:
+
+ redisContext *c = redisConnect("127.0.0.1", 6379);
+ if (c->err) {
+ printf("Error: %s\n", c->errstr);
+ // 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 an error occurs, the return value is `NULL` and
+the `err` field in the context will be set (see section on **Errors**).
+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->element[..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).
+
+**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:
+
+ 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 `err` 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 `read(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);
+ }
+
+### Errors
+
+When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
+returned. The `err` field inside the context will be non-zero and set to one of the
+following constants:
+
+* **`REDIS_ERR_IO`**:
+ There was an I/O error while creating the connection, trying to write
+ to the socket or read from the socket. If you included `errno.h` in your
+ application, you can use the global `errno` variable to find out what is
+ wrong.
+
+* **`REDIS_ERR_EOF`**:
+ The server closed the connection which resulted in an empty read.
+
+* **`REDIS_ERR_PROTOCOL`**:
+ There was an error while parsing the protocol.
+
+* **`REDIS_ERR_OTHER`**:
+ Any other error. Currently, it is only used when a specified hostname to connect
+ to cannot be resolved.
+
+In every case, the `errstr` field in the context will be set to hold a string representation
+of the error.
+
+## 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 `err` 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->err) {
+ printf("Error: %s\n", c->errstr);
+ // 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 `err`
+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
+
+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
+
+Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
+Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
--- /dev/null
+#include <sys/types.h>
+#include <ae.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisAeEvents {
+ redisAsyncContext *context;
+ aeEventLoop *loop;
+ int fd;
+ int reading, writing;
+} redisAeEvents;
+
+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) {
+ ((void)el); ((void)fd); ((void)mask);
+
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAsyncHandleWrite(e->context);
+}
+
+void redisAeAddRead(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (!e->reading) {
+ e->reading = 1;
+ aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
+ }
+}
+
+void redisAeDelRead(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (e->reading) {
+ e->reading = 0;
+ aeDeleteFileEvent(loop,e->fd,AE_READABLE);
+ }
+}
+
+void redisAeAddWrite(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (!e->writing) {
+ e->writing = 1;
+ aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
+ }
+}
+
+void redisAeDelWrite(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ aeEventLoop *loop = e->loop;
+ if (e->writing) {
+ e->writing = 0;
+ aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
+ }
+}
+
+void redisAeCleanup(void *privdata) {
+ redisAeEvents *e = (redisAeEvents*)privdata;
+ redisAeDelRead(privdata);
+ redisAeDelWrite(privdata);
+ free(e);
+}
+
+int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisAeEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisAeEvents*)malloc(sizeof(*e));
+ e->context = ac;
+ e->loop = loop;
+ e->fd = c->fd;
+ e->reading = e->writing = 0;
+
+ /* Register functions to start/stop listening for events */
+ 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;
+}
+
--- /dev/null
+#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(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+ ((void)loop);
+#endif
+ ((void)revents);
+
+ redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+ redisAsyncHandleRead(e->context);
+}
+
+void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+ ((void)loop);
+#endif
+ ((void)revents);
+
+ redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+ redisAsyncHandleWrite(e->context);
+}
+
+void redisLibevAddRead(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
+ if (!e->reading) {
+ e->reading = 1;
+ ev_io_start(EV_A_ &e->rev);
+ }
+}
+
+void redisLibevDelRead(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
+ if (e->reading) {
+ e->reading = 0;
+ ev_io_stop(EV_A_ &e->rev);
+ }
+}
+
+void redisLibevAddWrite(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
+ if (!e->writing) {
+ e->writing = 1;
+ ev_io_start(EV_A_ &e->wev);
+ }
+}
+
+void redisLibevDelWrite(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ struct ev_loop *loop = e->loop;
+ ((void)loop);
+ if (e->writing) {
+ e->writing = 0;
+ ev_io_stop(EV_A_ &e->wev);
+ }
+}
+
+void redisLibevCleanup(void *privdata) {
+ redisLibevEvents *e = (redisLibevEvents*)privdata;
+ redisLibevDelRead(privdata);
+ redisLibevDelWrite(privdata);
+ free(e);
+}
+
+int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisLibevEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisLibevEvents*)malloc(sizeof(*e));
+ e->context = ac;
+#if EV_MULTIPLICITY
+ e->loop = loop;
+#else
+ e->loop = NULL;
+#endif
+ e->reading = e->writing = 0;
+ e->rev.data = e;
+ e->wev.data = e;
+
+ /* Register functions to start/stop listening for events */
+ 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);
+ ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
+ return REDIS_OK;
+}
+
--- /dev/null
+#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 = (redisLibeventEvents*)arg;
+ redisAsyncHandleRead(e->context);
+}
+
+void redisLibeventWriteEvent(int fd, short event, void *arg) {
+ ((void)fd); ((void)event);
+ redisLibeventEvents *e = (redisLibeventEvents*)arg;
+ redisAsyncHandleWrite(e->context);
+}
+
+void redisLibeventAddRead(void *privdata) {
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+ event_add(&e->rev,NULL);
+}
+
+void redisLibeventDelRead(void *privdata) {
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+ event_del(&e->rev);
+}
+
+void redisLibeventAddWrite(void *privdata) {
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+ event_add(&e->wev,NULL);
+}
+
+void redisLibeventDelWrite(void *privdata) {
+ redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+ event_del(&e->wev);
+}
+
+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) {
+ redisContext *c = &(ac->c);
+ redisLibeventEvents *e;
+
+ /* Nothing should be attached when something is already attached */
+ if (ac->ev.data != NULL)
+ return REDIS_ERR;
+
+ /* Create container for context and r/w events */
+ e = (redisLibeventEvents*)malloc(sizeof(*e));
+ e->context = ac;
+
+ /* Register functions to start/stop listening for events */
+ 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);
+ 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;
+}
--- /dev/null
+/*
+ * 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:
+ *
+ * * 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 <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <ctype.h>
+#include "async.h"
+#include "dict.c"
+#include "sds.h"
+
+/* Forward declaration of function in hiredis.c */
+void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
+
+/* Functions managing dictionary of callbacks for pub/sub. */
+static unsigned int callbackHash(const void *key) {
+ return dictGenHashFunction((unsigned char*)key,sdslen((char*)key));
+}
+
+static void *callbackValDup(void *privdata, const void *src) {
+ ((void) privdata);
+ redisCallback *dup = malloc(sizeof(*dup));
+ memcpy(dup,src,sizeof(*dup));
+ return dup;
+}
+
+static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
+ int l1, l2;
+ ((void) privdata);
+
+ l1 = sdslen((sds)key1);
+ l2 = sdslen((sds)key2);
+ if (l1 != l2) return 0;
+ return memcmp(key1,key2,l1) == 0;
+}
+
+static void callbackKeyDestructor(void *privdata, void *key) {
+ ((void) privdata);
+ sdsfree((sds)key);
+}
+
+static void callbackValDestructor(void *privdata, void *val) {
+ ((void) privdata);
+ free(val);
+}
+
+static dictType callbackDict = {
+ callbackHash,
+ NULL,
+ callbackValDup,
+ callbackKeyCompare,
+ callbackKeyDestructor,
+ callbackValDestructor
+};
+
+static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
+ redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
+ c = &(ac->c);
+
+ /* The regular connect functions will always set the flag REDIS_CONNECTED.
+ * For the async API, we want to wait until the first write event is
+ * received up before setting this flag, so reset it here. */
+ c->flags &= ~REDIS_CONNECTED;
+
+ ac->err = 0;
+ ac->errstr = NULL;
+ ac->data = NULL;
+
+ ac->ev.data = NULL;
+ ac->ev.addRead = NULL;
+ ac->ev.delRead = NULL;
+ ac->ev.addWrite = NULL;
+ ac->ev.delWrite = NULL;
+ ac->ev.cleanup = NULL;
+
+ ac->onConnect = NULL;
+ ac->onDisconnect = NULL;
+
+ ac->replies.head = NULL;
+ ac->replies.tail = NULL;
+ ac->sub.invalid.head = NULL;
+ ac->sub.invalid.tail = NULL;
+ ac->sub.channels = dictCreate(&callbackDict,NULL);
+ ac->sub.patterns = dictCreate(&callbackDict,NULL);
+ 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 redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
+ if (ac->onConnect == NULL) {
+ ac->onConnect = 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);
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+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 = malloc(sizeof(*cb));
+ if (source != NULL) {
+ memcpy(cb,source,sizeof(*cb));
+ cb->next = NULL;
+ }
+
+ /* 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;
+}
+
+static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
+ redisContext *c = &(ac->c);
+ if (cb->fn != NULL) {
+ c->flags |= REDIS_IN_CALLBACK;
+ cb->fn(ac,reply,cb->privdata);
+ c->flags &= ~REDIS_IN_CALLBACK;
+ }
+}
+
+/* Helper function to free the context. */
+static void __redisAsyncFree(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+ dictIterator *it;
+ dictEntry *de;
+
+ /* Execute pending callbacks with NULL reply. */
+ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
+ __redisRunCallback(ac,&cb,NULL);
+
+ /* Execute callbacks for invalid commands */
+ while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
+ __redisRunCallback(ac,&cb,NULL);
+
+ /* Run subscription callbacks callbacks with NULL reply */
+ it = dictGetIterator(ac->sub.channels);
+ while ((de = dictNext(it)) != NULL)
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+ dictReleaseIterator(it);
+ dictRelease(ac->sub.channels);
+
+ it = dictGetIterator(ac->sub.patterns);
+ while ((de = dictNext(it)) != NULL)
+ __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+ dictReleaseIterator(it);
+ dictRelease(ac->sub.patterns);
+
+ /* Signal event lib to clean up */
+ if (ac->ev.cleanup) ac->ev.cleanup(ac->ev.data);
+
+ /* Execute disconnect callback. When redisAsyncFree() initiated destroying
+ * this context, the status will always be REDIS_OK. */
+ if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
+ if (c->flags & REDIS_FREEING) {
+ ac->onDisconnect(ac,REDIS_OK);
+ } else {
+ ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
+ }
+ }
+
+ /* Cleanup self */
+ redisFree(c);
+}
+
+/* Free the async context. When this function is called from a callback,
+ * control needs to be returned to redisProcessCallbacks() before actual
+ * free'ing. To do so, a flag is set on the context which is picked up by
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
+void redisAsyncFree(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ c->flags |= REDIS_FREEING;
+ if (!(c->flags & REDIS_IN_CALLBACK))
+ __redisAsyncFree(ac);
+}
+
+/* Helper function to make the disconnect happen and clean up. */
+static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ /* Make sure error is accessible if there is any */
+ __redisAsyncCopyError(ac);
+
+ if (ac->err == 0) {
+ /* For clean disconnects, there should be no pending callbacks. */
+ assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+ } else {
+ /* Disconnection is caused by an error, make sure that pending
+ * callbacks cannot call new commands. */
+ c->flags |= REDIS_DISCONNECTING;
+ }
+
+ /* For non-clean disconnects, __redisAsyncFree() will execute pending
+ * callbacks with a NULL-reply. */
+ __redisAsyncFree(ac);
+}
+
+/* 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. When this function is called from a
+ * callback, there might be more replies and we can safely defer disconnecting
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
+ * when there are no pending callbacks. */
+void redisAsyncDisconnect(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ c->flags |= REDIS_DISCONNECTING;
+ if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
+ __redisAsyncDisconnect(ac);
+}
+
+static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
+ redisContext *c = &(ac->c);
+ dict *callbacks;
+ dictEntry *de;
+ int pvariant;
+ char *stype;
+ sds sname;
+
+ /* Custom reply functions are not supported for pub/sub. This will fail
+ * very hard when they are used... */
+ if (reply->type == REDIS_REPLY_ARRAY) {
+ assert(reply->elements >= 2);
+ assert(reply->element[0]->type == REDIS_REPLY_STRING);
+ stype = reply->element[0]->str;
+ pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
+
+ if (pvariant)
+ callbacks = ac->sub.patterns;
+ else
+ callbacks = ac->sub.channels;
+
+ /* Locate the right callback */
+ assert(reply->element[1]->type == REDIS_REPLY_STRING);
+ sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
+ de = dictFind(callbacks,sname);
+ if (de != NULL) {
+ memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
+
+ /* If this is an unsubscribe message, remove it. */
+ if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
+ dictDelete(callbacks,sname);
+
+ /* If this was the last unsubscribe message, revert to
+ * non-subscribe mode. */
+ assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
+ if (reply->element[2]->integer == 0)
+ c->flags &= ~REDIS_SUBSCRIBED;
+ }
+ }
+ sdsfree(sname);
+ } else {
+ /* Shift callback for invalid commands. */
+ __redisShiftCallback(&ac->sub.invalid,dstcb);
+ }
+ return REDIS_OK;
+}
+
+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;
+ }
+
+ /* 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) {
+ /* 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->reader->fn->freeObject(reply);
+
+ /* Proceed with free'ing when redisAsyncFree() was called. */
+ if (c->flags & REDIS_FREEING) {
+ __redisAsyncFree(ac);
+ return;
+ }
+ } else {
+ /* No callback for this reply. This can either be a NULL callback,
+ * 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->reader->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->ev.addRead) ac->ev.addRead(ac->ev.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->ev.addWrite) ac->ev.addWrite(ac->ev.data);
+ } else {
+ if (ac->ev.delWrite) ac->ev.delWrite(ac->ev.data);
+ }
+
+ /* 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);
+ }
+ }
+}
+
+/* Sets a pointer to the first argument and its length starting at p. Returns
+ * the number of bytes to skip to get to the following argument. */
+static char *nextArgument(char *start, char **str, size_t *len) {
+ char *p = start;
+ if (p[0] != '$') {
+ p = strchr(p,'$');
+ if (p == NULL) return NULL;
+ }
+
+ *len = (int)strtol(p+1,NULL,10);
+ p = strchr(p,'\r');
+ assert(p);
+ *str = p+2;
+ return p+2+(*len)+2;
+}
+
+/* Helper function for the redisAsyncCommand* family of functions. Writes a
+ * formatted command to the output buffer and registers 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;
+ int pvariant, hasnext;
+ char *cstr, *astr;
+ size_t clen, alen;
+ char *p;
+ sds sname;
+
+ /* Don't accept new commands when the connection is about to be closed. */
+ if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
+
+ /* Setup callback */
+ cb.fn = fn;
+ cb.privdata = privdata;
+
+ /* Find out which command will be appended. */
+ p = nextArgument(cmd,&cstr,&clen);
+ assert(p != NULL);
+ hasnext = (p[0] == '$');
+ pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
+ cstr += pvariant;
+ clen -= pvariant;
+
+ if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
+ c->flags |= REDIS_SUBSCRIBED;
+
+ /* Add every channel/pattern to the list of subscription callbacks. */
+ while ((p = nextArgument(p,&astr,&alen)) != NULL) {
+ sname = sdsnewlen(astr,alen);
+ if (pvariant)
+ dictReplace(ac->sub.patterns,sname,&cb);
+ else
+ dictReplace(ac->sub.channels,sname,&cb);
+ }
+ } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
+ /* It is only useful to call (P)UNSUBSCRIBE when the context is
+ * subscribed to one or more channels or patterns. */
+ if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
+
+ /* (P)UNSUBSCRIBE does not have its own response: every channel or
+ * pattern that is unsubscribed will receive a message. This means we
+ * should not append a callback function for this command. */
+ } else {
+ if (c->flags & REDIS_SUBSCRIBED)
+ /* This will likely result in an error reply, but it needs to be
+ * received and passed to the callback. */
+ __redisPushCallback(&ac->sub.invalid,&cb);
+ else
+ __redisPushCallback(&ac->replies,&cb);
+ }
+
+ __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);
+
+ 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;
+}
--- /dev/null
+/*
+ * 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:
+ *
+ * * 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"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
+struct dict; /* dictionary header is included in async.c */
+
+/* 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;
+
+/* Connection callback prototypes */
+typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*);
+
+/* 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;
+
+ /* Not used by hiredis */
+ void *data;
+
+ /* Event library data and hooks */
+ struct {
+ void *data;
+
+ /* Hooks that are called when the library expects to start
+ * reading/writing. These functions should be idempotent. */
+ void (*addRead)(void *privdata);
+ void (*delRead)(void *privdata);
+ void (*addWrite)(void *privdata);
+ void (*delWrite)(void *privdata);
+ void (*cleanup)(void *privdata);
+ } ev;
+
+ /* 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;
+
+ /* Called when the first write event was received. */
+ redisConnectCallback *onConnect;
+
+ /* Regular command callbacks */
+ redisCallbackList replies;
+
+ /* Subscription callbacks */
+ struct {
+ redisCallbackList invalid;
+ struct dict *channels;
+ struct dict *patterns;
+ } sub;
+} redisAsyncContext;
+
+/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnect(const char *ip, int port);
+redisAsyncContext *redisAsyncConnectUnix(const char *path);
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+void redisAsyncDisconnect(redisAsyncContext *ac);
+void redisAsyncFree(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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /dev/null
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * 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 <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include "dict.h"
+
+/* -------------------------- private prototypes ---------------------------- */
+
+static int _dictExpandIfNeeded(dict *ht);
+static unsigned long _dictNextPower(unsigned long size);
+static int _dictKeyIndex(dict *ht, const void *key);
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
+
+/* -------------------------- hash functions -------------------------------- */
+
+/* Generic hash function (a popular one from Bernstein).
+ * I tested a few and this was the best. */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
+ unsigned int hash = 5381;
+
+ while (len--)
+ hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
+ return hash;
+}
+
+/* ----------------------------- API implementation ------------------------- */
+
+/* Reset an hashtable already initialized with ht_init().
+ * NOTE: This function should only called by ht_destroy(). */
+static void _dictReset(dict *ht) {
+ ht->table = NULL;
+ ht->size = 0;
+ ht->sizemask = 0;
+ ht->used = 0;
+}
+
+/* Create a new hash table */
+static dict *dictCreate(dictType *type, void *privDataPtr) {
+ dict *ht = malloc(sizeof(*ht));
+ _dictInit(ht,type,privDataPtr);
+ return ht;
+}
+
+/* Initialize the hash table */
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr) {
+ _dictReset(ht);
+ ht->type = type;
+ ht->privdata = privDataPtr;
+ return DICT_OK;
+}
+
+/* Expand or create the hashtable */
+static int dictExpand(dict *ht, unsigned long size) {
+ dict n; /* the new hashtable */
+ unsigned long realsize = _dictNextPower(size), i;
+
+ /* the size is invalid if it is smaller than the number of
+ * elements already inside the hashtable */
+ if (ht->used > size)
+ return DICT_ERR;
+
+ _dictInit(&n, ht->type, ht->privdata);
+ n.size = realsize;
+ n.sizemask = realsize-1;
+ n.table = calloc(realsize,sizeof(dictEntry*));
+
+ /* Copy all the elements from the old to the new table:
+ * note that if the old hash table is empty ht->size is zero,
+ * so dictExpand just creates an hash table. */
+ n.used = ht->used;
+ for (i = 0; i < ht->size && ht->used > 0; i++) {
+ dictEntry *he, *nextHe;
+
+ if (ht->table[i] == NULL) continue;
+
+ /* For each hash entry on this slot... */
+ he = ht->table[i];
+ while(he) {
+ unsigned int h;
+
+ nextHe = he->next;
+ /* Get the new element index */
+ h = dictHashKey(ht, he->key) & n.sizemask;
+ he->next = n.table[h];
+ n.table[h] = he;
+ ht->used--;
+ /* Pass to the next element */
+ he = nextHe;
+ }
+ }
+ assert(ht->used == 0);
+ free(ht->table);
+
+ /* Remap the new hashtable in the old */
+ *ht = n;
+ return DICT_OK;
+}
+
+/* Add an element to the target hash table */
+static int dictAdd(dict *ht, void *key, void *val) {
+ int index;
+ dictEntry *entry;
+
+ /* Get the index of the new element, or -1 if
+ * the element already exists. */
+ if ((index = _dictKeyIndex(ht, key)) == -1)
+ return DICT_ERR;
+
+ /* Allocates the memory and stores key */
+ entry = malloc(sizeof(*entry));
+ entry->next = ht->table[index];
+ ht->table[index] = entry;
+
+ /* Set the hash entry fields. */
+ dictSetHashKey(ht, entry, key);
+ dictSetHashVal(ht, entry, val);
+ ht->used++;
+ return DICT_OK;
+}
+
+/* Add an element, discarding the old if the key already exists.
+ * Return 1 if the key was added from scratch, 0 if there was already an
+ * element with such key and dictReplace() just performed a value update
+ * operation. */
+static int dictReplace(dict *ht, void *key, void *val) {
+ dictEntry *entry, auxentry;
+
+ /* Try to add the element. If the key
+ * does not exists dictAdd will suceed. */
+ if (dictAdd(ht, key, val) == DICT_OK)
+ return 1;
+ /* It already exists, get the entry */
+ entry = dictFind(ht, key);
+ /* Free the old value and set the new one */
+ /* Set the new value and free the old one. Note that it is important
+ * to do that in this order, as the value may just be exactly the same
+ * as the previous one. In this context, think to reference counting,
+ * you want to increment (set), and then decrement (free), and not the
+ * reverse. */
+ auxentry = *entry;
+ dictSetHashVal(ht, entry, val);
+ dictFreeEntryVal(ht, &auxentry);
+ return 0;
+}
+
+/* Search and remove an element */
+static int dictDelete(dict *ht, const void *key) {
+ unsigned int h;
+ dictEntry *de, *prevde;
+
+ if (ht->size == 0)
+ return DICT_ERR;
+ h = dictHashKey(ht, key) & ht->sizemask;
+ de = ht->table[h];
+
+ prevde = NULL;
+ while(de) {
+ if (dictCompareHashKeys(ht,key,de->key)) {
+ /* Unlink the element from the list */
+ if (prevde)
+ prevde->next = de->next;
+ else
+ ht->table[h] = de->next;
+
+ dictFreeEntryKey(ht,de);
+ dictFreeEntryVal(ht,de);
+ free(de);
+ ht->used--;
+ return DICT_OK;
+ }
+ prevde = de;
+ de = de->next;
+ }
+ return DICT_ERR; /* not found */
+}
+
+/* Destroy an entire hash table */
+static int _dictClear(dict *ht) {
+ unsigned long i;
+
+ /* Free all the elements */
+ for (i = 0; i < ht->size && ht->used > 0; i++) {
+ dictEntry *he, *nextHe;
+
+ if ((he = ht->table[i]) == NULL) continue;
+ while(he) {
+ nextHe = he->next;
+ dictFreeEntryKey(ht, he);
+ dictFreeEntryVal(ht, he);
+ free(he);
+ ht->used--;
+ he = nextHe;
+ }
+ }
+ /* Free the table and the allocated cache structure */
+ free(ht->table);
+ /* Re-initialize the table */
+ _dictReset(ht);
+ return DICT_OK; /* never fails */
+}
+
+/* Clear & Release the hash table */
+static void dictRelease(dict *ht) {
+ _dictClear(ht);
+ free(ht);
+}
+
+static dictEntry *dictFind(dict *ht, const void *key) {
+ dictEntry *he;
+ unsigned int h;
+
+ if (ht->size == 0) return NULL;
+ h = dictHashKey(ht, key) & ht->sizemask;
+ he = ht->table[h];
+ while(he) {
+ if (dictCompareHashKeys(ht, key, he->key))
+ return he;
+ he = he->next;
+ }
+ return NULL;
+}
+
+static dictIterator *dictGetIterator(dict *ht) {
+ dictIterator *iter = malloc(sizeof(*iter));
+
+ iter->ht = ht;
+ iter->index = -1;
+ iter->entry = NULL;
+ iter->nextEntry = NULL;
+ return iter;
+}
+
+static dictEntry *dictNext(dictIterator *iter) {
+ while (1) {
+ if (iter->entry == NULL) {
+ iter->index++;
+ if (iter->index >=
+ (signed)iter->ht->size) break;
+ iter->entry = iter->ht->table[iter->index];
+ } else {
+ iter->entry = iter->nextEntry;
+ }
+ if (iter->entry) {
+ /* We need to save the 'next' here, the iterator user
+ * may delete the entry we are returning. */
+ iter->nextEntry = iter->entry->next;
+ return iter->entry;
+ }
+ }
+ return NULL;
+}
+
+static void dictReleaseIterator(dictIterator *iter) {
+ free(iter);
+}
+
+/* ------------------------- private functions ------------------------------ */
+
+/* Expand the hash table if needed */
+static int _dictExpandIfNeeded(dict *ht) {
+ /* If the hash table is empty expand it to the intial size,
+ * if the table is "full" dobule its size. */
+ if (ht->size == 0)
+ return dictExpand(ht, DICT_HT_INITIAL_SIZE);
+ if (ht->used == ht->size)
+ return dictExpand(ht, ht->size*2);
+ return DICT_OK;
+}
+
+/* Our hash table capability is a power of two */
+static unsigned long _dictNextPower(unsigned long size) {
+ unsigned long i = DICT_HT_INITIAL_SIZE;
+
+ if (size >= LONG_MAX) return LONG_MAX;
+ while(1) {
+ if (i >= size)
+ return i;
+ i *= 2;
+ }
+}
+
+/* Returns the index of a free slot that can be populated with
+ * an hash entry for the given 'key'.
+ * If the key already exists, -1 is returned. */
+static int _dictKeyIndex(dict *ht, const void *key) {
+ unsigned int h;
+ dictEntry *he;
+
+ /* Expand the hashtable if needed */
+ if (_dictExpandIfNeeded(ht) == DICT_ERR)
+ return -1;
+ /* Compute the key hash value */
+ h = dictHashKey(ht, key) & ht->sizemask;
+ /* Search if this slot does not already contain the given key */
+ he = ht->table[h];
+ while(he) {
+ if (dictCompareHashKeys(ht, key, he->key))
+ return -1;
+ he = he->next;
+ }
+ return h;
+}
+
--- /dev/null
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * 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 __DICT_H
+#define __DICT_H
+
+#define DICT_OK 0
+#define DICT_ERR 1
+
+/* Unused arguments generate annoying warnings... */
+#define DICT_NOTUSED(V) ((void) V)
+
+typedef struct dictEntry {
+ void *key;
+ void *val;
+ struct dictEntry *next;
+} dictEntry;
+
+typedef struct dictType {
+ unsigned int (*hashFunction)(const void *key);
+ void *(*keyDup)(void *privdata, const void *key);
+ void *(*valDup)(void *privdata, const void *obj);
+ int (*keyCompare)(void *privdata, const void *key1, const void *key2);
+ void (*keyDestructor)(void *privdata, void *key);
+ void (*valDestructor)(void *privdata, void *obj);
+} dictType;
+
+typedef struct dict {
+ dictEntry **table;
+ dictType *type;
+ unsigned long size;
+ unsigned long sizemask;
+ unsigned long used;
+ void *privdata;
+} dict;
+
+typedef struct dictIterator {
+ dict *ht;
+ int index;
+ dictEntry *entry, *nextEntry;
+} dictIterator;
+
+/* This is the initial size of every hash table */
+#define DICT_HT_INITIAL_SIZE 4
+
+/* ------------------------------- Macros ------------------------------------*/
+#define dictFreeEntryVal(ht, entry) \
+ if ((ht)->type->valDestructor) \
+ (ht)->type->valDestructor((ht)->privdata, (entry)->val)
+
+#define dictSetHashVal(ht, entry, _val_) do { \
+ if ((ht)->type->valDup) \
+ entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
+ else \
+ entry->val = (_val_); \
+} while(0)
+
+#define dictFreeEntryKey(ht, entry) \
+ if ((ht)->type->keyDestructor) \
+ (ht)->type->keyDestructor((ht)->privdata, (entry)->key)
+
+#define dictSetHashKey(ht, entry, _key_) do { \
+ if ((ht)->type->keyDup) \
+ entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
+ else \
+ entry->key = (_key_); \
+} while(0)
+
+#define dictCompareHashKeys(ht, key1, key2) \
+ (((ht)->type->keyCompare) ? \
+ (ht)->type->keyCompare((ht)->privdata, key1, key2) : \
+ (key1) == (key2))
+
+#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
+
+#define dictGetEntryKey(he) ((he)->key)
+#define dictGetEntryVal(he) ((he)->val)
+#define dictSlots(ht) ((ht)->size)
+#define dictSize(ht) ((ht)->used)
+
+/* API */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len);
+static dict *dictCreate(dictType *type, void *privDataPtr);
+static int dictExpand(dict *ht, unsigned long size);
+static int dictAdd(dict *ht, void *key, void *val);
+static int dictReplace(dict *ht, void *key, void *val);
+static int dictDelete(dict *ht, const void *key);
+static void dictRelease(dict *ht);
+static dictEntry * dictFind(dict *ht, const void *key);
+static dictIterator *dictGetIterator(dict *ht);
+static dictEntry *dictNext(dictIterator *iter);
+static void dictReleaseIterator(dictIterator *iter);
+
+#endif /* __DICT_H */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/ae.h"
+
+/* Put event loop in the global scope, so it can be explicitly stopped */
+static aeEventLoop *loop;
+
+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 connectCallback(const redisAsyncContext *c) {
+ ((void)c);
+ printf("connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ }
+ printf("disconnected...\n");
+ aeStop(loop);
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+
+ redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+
+ loop = aeCreateEventLoop();
+ redisAeAttach(loop, c);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ 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");
+ aeMain(loop);
+ return 0;
+}
+
--- /dev/null
+#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 connectCallback(const redisAsyncContext *c) {
+ ((void)c);
+ printf("connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ }
+ printf("disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+
+ 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(EV_DEFAULT_ c);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ 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(EV_DEFAULT_ 0);
+ return 0;
+}
--- /dev/null
+#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 connectCallback(const redisAsyncContext *c) {
+ ((void)c);
+ printf("connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ }
+ printf("disconnected...\n");
+}
+
+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);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ 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;
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "hiredis.h"
+
+int main(void) {
+ unsigned int j;
+ redisContext *c;
+ redisReply *reply;
+
+ struct timeval timeout = { 1, 500000 }; // 1.5 seconds
+ c = redisConnectWithTimeout((char*)"127.0.0.2", 6379, timeout);
+ if (c->err) {
+ printf("Connection error: %s\n", c->errstr);
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(c,"PING");
+ printf("PING: %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;
+}
--- /dev/null
+#ifndef __HIREDIS_FMACRO_H
+#define __HIREDIS_FMACRO_H
+
+#if !defined(_BSD_SOURCE)
+#define _BSD_SOURCE
+#endif
+
+#if defined(__sun__)
+#define _POSIX_C_SOURCE 200112L
+#elif defined(__linux__)
+#define _XOPEN_SOURCE 600
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#endif
--- /dev/null
+/*
+ * 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:
+ *
+ * * 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 <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "hiredis.h"
+#include "net.h"
+#include "sds.h"
+
+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);
+
+/* 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,
+ createIntegerObject,
+ createNilObject,
+ freeReplyObject
+};
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type) {
+ redisReply *r = calloc(1,sizeof(*r));
+
+ if (r == NULL)
+ return NULL;
+
+ 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:
+ 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:
+ if (r->str != NULL)
+ free(r->str);
+ break;
+ }
+ free(r);
+}
+
+static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
+ 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(buf,str,len);
+ buf[len] = '\0';
+ r->str = buf;
+ r->len = len;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createArrayObject(const redisReadTask *task, int elements) {
+ 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 (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createIntegerObject(const redisReadTask *task, long long value) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_INTEGER);
+ if (r == NULL)
+ return NULL;
+
+ r->integer = value;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createNilObject(const redisReadTask *task) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_NIL);
+ if (r == NULL)
+ return NULL;
+
+ if (task->parent) {
+ 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) {
+ p = r->buf+r->pos;
+ r->pos += bytes;
+ return p;
+ }
+ return NULL;
+}
+
+/* Find pointer to \r\n. */
+static char *seekNewline(char *s, size_t len) {
+ int pos = 0;
+ int _len = len-1;
+
+ /* Position should be < len-1 because the character at "pos" should be
+ * followed by a \n. Note that strchr cannot be used because it doesn't
+ * allow to search a limited length and the buffer that is being searched
+ * might not have a trailing NULL character. */
+ while (pos < _len) {
+ while(pos < _len && s[pos] != '\r') pos++;
+ if (s[pos] != '\r') {
+ /* Not found. */
+ return NULL;
+ } else {
+ if (s[pos+1] == '\n') {
+ /* Found. */
+ return s+pos;
+ } else {
+ /* Continue searching. */
+ pos++;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Read a long long value starting at *s, under the assumption that it will be
+ * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
+static long long readLongLong(char *s) {
+ long long v = 0;
+ int dec, mult = 1;
+ char c;
+
+ if (*s == '-') {
+ mult = -1;
+ s++;
+ } else if (*s == '+') {
+ mult = 1;
+ s++;
+ }
+
+ while ((c = *(s++)) != '\r') {
+ dec = c - '0';
+ if (dec >= 0 && dec < 10) {
+ v *= 10;
+ v += dec;
+ } else {
+ /* Should not happen... */
+ return -1;
+ }
+ }
+
+ return mult*v;
+}
+
+static char *readLine(redisReader *r, int *_len) {
+ char *p, *s;
+ int len;
+
+ p = r->buf+r->pos;
+ s = seekNewline(p,(r->len-r->pos));
+ if (s != NULL) {
+ 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;
+ while (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--;
+ } else {
+ /* Reset the type because the next item can be anything */
+ assert(cur->idx < prv->elements);
+ cur->type = -1;
+ cur->elements = -1;
+ cur->idx++;
+ return;
+ }
+ }
+}
+
+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) {
+ if (r->fn && r->fn->createInteger)
+ obj = r->fn->createInteger(cur,readLongLong(p));
+ else
+ obj = (void*)REDIS_REPLY_INTEGER;
+ } else {
+ /* Type will be error or status. */
+ if (r->fn && r->fn->createString)
+ obj = r->fn->createString(cur,p,len);
+ else
+ 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 REDIS_OK;
+ }
+
+ return REDIS_ERR;
+}
+
+static int processBulkItem(redisReader *r) {
+ redisReadTask *cur = &(r->rstack[r->ridx]);
+ void *obj = NULL;
+ char *p, *s;
+ long len;
+ unsigned long bytelen;
+ int success = 0;
+
+ p = r->buf+r->pos;
+ s = seekNewline(p,r->len-r->pos);
+ if (s != NULL) {
+ p = r->buf+r->pos;
+ bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
+ len = readLongLong(p);
+
+ if (len < 0) {
+ /* The nil object can always be created. */
+ if (r->fn && r->fn->createNil)
+ obj = r->fn->createNil(cur);
+ else
+ obj = (void*)REDIS_REPLY_NIL;
+ success = 1;
+ } else {
+ /* Only continue when the buffer contains the entire bulk item. */
+ bytelen += len+2; /* include \r\n */
+ if (r->pos+bytelen <= r->len) {
+ if (r->fn && r->fn->createString)
+ obj = r->fn->createString(cur,s+2,len);
+ else
+ obj = (void*)REDIS_REPLY_STRING;
+ success = 1;
+ }
+ }
+
+ /* 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 REDIS_OK;
+ }
+ }
+
+ return REDIS_ERR;
+}
+
+static int processMultiBulkItem(redisReader *r) {
+ redisReadTask *cur = &(r->rstack[r->ridx]);
+ void *obj;
+ char *p;
+ long elements;
+ int root = 0;
+
+ /* Set error for nested multi bulks with depth > 1 */
+ if (r->ridx == 2) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "No support for nested multi bulk replies with depth > 1");
+ return REDIS_ERR;
+ }
+
+ if ((p = readLine(r,NULL)) != NULL) {
+ elements = readLongLong(p);
+ root = (r->ridx == 0);
+
+ if (elements == -1) {
+ if (r->fn && r->fn->createNil)
+ 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)
+ obj = r->fn->createArray(cur,elements);
+ 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;
+ cur->obj = obj;
+ r->ridx++;
+ r->rstack[r->ridx].type = -1;
+ r->rstack[r->ridx].elements = -1;
+ r->rstack[r->ridx].idx = 0;
+ r->rstack[r->ridx].obj = NULL;
+ r->rstack[r->ridx].parent = cur;
+ r->rstack[r->ridx].privdata = r->privdata;
+ } else {
+ moveToNextTask(r);
+ }
+ }
+
+ /* Set reply if this is the root object. */
+ if (root) r->reply = obj;
+ return REDIS_OK;
+ }
+
+ return REDIS_ERR;
+}
+
+static int processItem(redisReader *r) {
+ redisReadTask *cur = &(r->rstack[r->ridx]);
+ char *p;
+
+ /* 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:
+ __redisReaderSetErrorProtocolByte(r,*p);
+ return REDIS_ERR;
+ }
+ } else {
+ /* could not consume 1 byte */
+ return REDIS_ERR;
+ }
+ }
+
+ /* 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:
+ assert(NULL);
+ return REDIS_ERR; /* Avoid warning. */
+ }
+}
+
+redisReader *redisReaderCreate(void) {
+ redisReader *r;
+
+ r = calloc(sizeof(redisReader),1);
+ if (r == NULL)
+ return NULL;
+
+ r->err = 0;
+ r->errstr[0] = '\0';
+ r->fn = &defaultFunctions;
+ r->buf = sdsempty();
+ if (r->buf == NULL) {
+ free(r);
+ return NULL;
+ }
+
+ r->ridx = -1;
+ return r;
+}
+
+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);
+}
+
+int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
+ sds newbuf;
+
+ /* 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) {
+ /* 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;
+ }
+
+ r->buf = newbuf;
+ r->len = sdslen(r->buf);
+ }
+
+ return REDIS_OK;
+}
+
+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)
+ 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].idx = -1;
+ r->rstack[0].obj = NULL;
+ r->rstack[0].parent = NULL;
+ r->rstack[0].privdata = r->privdata;
+ r->ridx = 0;
+ }
+
+ /* Process items in reply. */
+ while (r->ridx >= 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) {
+ r->buf = sdsrange(r->buf,r->pos,-1);
+ r->pos = 0;
+ r->len = sdslen(r->buf);
+ }
+
+ /* Emit a reply when there is one. */
+ if (r->ridx == -1) {
+ if (reply != NULL)
+ *reply = r->reply;
+ r->reply = NULL;
+ }
+ 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 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) {
+ const char *c = format;
+ char *cmd = NULL; /* final command */
+ int pos; /* position in final command */
+ sds curarg, newarg; /* current argument */
+ int touched = 0; /* was the current argument touched? */
+ 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 */
+ curarg = sdsempty();
+ if (curarg == NULL)
+ return -1;
+
+ while(*c != '\0') {
+ if (*c != '%' || c[1] == '\0') {
+ if (*c == ' ') {
+ if (touched) {
+ 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 {
+ 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)
+ newarg = sdscatlen(curarg,arg,size);
+ break;
+ case 'b':
+ arg = va_arg(ap,char*);
+ size = va_arg(ap,size_t);
+ if (size > 0)
+ newarg = sdscatlen(curarg,arg,size);
+ break;
+ case '%':
+ newarg = sdscat(curarg,"%");
+ break;
+ default:
+ /* Try to detect printf format */
+ {
+ char _format[16];
+ const char *_p = c+1;
+ size_t _l = 0;
+ va_list _cpy;
+
+ /* Flags */
+ if (*_p != '\0' && *_p == '#') _p++;
+ if (*_p != '\0' && *_p == '0') _p++;
+ if (*_p != '\0' && *_p == '-') _p++;
+ if (*_p != '\0' && *_p == ' ') _p++;
+ if (*_p != '\0' && *_p == '+') _p++;
+
+ /* Field width */
+ while (*_p != '\0' && isdigit(*_p)) _p++;
+
+ /* Precision */
+ if (*_p == '.') {
+ _p++;
+ 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++;
+ }
+ }
+
+ /* 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);
+ newarg = sdscatvprintf(curarg,_format,_cpy);
+ va_end(_cpy);
+
+ /* Update current position (note: outer blocks
+ * increment c twice so compensate here) */
+ c = _p-1;
+ }
+ }
+
+ /* Consume and discard vararg */
+ va_arg(ap,void);
+ }
+ }
+
+ if (newarg == NULL) goto err;
+ curarg = newarg;
+
+ touched = 1;
+ c++;
+ }
+ c++;
+ }
+
+ /* Add the last argument if needed */
+ if (touched) {
+ newargv = realloc(curargv,sizeof(char*)*(argc+1));
+ if (newargv == NULL) goto err;
+ curargv = newargv;
+ curargv[argc++] = curarg;
+ totlen += bulklen(sdslen(curarg));
+ } else {
+ 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 == NULL) goto err;
+
+ pos = sprintf(cmd,"*%d\r\n",argc);
+ for (j = 0; j < argc; 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);
+ 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
+ * 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 += bulklen(len);
+ }
+
+ /* Build the command at protocol level */
+ cmd = malloc(totlen+1);
+ 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]);
+ 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[pos] = '\0';
+
+ *target = cmd;
+ return totlen;
+}
+
+void __redisSetError(redisContext *c, int type, const char *str) {
+ size_t len;
+
+ c->err = type;
+ 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);
+ strerror_r(errno,c->errstr,sizeof(c->errstr));
+ }
+}
+
+static redisContext *redisContextInit(void) {
+ redisContext *c;
+
+ c = calloc(1,sizeof(redisContext));
+ if (c == NULL)
+ return NULL;
+
+ c->err = 0;
+ c->errstr[0] = '\0';
+ c->obuf = sdsempty();
+ c->reader = redisReaderCreate();
+ return c;
+}
+
+void redisFree(redisContext *c) {
+ if (c->fd > 0)
+ close(c->fd);
+ if (c->obuf != NULL)
+ sdsfree(c->obuf);
+ if (c->reader != NULL)
+ redisReaderFree(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;
+ redisContextConnectTcp(c,ip,port,NULL);
+ return c;
+}
+
+redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv) {
+ redisContext *c = redisContextInit();
+ c->flags |= REDIS_BLOCK;
+ redisContextConnectTcp(c,ip,port,&tv);
+ return c;
+}
+
+redisContext *redisConnectNonBlock(const char *ip, int port) {
+ redisContext *c = redisContextInit();
+ c->flags &= ~REDIS_BLOCK;
+ redisContextConnectTcp(c,ip,port,NULL);
+ return c;
+}
+
+redisContext *redisConnectUnix(const char *path) {
+ redisContext *c = redisContextInit();
+ c->flags |= REDIS_BLOCK;
+ redisContextConnectUnix(c,path,NULL);
+ return c;
+}
+
+redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv) {
+ redisContext *c = redisContextInit();
+ c->flags |= REDIS_BLOCK;
+ redisContextConnectUnix(c,path,&tv);
+ return c;
+}
+
+redisContext *redisConnectUnixNonBlock(const char *path) {
+ redisContext *c = redisContextInit();
+ c->flags &= ~REDIS_BLOCK;
+ redisContextConnectUnix(c,path,NULL);
+ return c;
+}
+
+/* Set read/write timeout on a blocking socket. */
+int redisSetTimeout(redisContext *c, struct timeval tv) {
+ if (c->flags & REDIS_BLOCK)
+ return redisContextSetTimeout(c,tv);
+ return REDIS_ERR;
+}
+
+/* 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;
+
+ /* 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 */
+ } else {
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ return REDIS_ERR;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+ return REDIS_ERR;
+ } else {
+ if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
+ __redisSetError(c,c->reader->err,c->reader->errstr);
+ return REDIS_ERR;
+ }
+ }
+ 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;
+
+ /* 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) {
+ if (errno == EAGAIN && !(c->flags & REDIS_BLOCK)) {
+ /* 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) {
+ if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) {
+ __redisSetError(c,c->reader->err,c->reader->errstr);
+ 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).
+ */
+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;
+}
+
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
+ char *cmd;
+ int len;
+
+ len = redisvFormatCommand(&cmd,format,ap);
+ 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;
+}
+
+int redisAppendCommand(redisContext *c, const char *format, ...) {
+ va_list ap;
+ int ret;
+
+ va_start(ap,format);
+ ret = redisvAppendCommand(c,format,ap);
+ va_end(ap);
+ return ret;
+}
+
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+ char *cmd;
+ int len;
+
+ len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+ 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.
+ *
+ * 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 *__redisBlockForReply(redisContext *c) {
+ void *reply;
+
+ if (c->flags & REDIS_BLOCK) {
+ if (redisGetReply(c,&reply) != REDIS_OK)
+ return NULL;
+ return reply;
+ }
+ return NULL;
+}
+
+void *redisvCommand(redisContext *c, const char *format, va_list ap) {
+ if (redisvAppendCommand(c,format,ap) != REDIS_OK)
+ return NULL;
+ return __redisBlockForReply(c);
+}
+
+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) {
+ if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK)
+ return NULL;
+ return __redisBlockForReply(c);
+}
--- /dev/null
+/*
+ * 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:
+ *
+ * * 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 */
+#include <sys/time.h> /* for struct timeval */
+
+#define HIREDIS_MAJOR 0
+#define HIREDIS_MINOR 10
+#define HIREDIS_PATCH 1
+
+#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 /* 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. */
+#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
+
+/* Flag specific to the async API which means that the context should be clean
+ * up as soon as possible. */
+#define REDIS_FREEING 0x8
+
+/* Flag that is set when an async callback is executed. */
+#define REDIS_IN_CALLBACK 0x10
+
+/* Flag that is set when the async context has one or more subscriptions. */
+#define REDIS_SUBSCRIBED 0x20
+
+#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
+#define REDIS_REPLY_ERROR 6
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 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 */
+ int idx; /* index in parent (array) object */
+ void *obj; /* holds user-generated value for a read task */
+ struct redisReadTask *parent; /* parent task */
+ void *privdata; /* user-settable arbitrary field */
+} 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;
+
+/* State for the protocol parser */
+typedef struct redisReader {
+ int err; /* Error flags, 0 when there is no error */
+ char errstr[128]; /* String representation of error when applicable */
+
+ char *buf; /* Read buffer */
+ size_t pos; /* Buffer cursor */
+ size_t len; /* Buffer length */
+
+ redisReadTask rstack[3];
+ int ridx; /* Index of current read task */
+ void *reply; /* Temporary reply pointer */
+
+ redisReplyObjectFunctions *fn;
+ void *privdata;
+} redisReader;
+
+/* Public API for the protocol parser. */
+redisReader *redisReaderCreate(void);
+void redisReaderFree(redisReader *r);
+int redisReaderFeed(redisReader *r, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *r, void **reply);
+
+/* Backwards compatibility, can be removed on big version bump. */
+#define redisReplyReaderCreate redisReaderCreate
+#define redisReplyReaderFree redisReaderFree
+#define redisReplyReaderFeed redisReaderFeed
+#define redisReplyReaderGetReply redisReaderGetReply
+#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
+
+/* Function to free the reply objects hiredis returns by default. */
+void freeReplyObject(void *reply);
+
+/* 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);
+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);
+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. */
+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
+ * 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /dev/null
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * 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.
+ *
+ * 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/select.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 "net.h"
+#include "sds.h"
+
+/* Defined in hiredis.c */
+void __redisSetError(redisContext *c, int type, const char *str);
+
+static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+ char buf[128];
+ size_t len = 0;
+
+ if (prefix != NULL)
+ len = snprintf(buf,sizeof(buf),"%s: ",prefix);
+ strerror_r(errno,buf+len,sizeof(buf)-len);
+ __redisSetError(c,type,buf);
+}
+
+static int redisCreateSocket(redisContext *c, int type) {
+ int s, on = 1;
+ if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ return REDIS_ERR;
+ }
+ if (type == AF_INET) {
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ close(s);
+ return REDIS_ERR;
+ }
+ }
+ return s;
+}
+
+static int redisSetBlocking(redisContext *c, int fd, int blocking) {
+ int flags;
+
+ /* Set the socket nonblocking.
+ * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+ * interrupted by a signal. */
+ if ((flags = fcntl(fd, F_GETFL)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
+ close(fd);
+ return REDIS_ERR;
+ }
+
+ if (blocking)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
+ 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) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
+ close(fd);
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
+static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {
+ struct timeval to;
+ struct timeval *toptr = NULL;
+ fd_set wfd;
+ int err;
+ socklen_t errlen;
+
+ /* Only use timeout when not NULL. */
+ if (timeout != NULL) {
+ to = *timeout;
+ toptr = &to;
+ }
+
+ if (errno == EINPROGRESS) {
+ FD_ZERO(&wfd);
+ FD_SET(fd, &wfd);
+
+ if (select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"select(2)");
+ close(fd);
+ return REDIS_ERR;
+ }
+
+ if (!FD_ISSET(fd, &wfd)) {
+ errno = ETIMEDOUT;
+ __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) {
+ __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;
+ }
+
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+ close(fd);
+ return REDIS_ERR;
+}
+
+int redisContextSetTimeout(redisContext *c, struct timeval tv) {
+ if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+ __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
+ return REDIS_ERR;
+ }
+ if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+ __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 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;
+
+ he = gethostbyname(addr);
+ if (he == NULL) {
+ char buf[128];
+ snprintf(buf,sizeof(buf),"Can't resolve: %s", addr);
+ __redisSetError(c,REDIS_ERR_OTHER,buf);
+ 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 {
+ if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+ return REDIS_ERR;
+ }
+ }
+
+ /* Reset socket to be blocking after connect(2). */
+ if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
+ return REDIS_ERR;
+
+ if (redisSetTcpNoDelay(c,s) != REDIS_OK)
+ return REDIS_ERR;
+
+ c->fd = s;
+ c->flags |= REDIS_CONNECTED;
+ return REDIS_OK;
+}
+
+int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) {
+ int s;
+ int blocking = (c->flags & REDIS_BLOCK);
+ struct sockaddr_un sa;
+
+ if ((s = redisCreateSocket(c,AF_LOCAL)) < 0)
+ return REDIS_ERR;
+ if (redisSetBlocking(c,s,0) != REDIS_OK)
+ return REDIS_ERR;
+
+ sa.sun_family = AF_LOCAL;
+ strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
+ if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ if (errno == EINPROGRESS && !blocking) {
+ /* This is ok. */
+ } else {
+ if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+ return REDIS_ERR;
+ }
+ }
+
+ /* Reset socket to be blocking after connect(2). */
+ if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
+ return REDIS_ERR;
+
+ c->fd = s;
+ c->flags |= REDIS_CONNECTED;
+ return REDIS_OK;
+}
--- /dev/null
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * 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.
+ *
+ * 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
+
+#include "hiredis.h"
+
+#if defined(__sun)
+#define AF_LOCAL AF_UNIX
+#endif
+
+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);
+
+#endif
--- /dev/null
+/* 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.
+ */
+
+#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;
+
+ 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);
+}
+
+sds sdsdup(const sds s) {
+ return sdsnewlen(s, sdslen(s));
+}
+
+void sdsfree(sds s) {
+ if (s == NULL) return;
+ free(s-sizeof(struct sdshdr));
+}
+
+void sdsupdatelen(sds s) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ int reallen = strlen(s);
+ sh->free += (sh->len-reallen);
+ sh->len = reallen;
+}
+
+static sds sdsMakeRoomFor(sds s, size_t addlen) {
+ struct sdshdr *sh, *newsh;
+ size_t free = sdsavail(s);
+ size_t len, newlen;
+
+ if (free >= addlen) return s;
+ len = sdslen(s);
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ newlen = (len+addlen)*2;
+ newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
+#ifdef SDS_ABORT_ON_OOM
+ if (newsh == NULL) sdsOomAbort();
+#else
+ if (newsh == NULL) return NULL;
+#endif
+
+ newsh->free = newlen - len;
+ return newsh->buf;
+}
+
+/* Grow the sds to have the specified length. Bytes that were not part of
+ * the original length of the sds will be set to zero. */
+sds sdsgrowzero(sds s, size_t len) {
+ struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+ size_t totlen, curlen = sh->len;
+
+ if (len <= curlen) return s;
+ s = sdsMakeRoomFor(s,len-curlen);
+ if (s == NULL) return NULL;
+
+ /* Make sure added region doesn't contain garbage */
+ sh = (void*)(s-(sizeof(struct sdshdr)));
+ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
+ totlen = sh->len+sh->free;
+ sh->len = len;
+ sh->free = totlen-sh->len;
+ return s;
+}
+
+sds sdscatlen(sds s, const void *t, size_t len) {
+ struct sdshdr *sh;
+ size_t curlen = sdslen(s);
+
+ s = sdsMakeRoomFor(s,len);
+ if (s == NULL) return NULL;
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ memcpy(s+curlen, t, len);
+ sh->len = curlen+len;
+ sh->free = sh->free-len;
+ s[curlen+len] = '\0';
+ return s;
+}
+
+sds sdscat(sds s, const char *t) {
+ return sdscatlen(s, t, strlen(t));
+}
+
+sds sdscpylen(sds s, char *t, size_t len) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ size_t totlen = sh->free+sh->len;
+
+ if (totlen < len) {
+ s = sdsMakeRoomFor(s,len-sh->len);
+ if (s == NULL) return NULL;
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ totlen = sh->free+sh->len;
+ }
+ memcpy(s, t, len);
+ s[len] = '\0';
+ sh->len = len;
+ sh->free = totlen-len;
+ return s;
+}
+
+sds sdscpy(sds s, char *t) {
+ return sdscpylen(s, t, strlen(t));
+}
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
+ va_list cpy;
+ char *buf, *t;
+ size_t buflen = 16;
+
+ while(1) {
+ buf = malloc(buflen);
+#ifdef SDS_ABORT_ON_OOM
+ if (buf == NULL) sdsOomAbort();
+#else
+ if (buf == NULL) return NULL;
+#endif
+ buf[buflen-2] = '\0';
+ va_copy(cpy,ap);
+ vsnprintf(buf, buflen, fmt, cpy);
+ if (buf[buflen-2] != '\0') {
+ free(buf);
+ buflen *= 2;
+ continue;
+ }
+ break;
+ }
+ t = sdscat(s, buf);
+ free(buf);
+ return t;
+}
+
+sds sdscatprintf(sds s, const char *fmt, ...) {
+ va_list ap;
+ char *t;
+ va_start(ap, fmt);
+ t = sdscatvprintf(s,fmt,ap);
+ va_end(ap);
+ return t;
+}
+
+sds sdstrim(sds s, const char *cset) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ char *start, *end, *sp, *ep;
+ size_t len;
+
+ sp = start = s;
+ ep = end = s+sdslen(s)-1;
+ while(sp <= end && strchr(cset, *sp)) sp++;
+ while(ep > start && strchr(cset, *ep)) ep--;
+ len = (sp > ep) ? 0 : ((ep-sp)+1);
+ if (sh->buf != sp) memmove(sh->buf, sp, len);
+ sh->buf[len] = '\0';
+ sh->free = sh->free+(sh->len-len);
+ sh->len = len;
+ return s;
+}
+
+sds sdsrange(sds s, int start, int end) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ size_t newlen, len = sdslen(s);
+
+ if (len == 0) return s;
+ if (start < 0) {
+ start = len+start;
+ if (start < 0) start = 0;
+ }
+ if (end < 0) {
+ end = len+end;
+ if (end < 0) end = 0;
+ }
+ newlen = (start > end) ? 0 : (end-start)+1;
+ if (newlen != 0) {
+ if (start >= (signed)len) {
+ newlen = 0;
+ } else if (end >= (signed)len) {
+ end = len-1;
+ newlen = (start > end) ? 0 : (end-start)+1;
+ }
+ } else {
+ start = 0;
+ }
+ if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
+ sh->buf[newlen] = 0;
+ sh->free = sh->free+(sh->len-newlen);
+ sh->len = newlen;
+ return s;
+}
+
+void sdstolower(sds s) {
+ int len = sdslen(s), j;
+
+ for (j = 0; j < len; j++) s[j] = tolower(s[j]);
+}
+
+void sdstoupper(sds s) {
+ int len = sdslen(s), j;
+
+ for (j = 0; j < len; j++) s[j] = toupper(s[j]);
+}
+
+int sdscmp(sds s1, sds s2) {
+ size_t l1, l2, minlen;
+ int cmp;
+
+ l1 = sdslen(s1);
+ l2 = sdslen(s2);
+ minlen = (l1 < l2) ? l1 : l2;
+ cmp = memcmp(s1,s2,minlen);
+ if (cmp == 0) return l1-l2;
+ return cmp;
+}
+
+/* Split 's' with separator in 'sep'. An array
+ * of sds strings is returned. *count will be set
+ * by reference to the number of tokens returned.
+ *
+ * On out of memory, zero length string, zero length
+ * separator, NULL is returned.
+ *
+ * Note that 'sep' is able to split a string using
+ * a multi-character separator. For example
+ * sdssplit("foo_-_bar","_-_"); will return two
+ * elements "foo" and "bar".
+ *
+ * This version of the function is binary-safe but
+ * requires length arguments. sdssplit() is just the
+ * same function but for zero-terminated strings.
+ */
+sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
+ int elements = 0, slots = 5, start = 0, j;
+
+ sds *tokens = malloc(sizeof(sds)*slots);
+#ifdef SDS_ABORT_ON_OOM
+ if (tokens == NULL) sdsOomAbort();
+#endif
+ if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
+ if (len == 0) {
+ *count = 0;
+ return tokens;
+ }
+ for (j = 0; j < (len-(seplen-1)); j++) {
+ /* make sure there is room for the next element and the final one */
+ if (slots < elements+2) {
+ sds *newtokens;
+
+ slots *= 2;
+ newtokens = realloc(tokens,sizeof(sds)*slots);
+ if (newtokens == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+ sdsOomAbort();
+#else
+ goto cleanup;
+#endif
+ }
+ tokens = newtokens;
+ }
+ /* search the separator */
+ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
+ tokens[elements] = sdsnewlen(s+start,j-start);
+ if (tokens[elements] == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+ sdsOomAbort();
+#else
+ goto cleanup;
+#endif
+ }
+ elements++;
+ start = j+seplen;
+ j = j+seplen-1; /* skip the separator */
+ }
+ }
+ /* Add the final element. We are sure there is room in the tokens array. */
+ tokens[elements] = sdsnewlen(s+start,len-start);
+ if (tokens[elements] == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+ sdsOomAbort();
+#else
+ goto cleanup;
+#endif
+ }
+ elements++;
+ *count = elements;
+ return tokens;
+
+#ifndef SDS_ABORT_ON_OOM
+cleanup:
+ {
+ int i;
+ for (i = 0; i < elements; i++) sdsfree(tokens[i]);
+ free(tokens);
+ return NULL;
+ }
+#endif
+}
+
+void sdsfreesplitres(sds *tokens, int count) {
+ if (!tokens) return;
+ while(count--)
+ sdsfree(tokens[count]);
+ free(tokens);
+}
+
+sds sdsfromlonglong(long long value) {
+ char buf[32], *p;
+ unsigned long long v;
+
+ v = (value < 0) ? -value : value;
+ p = buf+31; /* point to the last character */
+ do {
+ *p-- = '0'+(v%10);
+ v /= 10;
+ } while(v);
+ if (value < 0) *p-- = '-';
+ p++;
+ return sdsnewlen(p,32-(p-buf));
+}
+
+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",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);
+ else
+ s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
+ break;
+ }
+ p++;
+ if (s == NULL) return NULL;
+ }
+ return sdscatlen(s,"\"",1);
+}
+
+/* Split a line into arguments, where every argument can be in the
+ * following programming-language REPL-alike form:
+ *
+ * foo bar "newline are supported\n" and "\xff\x00otherstuff"
+ *
+ * The number of arguments is stored into *argc, and an array
+ * of sds is returned. The caller should sdsfree() all the returned
+ * strings and finally free() the array itself.
+ *
+ * Note that sdscatrepr() is able to convert back a string into
+ * a quoted string in the same format sdssplitargs() is able to parse.
+ */
+sds *sdssplitargs(char *line, int *argc) {
+ char *p = line;
+ char *current = NULL;
+ char **vector = NULL, **_vector = NULL;
+
+ *argc = 0;
+ while(1) {
+ /* skip blanks */
+ while(*p && isspace(*p)) p++;
+ if (*p) {
+ /* get a token */
+ int inq=0; /* set to 1 if we are in "quotes" */
+ int done=0;
+
+ if (current == NULL) {
+ current = sdsempty();
+ if (current == NULL) goto err;
+ }
+
+ while(!done) {
+ if (inq) {
+ if (*p == '\\' && *(p+1)) {
+ char c;
+
+ p++;
+ switch(*p) {
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'b': c = '\b'; break;
+ case 'a': c = '\a'; break;
+ default: c = *p; break;
+ }
+ current = sdscatlen(current,&c,1);
+ } else if (*p == '"') {
+ /* closing quote must be followed by a space */
+ if (*(p+1) && !isspace(*(p+1))) goto err;
+ done=1;
+ } else if (!*p) {
+ /* unterminated quotes */
+ goto err;
+ } else {
+ current = sdscatlen(current,p,1);
+ }
+ } else {
+ switch(*p) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\0':
+ done=1;
+ break;
+ case '"':
+ inq=1;
+ break;
+ default:
+ current = sdscatlen(current,p,1);
+ break;
+ }
+ }
+ if (*p) p++;
+ if (current == NULL) goto err;
+ }
+ /* add the token to the vector */
+ _vector = realloc(vector,((*argc)+1)*sizeof(char*));
+ if (_vector == NULL) goto err;
+
+ vector = _vector;
+ vector[*argc] = current;
+ (*argc)++;
+ current = NULL;
+ } else {
+ return vector;
+ }
+ }
+
+err:
+ while((*argc)--)
+ sdsfree(vector[*argc]);
+ if (vector != NULL) free(vector);
+ if (current != NULL) sdsfree(current);
+ return NULL;
+}
+
+#ifdef SDS_TEST_MAIN
+#include <stdio.h>
+
+int __failed_tests = 0;
+int __test_num = 0;
+#define test_cond(descr,_c) do { \
+ __test_num++; printf("%d - %s: ", __test_num, descr); \
+ if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
+} while(0);
+#define test_report() do { \
+ printf("%d tests, %d passed, %d failed\n", __test_num, \
+ __test_num-__failed_tests, __failed_tests); \
+ if (__failed_tests) { \
+ printf("=== WARNING === We have failed tests here...\n"); \
+ } \
+} while(0);
+
+int main(void) {
+ {
+ sds x = sdsnew("foo"), y;
+
+ test_cond("Create a string and obtain the length",
+ sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
+
+ sdsfree(x);
+ x = sdsnewlen("foo",2);
+ test_cond("Create a string with specified length",
+ sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
+
+ x = sdscat(x,"bar");
+ test_cond("Strings concatenation",
+ sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
+
+ x = sdscpy(x,"a");
+ test_cond("sdscpy() against an originally longer string",
+ sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
+
+ x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
+ test_cond("sdscpy() against an originally shorter string",
+ sdslen(x) == 33 &&
+ memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
+
+ sdsfree(x);
+ x = sdscatprintf(sdsempty(),"%d",123);
+ test_cond("sdscatprintf() seems working in the base case",
+ sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
+
+ sdsfree(x);
+ x = sdstrim(sdsnew("xxciaoyyy"),"xy");
+ test_cond("sdstrim() correctly trims characters",
+ sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
+
+ y = sdsrange(sdsdup(x),1,1);
+ test_cond("sdsrange(...,1,1)",
+ sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
+
+ sdsfree(y);
+ y = sdsrange(sdsdup(x),1,-1);
+ test_cond("sdsrange(...,1,-1)",
+ sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+ sdsfree(y);
+ y = sdsrange(sdsdup(x),-2,-1);
+ test_cond("sdsrange(...,-2,-1)",
+ sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
+
+ sdsfree(y);
+ y = sdsrange(sdsdup(x),2,1);
+ test_cond("sdsrange(...,2,1)",
+ sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+ sdsfree(y);
+ y = sdsrange(sdsdup(x),1,100);
+ test_cond("sdsrange(...,1,100)",
+ sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+ sdsfree(y);
+ y = sdsrange(sdsdup(x),100,100);
+ test_cond("sdsrange(...,100,100)",
+ sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnew("foo");
+ y = sdsnew("foa");
+ test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnew("bar");
+ y = sdsnew("bar");
+ test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
+
+ sdsfree(y);
+ sdsfree(x);
+ x = sdsnew("aar");
+ y = sdsnew("bar");
+ test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
+ }
+ test_report()
+}
+#endif
--- /dev/null
+/* 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.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#include <sys/types.h>
+#include <stdarg.h>
+
+typedef char *sds;
+
+struct sdshdr {
+ int len;
+ int free;
+ char buf[];
+};
+
+static inline size_t sdslen(const sds s) {
+ struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+ return sh->len;
+}
+
+static inline size_t sdsavail(const sds s) {
+ struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+ return sh->free;
+}
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty(void);
+size_t sdslen(const sds s);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+size_t sdsavail(sds s);
+sds sdsgrowzero(sds s, size_t len);
+sds sdscatlen(sds s, const void *t, size_t len);
+sds sdscat(sds s, const char *t);
+sds sdscpylen(sds s, char *t, size_t len);
+sds sdscpy(sds s, char *t);
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdstrim(sds s, const char *cset);
+sds sdsrange(sds s, int start, int end);
+void sdsupdatelen(sds s);
+int sdscmp(sds s1, sds s2);
+sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
+void sdsfreesplitres(sds *tokens, int count);
+void sdstolower(sds s);
+void sdstoupper(sds s);
+sds sdsfromlonglong(long long value);
+sds sdscatrepr(sds s, char *p, size_t len);
+sds *sdssplitargs(char *line, int *argc);
+
+#endif
--- /dev/null
+#include "fmacros.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#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++;}
+
+static long long usec(void) {
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
+}
+
+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) {
+ char *cmd;
+ int len;
+
+ test("Format command without interpolation: ");
+ len = redisFormatCommand(&cmd,"SET foo bar");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ free(cmd);
+
+ test("Format command with %%s string interpolation: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ free(cmd);
+
+ test("Format command with %%s and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ free(cmd);
+
+ test("Format command with an empty string in between proper interpolations: ");
+ len = redisFormatCommand(&cmd,"SET %s %s","","foo");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(0+2)+4+(3+2));
+ free(cmd);
+
+ test("Format command with %%b string interpolation: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ free(cmd);
+
+ test("Format command with %%b and an empty string: ");
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+ free(cmd);
+
+ test("Format command with literal %%: ");
+ len = redisFormatCommand(&cmd,"SET %% %%");
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
+ 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);
+
+ const char *argv[3];
+ argv[0] = "SET";
+ argv[1] = "foo\0xxx";
+ argv[2] = "bar";
+ size_t lens[3] = { 3, 7, 3 };
+ int argc = 3;
+
+ test("Format command by passing argc/argv without lengths: ");
+ len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+ free(cmd);
+
+ test("Format command by passing argc/argv with lengths: ");
+ len = redisFormatCommandArgv(&cmd,argc,argv,lens);
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+ free(cmd);
+}
+
+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 > 1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
+ redisReaderFeed(reader,(char*)"*1\r\n",4);
+ ret = redisReaderGetReply(reader,NULL);
+ test_cond(ret == REDIS_ERR &&
+ strncasecmp(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;
+
+ 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);
+ redisFree(c);
+
+ test("Returns error when the port is not open: ");
+ c = redisConnect((char*)"localhost", 1);
+ test_cond(c->err == REDIS_ERR_IO &&
+ strcmp(c->errstr,"Connection refused") == 0);
+ redisFree(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);
+
+ test("Is a able to send commands verbatim: ");
+ reply = redisCommand(c,"SET foo bar");
+ test_cond (reply->type == REDIS_REPLY_STATUS &&
+ strcasecmp(reply->str,"ok") == 0)
+ freeReplyObject(reply);
+
+ test("%%s String interpolation works: ");
+ reply = redisCommand(c,"SET %s %s","foo","hello world");
+ freeReplyObject(reply);
+ reply = redisCommand(c,"GET foo");
+ test_cond(reply->type == REDIS_REPLY_STRING &&
+ strcmp(reply->str,"hello world") == 0);
+ freeReplyObject(reply);
+
+ test("%%b String interpolation works: ");
+ reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
+ freeReplyObject(reply);
+ reply = redisCommand(c,"GET foo");
+ test_cond(reply->type == REDIS_REPLY_STRING &&
+ memcmp(reply->str,"hello\x00world",11) == 0)
+
+ test("Binary reply length is correct: ");
+ test_cond(reply->len == 11)
+ freeReplyObject(reply);
+
+ test("Can parse nil replies: ");
+ reply = redisCommand(c,"GET nokey");
+ test_cond(reply->type == REDIS_REPLY_NIL)
+ freeReplyObject(reply);
+
+ /* test 7 */
+ test("Can parse integer replies: ");
+ reply = redisCommand(c,"INCR mycounter");
+ test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
+ freeReplyObject(reply);
+
+ test("Can parse multi bulk replies: ");
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+ freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 2 &&
+ !memcmp(reply->element[0]->str,"bar",3) &&
+ !memcmp(reply->element[1]->str,"foo",3))
+ freeReplyObject(reply);
+
+ /* m/e with multi bulk reply *before* other reply.
+ * specifically test ordering of reply items to parse. */
+ test("Can handle nested multi bulk replies: ");
+ freeReplyObject(redisCommand(c,"MULTI"));
+ freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
+ freeReplyObject(redisCommand(c,"PING"));
+ reply = (redisCommand(c,"EXEC"));
+ test_cond(reply->type == REDIS_REPLY_ARRAY &&
+ reply->elements == 2 &&
+ reply->element[0]->type == REDIS_REPLY_ARRAY &&
+ reply->element[0]->elements == 2 &&
+ !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
+ !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
+ reply->element[1]->type == REDIS_REPLY_STATUS &&
+ 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:";
+ char *p, *eptr;
+
+ reply = redisCommand(c,"INFO");
+ p = strstr(reply->str,field);
+ major = strtol(p+strlen(field),&eptr,10);
+ p = eptr+1; /* char next to the first "." */
+ minor = strtol(p,&eptr,10);
+ freeReplyObject(reply);
+ }
+
+ test("Returns I/O error when the connection is lost: ");
+ reply = redisCommand(c,"QUIT");
+ if (major >= 2 && minor > 0) {
+ /* > 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,&_reply) == REDIS_ERR);
+ freeReplyObject(reply);
+ } else {
+ test_cond(reply == NULL);
+ }
+
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
+ * the read(2) for the reply on QUIT will set the error to EOF.
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
+ * issued to find out the socket was closed by the server. In both
+ * conditions, the error will be set to EOF. */
+ assert(c->err == REDIS_ERR_EOF &&
+ strcmp(c->errstr,"Server closed the connection") == 0);
+ redisFree(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,&_reply) == REDIS_ERR &&
+ c->err == REDIS_ERR_IO && errno == EAGAIN);
+ redisFree(c);
+}
+
+static void test_throughput(struct config config) {
+ redisContext *c = connect(config);
+ redisReply **replies;
+ int i, num;
+ long long t1, t2;
+
+ test("Throughput:\n");
+ for (i = 0; i < 500; i++)
+ freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+
+ num = 1000;
+ replies = malloc(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c,"PING");
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ free(replies);
+ printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = malloc(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c,"LRANGE mylist 0 499");
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+ assert(replies[i] != NULL && replies[i]->elements == 500);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ free(replies);
+ printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ num = 10000;
+ replies = malloc(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"PING");
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ free(replies);
+ printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ replies = malloc(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"LRANGE mylist 0 499");
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+ assert(replies[i] != NULL && replies[i]->elements == 500);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ free(replies);
+ printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+ disconnect(c);
+}
+
+// static long __test_callback_flags = 0;
+// static void __test_callback(redisContext *c, void *privdata) {
+// ((void)c);
+// /* Shift to detect execution order */
+// __test_callback_flags <<= 8;
+// __test_callback_flags |= (long)privdata;
+// }
+//
+// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
+// ((void)c);
+// /* Shift to detect execution order */
+// __test_callback_flags <<= 8;
+// __test_callback_flags |= (long)privdata;
+// if (reply) freeReplyObject(reply);
+// }
+//
+// static redisContext *__connect_nonblock() {
+// /* Reset callback flags */
+// __test_callback_flags = 0;
+// return redisConnectNonBlock("127.0.0.1", port, NULL);
+// }
+//
+// static void test_nonblocking_connection() {
+// redisContext *c;
+// int wdone = 0;
+//
+// test("Calls command callback when command is issued: ");
+// c = __connect_nonblock();
+// redisSetCommandCallback(c,__test_callback,(void*)1);
+// redisCommand(c,"PING");
+// test_cond(__test_callback_flags == 1);
+// redisFree(c);
+//
+// test("Calls disconnect callback on redisDisconnect: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)2);
+// redisDisconnect(c);
+// test_cond(__test_callback_flags == 2);
+// redisFree(c);
+//
+// test("Calls disconnect callback and free callback on redisFree: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)2);
+// redisSetFreeCallback(c,__test_callback,(void*)4);
+// redisFree(c);
+// test_cond(__test_callback_flags == ((2 << 8) | 4));
+//
+// test("redisBufferWrite against empty write buffer: ");
+// c = __connect_nonblock();
+// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
+// redisFree(c);
+//
+// test("redisBufferWrite against not yet connected fd: ");
+// c = __connect_nonblock();
+// redisCommand(c,"PING");
+// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+// strncmp(c->error,"write:",6) == 0);
+// redisFree(c);
+//
+// test("redisBufferWrite against closed fd: ");
+// c = __connect_nonblock();
+// redisCommand(c,"PING");
+// redisDisconnect(c);
+// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+// strncmp(c->error,"write:",6) == 0);
+// redisFree(c);
+//
+// test("Process callbacks in the right sequence: ");
+// c = __connect_nonblock();
+// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
+// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
+//
+// /* Write output buffer */
+// wdone = 0;
+// while(!wdone) {
+// usleep(500);
+// redisBufferWrite(c,&wdone);
+// }
+//
+// /* Read until at least one callback is executed (the 3 replies will
+// * arrive in a single packet, causing all callbacks to be executed in
+// * a single pass). */
+// while(__test_callback_flags == 0) {
+// assert(redisBufferRead(c) == REDIS_OK);
+// redisProcessCallbacks(c);
+// }
+// test_cond(__test_callback_flags == 0x010203);
+// redisFree(c);
+//
+// test("redisDisconnect executes pending callbacks with NULL reply: ");
+// c = __connect_nonblock();
+// redisSetDisconnectCallback(c,__test_callback,(void*)1);
+// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+// redisDisconnect(c);
+// test_cond(__test_callback_flags == 0x0201);
+// redisFree(c);
+// }
+
+int main(int argc, char **argv) {
+ 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--;
+ }
+
+ test_format_commands();
+ test_reply_reader();
+ test_blocking_connection_errors();
+
+ 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;
+}