@@ -666,10 +666,10 @@ IF(PCRE_LIBRARY) | |||
ELSE(PCRE_LIBRARY) | |||
SET(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};-lpcre") | |||
ENDIF(PCRE_LIBRARY) | |||
# Libhiredis pc file is so special | |||
IF(ENABLE_HIREDIS MATCHES "ON") | |||
ProcessPackage(HIREDIS LIBRARY hiredis INCLUDE hiredis.h INCLUDE_SUFFIXES include/hiredis | |||
ROOT ${HIREDIS_ROOT_DIR} MODULES hiredis libhiredis) | |||
ADD_SUBDIRECTORY(contrib/hiredis) | |||
INCLUDE_DIRECTORIES(BEFORE "${CMAKE_SOURCE_DIR}/contrib/hiredis") | |||
ENDIF(ENABLE_HIREDIS MATCHES "ON") | |||
@@ -0,0 +1,15 @@ | |||
SET(HIREDISSRC async.c | |||
dict.c | |||
hiredis.c | |||
net.c | |||
read.c | |||
sds.c) | |||
SET(HIREDIS_CFLAGS "") | |||
IF("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") | |||
SET(HIREDIS_CFLAGS "${HIREDIS_CFLAGS} -O3") | |||
ENDIF() | |||
ADD_LIBRARY(rspamd-hiredis STATIC "${HIREDISSRC}") | |||
SET_TARGET_PROPERTIES(rspamd-hiredis PROPERTIES COMPILE_FLAGS "${HIREDIS_CFLAGS}") |
@@ -0,0 +1,29 @@ | |||
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. |
@@ -0,0 +1,392 @@ | |||
[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) | |||
# 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 a 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: | |||
```c | |||
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 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: | |||
```c | |||
redisContext *c = redisConnect("127.0.0.1", 6379); | |||
if (c != NULL && 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: | |||
```c | |||
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: | |||
```c | |||
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: | |||
```c | |||
reply = redisCommand(context, "SET foo %b", value, (size_t) 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: | |||
```c | |||
reply = redisCommand(context, "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-reply 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) frees 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: | |||
```c | |||
void redisFree(redisContext *c); | |||
``` | |||
This function immediately closes the socket and then frees 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: | |||
```c | |||
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: | |||
```c | |||
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)`): | |||
```c | |||
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: | |||
```c | |||
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. | |||
```c | |||
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: | |||
```c | |||
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 freed 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: | |||
```c | |||
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: | |||
```c | |||
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: | |||
```c | |||
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 freed. When the callback | |||
for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only | |||
valid for the duration of the callback. | |||
All pending callbacks are called with a `NULL` reply when the context encountered an error. | |||
### Disconnecting | |||
An asynchronous connection can be terminated using: | |||
```c | |||
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 freed. | |||
### 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: | |||
```c | |||
redisReader *redisReaderCreate(void); | |||
void redisReaderFree(redisReader *reader); | |||
int redisReaderFeed(redisReader *reader, const char *buf, size_t len); | |||
int redisReaderGetReply(redisReader *reader, void **reply); | |||
``` | |||
The same set of functions are used internally by hiredis when creating a | |||
normal Redis context, the above API just exposes it to the user for a direct | |||
usage. | |||
### 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). | |||
The parser limits the level of nesting for multi bulk payloads to 7. If the | |||
multi bulk nesting level is higher than this, the parser returns an 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. | |||
### Reader max buffer | |||
Both when using the Reader API directly or when using it indirectly via a | |||
normal Redis context, the redisReader structure uses a buffer in order to | |||
accumulate data from the server. | |||
Usually this buffer is destroyed when it is empty and is larger than 16 | |||
KiB in order to avoid wasting memory in unused buffers | |||
However when working with very big payloads destroying the buffer may slow | |||
down performances considerably, so it is possible to modify the max size of | |||
an idle buffer changing the value of the `maxbuf` field of the reader structure | |||
to the desired value. The special value of 0 means that there is no maximum | |||
value for an idle buffer, so the buffer will never get freed. | |||
For instance if you have a normal Redis context you can set the maximum idle | |||
buffer to zero (unlimited) just with: | |||
```c | |||
context->reader->maxbuf = 0; | |||
``` | |||
This should be done only in order to maximize performances when working with | |||
large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again | |||
as soon as possible in order to prevent allocation of useless memory. | |||
## AUTHORS | |||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and | |||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. | |||
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and | |||
Jan-Erik Rediger (janerik at fnordig dot com) |
@@ -0,0 +1,108 @@ | |||
/* | |||
* 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_LIBEVENT_H__ | |||
#define __HIREDIS_LIBEVENT_H__ | |||
#include <event.h> | |||
#include "../hiredis.h" | |||
#include "../async.h" | |||
typedef struct redisLibeventEvents { | |||
redisAsyncContext *context; | |||
struct event rev, wev; | |||
} redisLibeventEvents; | |||
static void redisLibeventReadEvent(int fd, short event, void *arg) { | |||
((void)fd); ((void)event); | |||
redisLibeventEvents *e = (redisLibeventEvents*)arg; | |||
redisAsyncHandleRead(e->context); | |||
} | |||
static void redisLibeventWriteEvent(int fd, short event, void *arg) { | |||
((void)fd); ((void)event); | |||
redisLibeventEvents *e = (redisLibeventEvents*)arg; | |||
redisAsyncHandleWrite(e->context); | |||
} | |||
static void redisLibeventAddRead(void *privdata) { | |||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; | |||
event_add(&e->rev,NULL); | |||
} | |||
static void redisLibeventDelRead(void *privdata) { | |||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; | |||
event_del(&e->rev); | |||
} | |||
static void redisLibeventAddWrite(void *privdata) { | |||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; | |||
event_add(&e->wev,NULL); | |||
} | |||
static void redisLibeventDelWrite(void *privdata) { | |||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; | |||
event_del(&e->wev); | |||
} | |||
static void redisLibeventCleanup(void *privdata) { | |||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; | |||
event_del(&e->rev); | |||
event_del(&e->wev); | |||
free(e); | |||
} | |||
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { | |||
redisContext *c = &(ac->c); | |||
redisLibeventEvents *e; | |||
/* Nothing should be attached when something is already attached */ | |||
if (ac->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; | |||
} | |||
#endif |
@@ -0,0 +1,687 @@ | |||
/* | |||
* 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 <stdlib.h> | |||
#include <string.h> | |||
#include <strings.h> | |||
#include <assert.h> | |||
#include <ctype.h> | |||
#include <errno.h> | |||
#include "async.h" | |||
#include "net.h" | |||
#include "dict.c" | |||
#include "sds.h" | |||
#define _EL_ADD_READ(ctx) do { \ | |||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ | |||
} while(0) | |||
#define _EL_DEL_READ(ctx) do { \ | |||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ | |||
} while(0) | |||
#define _EL_ADD_WRITE(ctx) do { \ | |||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ | |||
} while(0) | |||
#define _EL_DEL_WRITE(ctx) do { \ | |||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ | |||
} while(0) | |||
#define _EL_CLEANUP(ctx) do { \ | |||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ | |||
} while(0); | |||
/* Forward declaration of function in hiredis.c */ | |||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); | |||
/* Functions managing dictionary of callbacks for pub/sub. */ | |||
static unsigned int callbackHash(const void *key) { | |||
return dictGenHashFunction((const unsigned char *)key, | |||
sdslen((const sds)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((const sds)key1); | |||
l2 = sdslen((const 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; | |||
ac = realloc(c,sizeof(redisAsyncContext)); | |||
if (ac == NULL) | |||
return NULL; | |||
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) { | |||
if (!ac) | |||
return; | |||
redisContext *c = &(ac->c); | |||
ac->err = c->err; | |||
ac->errstr = c->errstr; | |||
} | |||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { | |||
redisContext *c; | |||
redisAsyncContext *ac; | |||
c = redisConnectNonBlock(ip,port); | |||
if (c == NULL) | |||
return NULL; | |||
ac = redisAsyncInitialize(c); | |||
if (ac == NULL) { | |||
redisFree(c); | |||
return NULL; | |||
} | |||
__redisAsyncCopyError(ac); | |||
return ac; | |||
} | |||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, | |||
const char *source_addr) { | |||
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); | |||
redisAsyncContext *ac = redisAsyncInitialize(c); | |||
__redisAsyncCopyError(ac); | |||
return ac; | |||
} | |||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, | |||
const char *source_addr) { | |||
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); | |||
redisAsyncContext *ac = redisAsyncInitialize(c); | |||
__redisAsyncCopyError(ac); | |||
return ac; | |||
} | |||
redisAsyncContext *redisAsyncConnectUnix(const char *path) { | |||
redisContext *c; | |||
redisAsyncContext *ac; | |||
c = redisConnectUnixNonBlock(path); | |||
if (c == NULL) | |||
return NULL; | |||
ac = redisAsyncInitialize(c); | |||
if (ac == NULL) { | |||
redisFree(c); | |||
return NULL; | |||
} | |||
__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. */ | |||
_EL_ADD_WRITE(ac); | |||
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 (cb == NULL) | |||
return REDIS_ERR_OOM; | |||
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 */ | |||
_EL_CLEANUP(ac); | |||
/* 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 = {NULL, NULL, NULL}; | |||
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 | |||
&& ac->replies.head == NULL) { | |||
__redisAsyncDisconnect(ac); | |||
return; | |||
} | |||
/* If monitor mode, repush callback */ | |||
if(c->flags & REDIS_MONITORING) { | |||
__redisPushCallback(&ac->replies,&cb); | |||
} | |||
/* 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 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. | |||
* | |||
* Another possibility is that the server is loading its dataset. | |||
* In this case we also want to close the connection, and have the | |||
* user wait until the server is ready to take our request. | |||
*/ | |||
if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { | |||
c->err = REDIS_ERR_OTHER; | |||
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); | |||
c->reader->fn->freeObject(reply); | |||
__redisAsyncDisconnect(ac); | |||
return; | |||
} | |||
/* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ | |||
assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); | |||
if(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); | |||
} | |||
/* Internal helper function to detect socket status the first time a read or | |||
* write event fires. When connecting was not successful, the connect callback | |||
* is called with a REDIS_ERR status and the context is free'd. */ | |||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
if (redisCheckSocketError(c) == REDIS_ERR) { | |||
/* Try again later when connect(2) is still in progress. */ | |||
if (errno == EINPROGRESS) | |||
return REDIS_OK; | |||
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); | |||
__redisAsyncDisconnect(ac); | |||
return REDIS_ERR; | |||
} | |||
/* Mark context as connected. */ | |||
c->flags |= REDIS_CONNECTED; | |||
if (ac->onConnect) ac->onConnect(ac,REDIS_OK); | |||
return REDIS_OK; | |||
} | |||
/* This function should be called when the socket is readable. | |||
* It processes all replies that can be read and executes their callbacks. | |||
*/ | |||
void redisAsyncHandleRead(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
if (!(c->flags & REDIS_CONNECTED)) { | |||
/* Abort connect was not successful. */ | |||
if (__redisAsyncHandleConnect(ac) != REDIS_OK) | |||
return; | |||
/* Try again later when the context is still not connected. */ | |||
if (!(c->flags & REDIS_CONNECTED)) | |||
return; | |||
} | |||
if (redisBufferRead(c) == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
} else { | |||
/* Always re-schedule reads */ | |||
_EL_ADD_READ(ac); | |||
redisProcessCallbacks(ac); | |||
} | |||
} | |||
void redisAsyncHandleWrite(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
int done = 0; | |||
if (!(c->flags & REDIS_CONNECTED)) { | |||
/* Abort connect was not successful. */ | |||
if (__redisAsyncHandleConnect(ac) != REDIS_OK) | |||
return; | |||
/* Try again later when the context is still not connected. */ | |||
if (!(c->flags & REDIS_CONNECTED)) | |||
return; | |||
} | |||
if (redisBufferWrite(c,&done) == REDIS_ERR) { | |||
__redisAsyncDisconnect(ac); | |||
} else { | |||
/* Continue writing when not done, stop writing otherwise */ | |||
if (!done) | |||
_EL_ADD_WRITE(ac); | |||
else | |||
_EL_DEL_WRITE(ac); | |||
/* Always schedule reads after writes */ | |||
_EL_ADD_READ(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 const char *nextArgument(const char *start, const char **str, size_t *len) { | |||
const 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, const char *cmd, size_t len) { | |||
redisContext *c = &(ac->c); | |||
redisCallback cb; | |||
int pvariant, hasnext; | |||
const char *cstr, *astr; | |||
size_t clen, alen; | |||
const char *p; | |||
sds sname; | |||
int ret; | |||
/* 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) | |||
ret = dictReplace(ac->sub.patterns,sname,&cb); | |||
else | |||
ret = dictReplace(ac->sub.channels,sname,&cb); | |||
if (ret == 0) sdsfree(sname); | |||
} | |||
} 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(strncasecmp(cstr,"monitor\r\n",9) == 0) { | |||
/* Set monitor flag and push callback */ | |||
c->flags |= REDIS_MONITORING; | |||
__redisPushCallback(&ac->replies,&cb); | |||
} 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 */ | |||
_EL_ADD_WRITE(ac); | |||
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); | |||
/* We don't want to pass -1 or -2 to future functions as a length. */ | |||
if (len < 0) | |||
return REDIS_ERR; | |||
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) { | |||
sds cmd; | |||
int len; | |||
int status; | |||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); | |||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); | |||
sdsfree(cmd); | |||
return status; | |||
} | |||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { | |||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); | |||
return status; | |||
} |
@@ -0,0 +1,129 @@ | |||
/* | |||
* 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*, int status); | |||
/* Context for an async connection to Redis */ | |||
typedef struct redisAsyncContext { | |||
/* Hold the regular context, so it can be realloc'ed. */ | |||
redisContext c; | |||
/* Setup error flags so they can be used directly. */ | |||
int err; | |||
char *errstr; | |||
/* 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 *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); | |||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, | |||
const char *source_addr); | |||
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); | |||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); | |||
#ifdef __cplusplus | |||
} | |||
#endif | |||
#endif |
@@ -0,0 +1,338 @@ | |||
/* 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 succeed. */ | |||
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 initial 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; | |||
} | |||
@@ -0,0 +1,126 @@ | |||
/* 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 */ |
@@ -0,0 +1,21 @@ | |||
#ifndef __HIREDIS_FMACRO_H | |||
#define __HIREDIS_FMACRO_H | |||
#if defined(__linux__) | |||
#define _BSD_SOURCE | |||
#define _DEFAULT_SOURCE | |||
#endif | |||
#if defined(__sun__) | |||
#define _POSIX_C_SOURCE 200112L | |||
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) | |||
#define _XOPEN_SOURCE 600 | |||
#else | |||
#define _XOPEN_SOURCE | |||
#endif | |||
#if __APPLE__ && __MACH__ | |||
#define _OSX | |||
#endif | |||
#endif |
@@ -0,0 +1,223 @@ | |||
/* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, | |||
* Jan-Erik Rediger <janerik at fnordig 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 "read.h" | |||
#include <stdarg.h> /* for va_list */ | |||
#include <sys/time.h> /* for struct timeval */ | |||
#include <stdint.h> /* uintXX_t, etc */ | |||
#include "sds.h" /* for sds */ | |||
#define HIREDIS_MAJOR 0 | |||
#define HIREDIS_MINOR 13 | |||
#define HIREDIS_PATCH 3 | |||
#define HIREDIS_SONAME 0.13 | |||
/* 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 | |||
/* Flag that is set when monitor mode is active */ | |||
#define REDIS_MONITORING 0x40 | |||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ | |||
#define REDIS_REUSEADDR 0x80 | |||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ | |||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and | |||
* SO_REUSEADDR is being used. */ | |||
#define REDIS_CONNECT_RETRIES 10 | |||
/* strerror_r has two completely different prototypes and behaviors | |||
* depending on system issues, so we need to operate on the error buffer | |||
* differently depending on which strerror_r we're using. */ | |||
#ifndef _GNU_SOURCE | |||
/* "regular" POSIX strerror_r that does the right thing. */ | |||
#define __redis_strerror_r(errno, buf, len) \ | |||
do { \ | |||
strerror_r((errno), (buf), (len)); \ | |||
} while (0) | |||
#else | |||
/* "bad" GNU strerror_r we need to clean up after. */ | |||
#define __redis_strerror_r(errno, buf, len) \ | |||
do { \ | |||
char *err_str = strerror_r((errno), (buf), (len)); \ | |||
/* If return value _isn't_ the start of the buffer we passed in, \ | |||
* then GNU strerror_r returned an internal static buffer and we \ | |||
* need to copy the result into our private buffer. */ \ | |||
if (err_str != (buf)) { \ | |||
buf[(len)] = '\0'; \ | |||
strncat((buf), err_str, ((len) - 1)); \ | |||
} \ | |||
} while (0) | |||
#endif | |||
#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; | |||
redisReader *redisReaderCreate(void); | |||
/* 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); | |||
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); | |||
void redisFreeCommand(char *cmd); | |||
void redisFreeSdsCommand(sds cmd); | |||
enum redisConnectionType { | |||
REDIS_CONN_TCP, | |||
REDIS_CONN_UNIX, | |||
}; | |||
/* 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 */ | |||
enum redisConnectionType connection_type; | |||
struct timeval *timeout; | |||
struct { | |||
char *host; | |||
char *source_addr; | |||
int port; | |||
} tcp; | |||
struct { | |||
char *path; | |||
} unix_sock; | |||
} redisContext; | |||
redisContext *redisConnect(const char *ip, int port); | |||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); | |||
redisContext *redisConnectNonBlock(const char *ip, int port); | |||
redisContext *redisConnectBindNonBlock(const char *ip, int port, | |||
const char *source_addr); | |||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, | |||
const char *source_addr); | |||
redisContext *redisConnectUnix(const char *path); | |||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); | |||
redisContext *redisConnectUnixNonBlock(const char *path); | |||
redisContext *redisConnectFd(int fd); | |||
/** | |||
* Reconnect the given context using the saved information. | |||
* | |||
* This re-uses the exact same connect options as in the initial connection. | |||
* host, ip (or path), timeout and bind address are reused, | |||
* flags are used unmodified from the existing context. | |||
* | |||
* Returns REDIS_OK on successful connect or REDIS_ERR otherwise. | |||
*/ | |||
int redisReconnect(redisContext *c); | |||
int redisSetTimeout(redisContext *c, const struct timeval tv); | |||
int redisEnableKeepAlive(redisContext *c); | |||
void redisFree(redisContext *c); | |||
int redisFreeKeepFd(redisContext *c); | |||
int redisBufferRead(redisContext *c); | |||
int redisBufferWrite(redisContext *c, int *done); | |||
/* 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 formatted command to the output buffer. Use these functions in blocking mode | |||
* to get a pipeline of commands. */ | |||
int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); | |||
/* Write a command to the output buffer. Use these functions in blocking mode | |||
* to get a pipeline of commands. */ | |||
int redisvAppendCommand(redisContext *c, const char *format, va_list ap); | |||
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 |
@@ -0,0 +1,458 @@ | |||
/* Extracted from anet.c to work properly with Hiredis error reporting. | |||
* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, | |||
* Jan-Erik Rediger <janerik at fnordig 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 <poll.h> | |||
#include <limits.h> | |||
#include <stdlib.h> | |||
#include "net.h" | |||
#include "sds.h" | |||
/* Defined in hiredis.c */ | |||
void __redisSetError(redisContext *c, int type, const char *str); | |||
static void redisContextCloseFd(redisContext *c) { | |||
if (c && c->fd >= 0) { | |||
close(c->fd); | |||
c->fd = -1; | |||
} | |||
} | |||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { | |||
char buf[128] = { 0 }; | |||
size_t len = 0; | |||
if (prefix != NULL) | |||
len = snprintf(buf,sizeof(buf),"%s: ",prefix); | |||
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); | |||
__redisSetError(c,type,buf); | |||
} | |||
static int redisSetReuseAddr(redisContext *c) { | |||
int on = 1; | |||
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
static int redisCreateSocket(redisContext *c, int type) { | |||
int s; | |||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
return REDIS_ERR; | |||
} | |||
c->fd = s; | |||
if (type == AF_INET) { | |||
if (redisSetReuseAddr(c) == REDIS_ERR) { | |||
return REDIS_ERR; | |||
} | |||
} | |||
return REDIS_OK; | |||
} | |||
static int redisSetBlocking(redisContext *c, int blocking) { | |||
int flags; | |||
/* Set the socket nonblocking. | |||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be | |||
* interrupted by a signal. */ | |||
if ((flags = fcntl(c->fd, F_GETFL)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
if (blocking) | |||
flags &= ~O_NONBLOCK; | |||
else | |||
flags |= O_NONBLOCK; | |||
if (fcntl(c->fd, F_SETFL, flags) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
int redisKeepAlive(redisContext *c, int interval) { | |||
int val = 1; | |||
int fd = c->fd; | |||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
val = interval; | |||
#ifdef _OSX | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
#else | |||
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) | |||
val = interval; | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
val = interval/3; | |||
if (val == 0) val = 1; | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
val = 3; | |||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); | |||
return REDIS_ERR; | |||
} | |||
#endif | |||
#endif | |||
return REDIS_OK; | |||
} | |||
static int redisSetTcpNoDelay(redisContext *c) { | |||
int yes = 1; | |||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) | |||
static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { | |||
struct pollfd wfd[1]; | |||
long msec; | |||
msec = -1; | |||
wfd[0].fd = c->fd; | |||
wfd[0].events = POLLOUT; | |||
/* Only use timeout when not NULL. */ | |||
if (timeout != NULL) { | |||
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { | |||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); | |||
if (msec < 0 || msec > INT_MAX) { | |||
msec = INT_MAX; | |||
} | |||
} | |||
if (errno == EINPROGRESS) { | |||
int res; | |||
if ((res = poll(wfd, 1, msec)) == -1) { | |||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} else if (res == 0) { | |||
errno = ETIMEDOUT; | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
if (redisCheckSocketError(c) != REDIS_OK) | |||
return REDIS_ERR; | |||
return REDIS_OK; | |||
} | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
redisContextCloseFd(c); | |||
return REDIS_ERR; | |||
} | |||
int redisCheckSocketError(redisContext *c) { | |||
int err = 0; | |||
socklen_t errlen = sizeof(err); | |||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); | |||
return REDIS_ERR; | |||
} | |||
if (err) { | |||
errno = err; | |||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); | |||
return REDIS_ERR; | |||
} | |||
return REDIS_OK; | |||
} | |||
int redisContextSetTimeout(redisContext *c, const 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; | |||
} | |||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout, | |||
const char *source_addr) { | |||
int s, rv, n; | |||
char _port[6]; /* strlen("65535"); */ | |||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b; | |||
int blocking = (c->flags & REDIS_BLOCK); | |||
int reuseaddr = (c->flags & REDIS_REUSEADDR); | |||
int reuses = 0; | |||
c->connection_type = REDIS_CONN_TCP; | |||
c->tcp.port = port; | |||
/* We need to take possession of the passed parameters | |||
* to make them reusable for a reconnect. | |||
* We also carefully check we don't free data we already own, | |||
* as in the case of the reconnect method. | |||
* | |||
* This is a bit ugly, but atleast it works and doesn't leak memory. | |||
**/ | |||
if (c->tcp.host != addr) { | |||
if (c->tcp.host) | |||
free(c->tcp.host); | |||
c->tcp.host = strdup(addr); | |||
} | |||
if (timeout) { | |||
if (c->timeout != timeout) { | |||
if (c->timeout == NULL) | |||
c->timeout = malloc(sizeof(struct timeval)); | |||
memcpy(c->timeout, timeout, sizeof(struct timeval)); | |||
} | |||
} else { | |||
if (c->timeout) | |||
free(c->timeout); | |||
c->timeout = NULL; | |||
} | |||
if (source_addr == NULL) { | |||
free(c->tcp.source_addr); | |||
c->tcp.source_addr = NULL; | |||
} else if (c->tcp.source_addr != source_addr) { | |||
free(c->tcp.source_addr); | |||
c->tcp.source_addr = strdup(source_addr); | |||
} | |||
snprintf(_port, 6, "%d", port); | |||
memset(&hints,0,sizeof(hints)); | |||
hints.ai_family = AF_INET; | |||
hints.ai_socktype = SOCK_STREAM; | |||
/* Try with IPv6 if no IPv4 address was found. We do it in this order since | |||
* in a Redis client you can't afford to test if you have IPv6 connectivity | |||
* as this would add latency to every connect. Otherwise a more sensible | |||
* route could be: Use IPv6 if both addresses are available and there is IPv6 | |||
* connectivity. */ | |||
if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { | |||
hints.ai_family = AF_INET6; | |||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { | |||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); | |||
return REDIS_ERR; | |||
} | |||
} | |||
for (p = servinfo; p != NULL; p = p->ai_next) { | |||
addrretry: | |||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) | |||
continue; | |||
c->fd = s; | |||
if (redisSetBlocking(c,0) != REDIS_OK) | |||
goto error; | |||
if (c->tcp.source_addr) { | |||
int bound = 0; | |||
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ | |||
if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { | |||
char buf[128]; | |||
snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); | |||
__redisSetError(c,REDIS_ERR_OTHER,buf); | |||
goto error; | |||
} | |||
if (reuseaddr) { | |||
n = 1; | |||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, | |||
sizeof(n)) < 0) { | |||
goto error; | |||
} | |||
} | |||
for (b = bservinfo; b != NULL; b = b->ai_next) { | |||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { | |||
bound = 1; | |||
break; | |||
} | |||
} | |||
freeaddrinfo(bservinfo); | |||
if (!bound) { | |||
char buf[128]; | |||
snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); | |||
__redisSetError(c,REDIS_ERR_OTHER,buf); | |||
goto error; | |||
} | |||
} | |||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { | |||
if (errno == EHOSTUNREACH) { | |||
redisContextCloseFd(c); | |||
continue; | |||
} else if (errno == EINPROGRESS && !blocking) { | |||
/* This is ok. */ | |||
} else if (errno == EADDRNOTAVAIL && reuseaddr) { | |||
if (++reuses >= REDIS_CONNECT_RETRIES) { | |||
goto error; | |||
} else { | |||
goto addrretry; | |||
} | |||
} else { | |||
if (redisContextWaitReady(c,c->timeout) != REDIS_OK) | |||
goto error; | |||
} | |||
} | |||
if (blocking && redisSetBlocking(c,1) != REDIS_OK) | |||
goto error; | |||
if (redisSetTcpNoDelay(c) != REDIS_OK) | |||
goto error; | |||
c->flags |= REDIS_CONNECTED; | |||
rv = REDIS_OK; | |||
goto end; | |||
} | |||
if (p == NULL) { | |||
char buf[128]; | |||
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); | |||
__redisSetError(c,REDIS_ERR_OTHER,buf); | |||
goto error; | |||
} | |||
error: | |||
rv = REDIS_ERR; | |||
end: | |||
freeaddrinfo(servinfo); | |||
return rv; // Need to return REDIS_OK if alright | |||
} | |||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout) { | |||
return _redisContextConnectTcp(c, addr, port, timeout, NULL); | |||
} | |||
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout, | |||
const char *source_addr) { | |||
return _redisContextConnectTcp(c, addr, port, timeout, source_addr); | |||
} | |||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { | |||
int blocking = (c->flags & REDIS_BLOCK); | |||
struct sockaddr_un sa; | |||
if (redisCreateSocket(c,AF_LOCAL) < 0) | |||
return REDIS_ERR; | |||
if (redisSetBlocking(c,0) != REDIS_OK) | |||
return REDIS_ERR; | |||
c->connection_type = REDIS_CONN_UNIX; | |||
if (c->unix_sock.path != path) | |||
c->unix_sock.path = strdup(path); | |||
if (timeout) { | |||
if (c->timeout != timeout) { | |||
if (c->timeout == NULL) | |||
c->timeout = malloc(sizeof(struct timeval)); | |||
memcpy(c->timeout, timeout, sizeof(struct timeval)); | |||
} | |||
} else { | |||
if (c->timeout) | |||
free(c->timeout); | |||
c->timeout = NULL; | |||
} | |||
sa.sun_family = AF_LOCAL; | |||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); | |||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { | |||
if (errno == EINPROGRESS && !blocking) { | |||
/* This is ok. */ | |||
} else { | |||
if (redisContextWaitReady(c,c->timeout) != REDIS_OK) | |||
return REDIS_ERR; | |||
} | |||
} | |||
/* Reset socket to be blocking after connect(2). */ | |||
if (blocking && redisSetBlocking(c,1) != REDIS_OK) | |||
return REDIS_ERR; | |||
c->flags |= REDIS_CONNECTED; | |||
return REDIS_OK; | |||
} |
@@ -0,0 +1,53 @@ | |||
/* Extracted from anet.c to work properly with Hiredis error reporting. | |||
* | |||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, | |||
* Jan-Erik Rediger <janerik at fnordig 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 redisCheckSocketError(redisContext *c); | |||
int redisContextSetTimeout(redisContext *c, const struct timeval tv); | |||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); | |||
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, | |||
const struct timeval *timeout, | |||
const char *source_addr); | |||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); | |||
int redisKeepAlive(redisContext *c, int interval); | |||
#endif |
@@ -0,0 +1,525 @@ | |||
/* | |||
* 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> | |||
#ifndef _MSC_VER | |||
#include <unistd.h> | |||
#endif | |||
#include <assert.h> | |||
#include <errno.h> | |||
#include <ctype.h> | |||
#include "read.h" | |||
#include "sds.h" | |||
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 > 7 */ | |||
if (r->ridx == 8) { | |||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, | |||
"No support for nested multi bulk replies with depth > 7"); | |||
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 *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { | |||
redisReader *r; | |||
r = calloc(sizeof(redisReader),1); | |||
if (r == NULL) | |||
return NULL; | |||
r->err = 0; | |||
r->errstr[0] = '\0'; | |||
r->fn = fn; | |||
r->buf = sdsempty(); | |||
r->maxbuf = REDIS_READER_MAX_BUF; | |||
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 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { | |||
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) { | |||
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; | |||
} |
@@ -0,0 +1,116 @@ | |||
/* | |||
* 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_READ_H | |||
#define __HIREDIS_READ_H | |||
#include <stdio.h> /* for size_t */ | |||
#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 occurred. 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... */ | |||
#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 | |||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ | |||
#ifdef __cplusplus | |||
extern "C" { | |||
#endif | |||
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; | |||
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 */ | |||
size_t maxbuf; /* Max length of unused buffer */ | |||
redisReadTask rstack[9]; | |||
int ridx; /* Index of current read task */ | |||
void *reply; /* Temporary reply pointer */ | |||
redisReplyObjectFunctions *fn; | |||
void *privdata; | |||
} redisReader; | |||
/* Public API for the protocol parser. */ | |||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); | |||
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) | |||
#ifdef __cplusplus | |||
} | |||
#endif | |||
#endif |
@@ -0,0 +1,105 @@ | |||
/* SDS (Simple Dynamic Strings), A C dynamic strings library. | |||
* | |||
* Copyright (c) 2006-2014, 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 | |||
#define SDS_MAX_PREALLOC (1024*1024) | |||
#include <sys/types.h> | |||
#include <stdarg.h> | |||
#ifdef _MSC_VER | |||
#include "win32.h" | |||
#endif | |||
typedef char *sds; | |||
struct sdshdr { | |||
int len; | |||
int free; | |||
char buf[]; | |||
}; | |||
static inline size_t sdslen(const sds s) { | |||
struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); | |||
return sh->len; | |||
} | |||
static inline size_t sdsavail(const sds s) { | |||
struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); | |||
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(const sds s); | |||
sds sdsgrowzero(sds s, size_t len); | |||
sds sdscatlen(sds s, const void *t, size_t len); | |||
sds sdscat(sds s, const char *t); | |||
sds sdscatsds(sds s, const sds t); | |||
sds sdscpylen(sds s, const char *t, size_t len); | |||
sds sdscpy(sds s, const char *t); | |||
sds sdscatvprintf(sds s, const char *fmt, va_list ap); | |||
#ifdef __GNUC__ | |||
sds sdscatprintf(sds s, const char *fmt, ...) | |||
__attribute__((format(printf, 2, 3))); | |||
#else | |||
sds sdscatprintf(sds s, const char *fmt, ...); | |||
#endif | |||
sds sdscatfmt(sds s, char const *fmt, ...); | |||
void sdstrim(sds s, const char *cset); | |||
void sdsrange(sds s, int start, int end); | |||
void sdsupdatelen(sds s); | |||
void sdsclear(sds s); | |||
int sdscmp(const sds s1, const sds s2); | |||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); | |||
void sdsfreesplitres(sds *tokens, int count); | |||
void sdstolower(sds s); | |||
void sdstoupper(sds s); | |||
sds sdsfromlonglong(long long value); | |||
sds sdscatrepr(sds s, const char *p, size_t len); | |||
sds *sdssplitargs(const char *line, int *argc); | |||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); | |||
sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); | |||
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); | |||
/* Low level functions exposed to the user API */ | |||
sds sdsMakeRoomFor(sds s, size_t addlen); | |||
void sdsIncrLen(sds s, int incr); | |||
sds sdsRemoveFreeSpace(sds s); | |||
size_t sdsAllocSize(sds s); | |||
#endif |
@@ -69,6 +69,11 @@ Copyright: 2001-2015 Dr Martin Porter | |||
2002-2015 Richard Boulton | |||
License: BSD-2-Clause | |||
Files: contrib/hiredis/* | |||
Copyright: 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> | |||
2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> | |||
License: BSD-3-Clause-Redis | |||
Files: src/libutil/diff.c | |||
Copyright: 2004 Michael B. Allen <mba2000 ioplex.com> | |||
2010-2014 Vsevolod Stakhov <vsevolod@highsecure.ru> | |||
@@ -110,7 +115,7 @@ Comment: | |||
The exact meaning of "Public domain" mentioned in the file headers was taken from: | |||
http://www.corpit.ru/mjt/tinycdb.html | |||
Files: src/libcryptobox/curve25519/* | |||
Files: src/libcryptobox/curve25519/curve25519-donna* | |||
Copyright: 2008, Google Inc. | |||
License: BSD-3-Clause-Google | |||
@@ -238,6 +243,32 @@ License: BSD-3-Clause-Google | |||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
License: BSD-3-Clause-Redis | |||
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. | |||
License: Expat | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal |
@@ -125,6 +125,9 @@ TARGET_LINK_LIBRARIES(rspamd rspamd-server) | |||
IF (ENABLE_SNOWBALL MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamd stemmer) | |||
ENDIF() | |||
IF(ENABLE_HIREDIS MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamd rspamd-hiredis) | |||
ENDIF() | |||
TARGET_LINK_LIBRARIES(rspamd rspamd-actrie) | |||
IF (ENABLE_FANN MATCHES "ON") |
@@ -36,6 +36,9 @@ TARGET_LINK_LIBRARIES(rspamadm ${RSPAMD_REQUIRED_LIBRARIES}) | |||
IF (ENABLE_SNOWBALL MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamadm stemmer) | |||
ENDIF() | |||
IF(ENABLE_HIREDIS MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamadm rspamd-hiredis) | |||
ENDIF() | |||
TARGET_LINK_LIBRARIES(rspamadm rspamd-actrie) | |||
IF (NOT DEBIAN_BUILD) |
@@ -28,6 +28,9 @@ TARGET_LINK_LIBRARIES(rspamd-test ${RSPAMD_REQUIRED_LIBRARIES}) | |||
IF (ENABLE_SNOWBALL MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamd-test stemmer) | |||
ENDIF() | |||
IF(ENABLE_HIREDIS MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamd-test rspamd-hiredis) | |||
ENDIF() | |||
IF (ENABLE_HYPERSCAN MATCHES "ON") | |||
TARGET_LINK_LIBRARIES(rspamd-test hs) | |||
SET_TARGET_PROPERTIES(rspamd-test PROPERTIES LINKER_LANGUAGE CXX) |