summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2010-05-18 17:11:09 +0200
committerantirez <antirez@gmail.com>2010-05-18 17:11:09 +0200
commit4f6fc6dfb13ba65a88b3976814f41f5f5ce24dc0 (patch)
tree9031cf1936722715aba14135781e05cec1af8592
hiredis was extracted from redis-tools, reverted to standard malloc/free, ported to the new protocol, and started as a stand alone project in order to support the need of a C client in the Redis community
-rw-r--r--Makefile53
-rw-r--r--anet.c270
-rw-r--r--anet.h49
-rw-r--r--config.h45
-rw-r--r--example.c33
-rw-r--r--fmacros.h15
-rw-r--r--hiredis.c269
-rw-r--r--hiredis.h25
-rw-r--r--sds.c358
-rw-r--r--sds.h73
10 files changed, 1190 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ee73a1a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+# Hiredis Makefile
+# Copyright (C) 2010 Salvatore Sanfilippo <antirez at gmail dot com>
+# This file is released under the BSD license, see the COPYING file
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+OPTIMIZATION?=-O2
+ifeq ($(uname_S),SunOS)
+ CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
+ CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
+else
+ CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
+ CCLINK?= -lm -pthread
+endif
+CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
+DEBUG?= -g -rdynamic -ggdb
+
+OBJ = anet.o hiredis.o sds.o example.o
+
+PRGNAME = hiredis-example
+
+all: hiredis-example
+
+# Deps (use make dep to generate this)
+anet.o: anet.c fmacros.h anet.h
+hiredis.o: hiredis.c hiredis.h sds.h anet.h
+sds.o: sds.c sds.h
+
+hiredis-example: $(OBJ)
+ $(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
+
+.c.o:
+ $(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
+
+clean:
+ rm -rf $(PRGNAME) *.o *.gcda *.gcno *.gcov
+
+dep:
+ $(CC) -MM *.c
+
+32bit:
+ @echo ""
+ @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
+ @echo ""
+ make ARCH="-m32"
+
+gprof:
+ make PROF="-pg"
+
+gcov:
+ make PROF="-fprofile-arcs -ftest-coverage"
+
+noopt:
+ make OPTIMIZATION=""
diff --git a/anet.c b/anet.c
new file mode 100644
index 0000000..4fe811a
--- /dev/null
+++ b/anet.c
@@ -0,0 +1,270 @@
+/* anet.c -- Basic TCP socket stuff made a bit less boring
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <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 "anet.h"
+
+static void anetSetError(char *err, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!err) return;
+ va_start(ap, fmt);
+ vsnprintf(err, ANET_ERR_LEN, fmt, ap);
+ va_end(ap);
+}
+
+int anetNonBlock(char *err, int fd)
+{
+ int flags;
+
+ /* Set the socket nonblocking.
+ * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+ * interrupted by a signal. */
+ if ((flags = fcntl(fd, F_GETFL)) == -1) {
+ anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetTcpNoDelay(char *err, int fd)
+{
+ int yes = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
+ {
+ anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetSetSendBuffer(char *err, int fd, int buffsize)
+{
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
+ {
+ anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetTcpKeepAlive(char *err, int fd)
+{
+ int yes = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
+ anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
+int anetResolve(char *err, char *host, char *ipbuf)
+{
+ struct sockaddr_in sa;
+
+ sa.sin_family = AF_INET;
+ if (inet_aton(host, &sa.sin_addr) == 0) {
+ struct hostent *he;
+
+ he = gethostbyname(host);
+ if (he == NULL) {
+ anetSetError(err, "can't resolve: %s\n", host);
+ return ANET_ERR;
+ }
+ memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
+ }
+ strcpy(ipbuf,inet_ntoa(sa.sin_addr));
+ return ANET_OK;
+}
+
+#define ANET_CONNECT_NONE 0
+#define ANET_CONNECT_NONBLOCK 1
+static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
+{
+ int s, on = 1;
+ struct sockaddr_in sa;
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ anetSetError(err, "creating socket: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ /* Make sure connection-intensive things like the redis benckmark
+ * will be able to close/open sockets a zillion of times */
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ if (inet_aton(addr, &sa.sin_addr) == 0) {
+ struct hostent *he;
+
+ he = gethostbyname(addr);
+ if (he == NULL) {
+ anetSetError(err, "can't resolve: %s\n", addr);
+ close(s);
+ return ANET_ERR;
+ }
+ memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
+ }
+ if (flags & ANET_CONNECT_NONBLOCK) {
+ if (anetNonBlock(err,s) != ANET_OK)
+ return ANET_ERR;
+ }
+ if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ if (errno == EINPROGRESS &&
+ flags & ANET_CONNECT_NONBLOCK)
+ return s;
+
+ anetSetError(err, "connect: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ return s;
+}
+
+int anetTcpConnect(char *err, char *addr, int port)
+{
+ return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
+}
+
+int anetTcpNonBlockConnect(char *err, char *addr, int port)
+{
+ return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
+}
+
+/* Like read(2) but make sure 'count' is read before to return
+ * (unless error or EOF condition is encountered) */
+int anetRead(int fd, char *buf, int count)
+{
+ int nread, totlen = 0;
+ while(totlen != count) {
+ nread = read(fd,buf,count-totlen);
+ if (nread == 0) return totlen;
+ if (nread == -1) return -1;
+ totlen += nread;
+ buf += nread;
+ }
+ return totlen;
+}
+
+/* Like write(2) but make sure 'count' is read before to return
+ * (unless error is encountered) */
+int anetWrite(int fd, char *buf, int count)
+{
+ int nwritten, totlen = 0;
+ while(totlen != count) {
+ nwritten = write(fd,buf,count-totlen);
+ if (nwritten == 0) return totlen;
+ if (nwritten == -1) return -1;
+ totlen += nwritten;
+ buf += nwritten;
+ }
+ return totlen;
+}
+
+int anetTcpServer(char *err, int port, char *bindaddr)
+{
+ int s, on = 1;
+ struct sockaddr_in sa;
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+ anetSetError(err, "socket: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ memset(&sa,0,sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ sa.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bindaddr) {
+ if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
+ anetSetError(err, "Invalid bind address\n");
+ close(s);
+ return ANET_ERR;
+ }
+ }
+ if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ anetSetError(err, "bind: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ if (listen(s, 511) == -1) { /* the magic 511 constant is from nginx */
+ anetSetError(err, "listen: %s\n", strerror(errno));
+ close(s);
+ return ANET_ERR;
+ }
+ return s;
+}
+
+int anetAccept(char *err, int serversock, char *ip, int *port)
+{
+ int fd;
+ struct sockaddr_in sa;
+ unsigned int saLen;
+
+ while(1) {
+ saLen = sizeof(sa);
+ fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
+ if (fd == -1) {
+ if (errno == EINTR)
+ continue;
+ else {
+ anetSetError(err, "accept: %s\n", strerror(errno));
+ return ANET_ERR;
+ }
+ }
+ break;
+ }
+ if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
+ if (port) *port = ntohs(sa.sin_port);
+ return fd;
+}
diff --git a/anet.h b/anet.h
new file mode 100644
index 0000000..ce0f477
--- /dev/null
+++ b/anet.h
@@ -0,0 +1,49 @@
+/* anet.c -- Basic TCP socket stuff made a bit less boring
+ *
+ * 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 ANET_H
+#define ANET_H
+
+#define ANET_OK 0
+#define ANET_ERR -1
+#define ANET_ERR_LEN 256
+
+int anetTcpConnect(char *err, char *addr, int port);
+int anetTcpNonBlockConnect(char *err, char *addr, int port);
+int anetRead(int fd, char *buf, int count);
+int anetResolve(char *err, char *host, char *ipbuf);
+int anetTcpServer(char *err, int port, char *bindaddr);
+int anetAccept(char *err, int serversock, char *ip, int *port);
+int anetWrite(int fd, char *buf, int count);
+int anetNonBlock(char *err, int fd);
+int anetTcpNoDelay(char *err, int fd);
+int anetTcpKeepAlive(char *err, int fd);
+
+#endif
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..6e98fbb
--- /dev/null
+++ b/config.h
@@ -0,0 +1,45 @@
+#ifndef __CONFIG_H
+#define __CONFIG_H
+
+#ifdef __APPLE__
+#include <AvailabilityMacros.h>
+#endif
+
+/* test for malloc_size() */
+#ifdef __APPLE__
+#include <malloc/malloc.h>
+#define HAVE_MALLOC_SIZE 1
+#define redis_malloc_size(p) malloc_size(p)
+#endif
+
+/* define redis_fstat to fstat or fstat64() */
+#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
+#define redis_fstat fstat64
+#define redis_stat stat64
+#else
+#define redis_fstat fstat
+#define redis_stat stat
+#endif
+
+/* test for backtrace() */
+#if defined(__APPLE__) || defined(__linux__)
+#define HAVE_BACKTRACE 1
+#endif
+
+/* test for polling API */
+#ifdef __linux__
+#define HAVE_EPOLL 1
+#endif
+
+#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
+#define HAVE_KQUEUE 1
+#endif
+
+/* define aof_fsync to fdatasync() in Linux and fsync() for all the rest */
+#ifdef __linux__
+#define aof_fsync fdatasync
+#else
+#define aof_fsync fsync
+#endif
+
+#endif
diff --git a/example.c b/example.c
new file mode 100644
index 0000000..05a0fa4
--- /dev/null
+++ b/example.c
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "hiredis.h"
+
+int main(void) {
+ int fd;
+ redisReply *reply;
+
+ reply = redisConnect(&fd, "127.0.0.1", 6379);
+ if (reply != NULL) {
+ printf("Connection error: %s", reply->reply);
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(fd,"PING");
+ printf("PONG: %s\n", reply->reply);
+ freeReplyObject(reply);
+
+ /* Set a key */
+ reply = redisCommand(fd,"SET %s %s\n", "foo", "hello world");
+ printf("SET: %s\n", reply->reply);
+ freeReplyObject(reply);
+
+ /* Set a key using binary safe API */
+ reply = redisCommand(fd,"SET %b %b\n", "bar", 3, "hello", 5);
+ printf("SET (binary API): %s\n", reply->reply);
+ freeReplyObject(reply);
+
+ return 0;
+}
diff --git a/fmacros.h b/fmacros.h
new file mode 100644
index 0000000..38f4648
--- /dev/null
+++ b/fmacros.h
@@ -0,0 +1,15 @@
+#ifndef _REDIS_FMACRO_H
+#define _REDIS_FMACRO_H
+
+#define _BSD_SOURCE
+
+#ifdef __linux__
+#define _XOPEN_SOURCE 700
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#define _LARGEFILE_SOURCE
+#define _FILE_OFFSET_BITS 64
+
+#endif
diff --git a/hiredis.c b/hiredis.c
new file mode 100644
index 0000000..0147a4c
--- /dev/null
+++ b/hiredis.c
@@ -0,0 +1,269 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "hiredis.h"
+#include "anet.h"
+#include "sds.h"
+
+static redisReply *redisReadReply(int fd);
+static redisReply *createReplyObject(int type, sds reply);
+
+/* We simply abort on out of memory */
+static void redisOOM(void) {
+ fprintf(stderr,"Out of memory in hiredis.c");
+ exit(1);
+}
+
+/* Connect to a Redis instance. On success NULL is returned and *fd is set
+ * to the socket file descriptor. On error a redisReply object is returned
+ * with reply->type set to REDIS_REPLY_ERROR and reply->string containing
+ * the error message. This replyObject must be freed with redisFreeReply(). */
+redisReply *redisConnect(int *fd, char *ip, int port) {
+ char err[ANET_ERR_LEN];
+
+ *fd = anetTcpConnect(err,ip,port);
+ if (*fd == ANET_ERR)
+ return createReplyObject(REDIS_REPLY_ERROR,sdsnew(err));
+ anetTcpNoDelay(NULL,*fd);
+ return NULL;
+}
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type, sds reply) {
+ redisReply *r = malloc(sizeof(*r));
+
+ if (!r) redisOOM();
+ r->type = type;
+ r->reply = reply;
+ return r;
+}
+
+/* Free a reply object */
+void freeReplyObject(redisReply *r) {
+ size_t j;
+
+ switch(r->type) {
+ case REDIS_REPLY_INTEGER:
+ break; /* Nothing to free */
+ case REDIS_REPLY_ARRAY:
+ for (j = 0; j < r->elements; j++)
+ freeReplyObject(r->element[j]);
+ free(r->element);
+ break;
+ default:
+ sdsfree(r->reply);
+ break;
+ }
+ free(r);
+}
+
+static redisReply *redisIOError(void) {
+ return createReplyObject(REDIS_REPLY_ERROR,sdsnew("I/O error"));
+}
+
+/* In a real high performance C client this should be bufferized */
+static sds redisReadLine(int fd) {
+ sds line = sdsempty();
+
+ while(1) {
+ char c;
+ ssize_t ret;
+
+ ret = read(fd,&c,1);
+ if (ret == -1) {
+ sdsfree(line);
+ return NULL;
+ } else if ((ret == 0) || (c == '\n')) {
+ break;
+ } else {
+ line = sdscatlen(line,&c,1);
+ }
+ }
+ return sdstrim(line,"\r\n");
+}
+
+static redisReply *redisReadSingleLineReply(int fd, int type) {
+ sds buf = redisReadLine(fd);
+
+ if (buf == NULL) return redisIOError();
+ return createReplyObject(type,buf);
+}
+
+static redisReply *redisReadIntegerReply(int fd) {
+ sds buf = redisReadLine(fd);
+ redisReply *r = malloc(sizeof(*r));
+
+ if (r == NULL) redisOOM();
+ if (buf == NULL) return redisIOError();
+ r->type = REDIS_REPLY_INTEGER;
+ r->integer = strtoll(buf,NULL,10);
+ return r;
+}
+
+static redisReply *redisReadBulkReply(int fd) {
+ sds replylen = redisReadLine(fd);
+ sds buf;
+ char crlf[2];
+ int bulklen;
+
+ if (replylen == NULL) return redisIOError();
+ bulklen = atoi(replylen);
+ sdsfree(replylen);
+ if (bulklen == -1)
+ return createReplyObject(REDIS_REPLY_NIL,sdsempty());
+
+ buf = sdsnewlen(NULL,bulklen);
+ anetRead(fd,buf,bulklen);
+ anetRead(fd,crlf,2);
+ return createReplyObject(REDIS_REPLY_STRING,buf);
+}
+
+static redisReply *redisReadMultiBulkReply(int fd) {
+ sds replylen = redisReadLine(fd);
+ long elements, j;
+ redisReply *r;
+
+ if (replylen == NULL) return redisIOError();
+ elements = strtol(replylen,NULL,10);
+ sdsfree(replylen);
+
+ if (elements == -1)
+ return createReplyObject(REDIS_REPLY_NIL,sdsempty());
+
+ if ((r = malloc(sizeof(*r))) == NULL) redisOOM();
+ r->type = REDIS_REPLY_ARRAY;
+ r->elements = elements;
+ if ((r->element = malloc(sizeof(*r)*elements)) == NULL) redisOOM();
+ for (j = 0; j < elements; j++)
+ r->element[j] = redisReadReply(fd);
+ return r;
+}
+
+static redisReply *redisReadReply(int fd) {
+ char type;
+
+ if (anetRead(fd,&type,1) <= 0) return redisIOError();
+ switch(type) {
+ case '-':
+ return redisReadSingleLineReply(fd,REDIS_REPLY_ERROR);
+ case '+':
+ return redisReadSingleLineReply(fd,REDIS_REPLY_STRING);
+ case ':':
+ return redisReadIntegerReply(fd);
+ case '$':
+ return redisReadBulkReply(fd);
+ case '*':
+ return redisReadMultiBulkReply(fd);
+ default:
+ printf("protocol error, got '%c' as reply type byte\n", type);
+ exit(1);
+ }
+}
+
+/* Helper function for redisCommand(). It's used to append the next argument
+ * to the argument vector. */
+static void addArgument(sds a, char ***argv, int *argc) {
+ (*argc)++;
+ if ((*argv = realloc(*argv, sizeof(char*)*(*argc))) == NULL) redisOOM();
+ (*argv)[(*argc)-1] = a;
+}
+
+/* Execute a command. This function is printf alike:
+ *
+ * %s represents a C nul terminated string you want to interpolate
+ * %b represents a binary safe string
+ *
+ * When using %b you need to provide both the pointer to the string
+ * and the length in bytes. Examples:
+ *
+ * redisCommand("GET %s", mykey);
+ * redisCommand("SET %s %b", mykey, somevalue, somevalue_len);
+ *
+ * RETURN VALUE:
+ *
+ * The returned value is a redisReply object that must be freed using the
+ * redisFreeReply() function.
+ *
+ * given a redisReply "reply" you can test if there was an error in this way:
+ *
+ * if (reply->type == REDIS_REPLY_ERROR) {
+ * printf("Error in request: %s\n", reply->reply);
+ * }
+ *
+ * The replied string itself is in reply->reply if the reply type is
+ * a REDIS_REPLY_STRING. If the reply is a multi bulk reply then
+ * reply->type is REDIS_REPLY_ARRAY and you can access all the elements
+ * in this way:
+ *
+ * for (i = 0; i < reply->elements; i++)
+ * printf("%d: %s\n", i, reply->element[i]);
+ *
+ * Finally when type is REDIS_REPLY_INTEGER the long long integer is
+ * stored at reply->integer.
+ */
+redisReply *redisCommand(int fd, char *format, ...) {
+ va_list ap;
+ size_t size;
+ char *arg, *c = format;
+ sds cmd = sdsempty(); /* whole command buffer */
+ sds current = sdsempty(); /* current argument */
+ char **argv = NULL;
+ int argc = 0, j;
+
+ /* Build the command string accordingly to protocol */
+ va_start(ap,format);
+ while(*c != '\0') {
+ if (*c != '%' || c[1] == '\0') {
+ if (*c == ' ') {
+ if (sdslen(current) != 0) {
+ addArgument(current, &argv, &argc);
+ current = sdsempty();
+ }
+ } else {
+ current = sdscatlen(current,c,1);
+ }
+ } else {
+ switch(c[1]) {
+ case 's':
+ arg = va_arg(ap,char*);
+ current = sdscat(current,arg);
+ break;
+ case 'b':
+ arg = va_arg(ap,char*);
+ size = va_arg(ap,size_t);
+ current = sdscatlen(current,arg,size);
+ break;
+ case '%':
+ cmd = sdscat(cmd,"%");
+ break;
+ }
+ c++;
+ }
+ c++;
+ }
+ va_end(ap);
+
+ /* Add the last argument if needed */
+ if (sdslen(current) != 0)
+ addArgument(current, &argv, &argc);
+ else
+ sdsfree(current);
+
+ /* Build the command at protocol level */
+ cmd = sdscatprintf(cmd,"*%d\r\n",argc);
+ for (j = 0; j < argc; j++) {
+ cmd = sdscatprintf(cmd,"$%zu\r\n",sdslen(argv[j]));
+ cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
+ cmd = sdscatlen(cmd,"\r\n",2);
+ sdsfree(argv[j]);
+ }
+ free(argv);
+
+ /* Send the command via socket */
+ anetWrite(fd,cmd,sdslen(cmd));
+ sdsfree(cmd);
+ return redisReadReply(fd);
+}
diff --git a/hiredis.h b/hiredis.h
new file mode 100644
index 0000000..78da115
--- /dev/null
+++ b/hiredis.h
@@ -0,0 +1,25 @@
+#ifndef __HIREDIS_H
+#define __HIREDIS_H
+
+#define REDIS_REPLY_ERROR 0
+#define REDIS_REPLY_STRING 1
+#define REDIS_REPLY_ARRAY 2
+#define REDIS_REPLY_INTEGER 3
+#define REDIS_REPLY_NIL 4
+
+#include "sds.h"
+
+/* 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 */
+ char *reply; /* 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;
+
+redisReply *redisConnect(int *fd, char *ip, int port);
+void freeReplyObject(redisReply *r);
+redisReply *redisCommand(int fd, char *format, ...);
+
+#endif
diff --git a/sds.c b/sds.c
new file mode 100644
index 0000000..f78a0a8
--- /dev/null
+++ b/sds.c
@@ -0,0 +1,358 @@
+/* SDSLib, A C dynamic strings library
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define SDS_ABORT_ON_OOM
+
+#include "sds.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+static void sdsOomAbort(void) {
+ fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
+ abort();
+}
+
+sds sdsnewlen(const void *init, size_t initlen) {
+ struct sdshdr *sh;
+
+ sh = malloc(sizeof(struct sdshdr)+initlen+1);
+#ifdef SDS_ABORT_ON_OOM
+ if (sh == NULL) sdsOomAbort();
+#else
+ if (sh == NULL) return NULL;
+#endif
+ sh->len = initlen;
+ sh->free = 0;
+ if (initlen) {
+ if (init) memcpy(sh->buf, init, initlen);
+ else memset(sh->buf,0,initlen);
+ }
+ sh->buf[initlen] = '\0';
+ return (char*)sh->buf;
+}
+
+sds sdsempty(void) {
+ return sdsnewlen("",0);
+}
+
+sds sdsnew(const char *init) {
+ size_t initlen = (init == NULL) ? 0 : strlen(init);
+ return sdsnewlen(init, initlen);
+}
+
+size_t sdslen(const sds s) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ return sh->len;
+}
+
+sds sdsdup(const sds s) {
+ return sdsnewlen(s, sdslen(s));
+}
+
+void sdsfree(sds s) {
+ if (s == NULL) return;
+ free(s-sizeof(struct sdshdr));
+}
+
+size_t sdsavail(sds s) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ return sh->free;
+}
+
+void sdsupdatelen(sds s) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ int reallen = strlen(s);
+ sh->free += (sh->len-reallen);
+ sh->len = reallen;
+}
+
+static sds sdsMakeRoomFor(sds s, size_t addlen) {
+ struct sdshdr *sh, *newsh;
+ size_t free = sdsavail(s);
+ size_t len, newlen;
+
+ if (free >= addlen) return s;
+ len = sdslen(s);
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ newlen = (len+addlen)*2;
+ newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
+#ifdef SDS_ABORT_ON_OOM
+ if (newsh == NULL) sdsOomAbort();
+#else
+ if (newsh == NULL) return NULL;
+#endif
+
+ newsh->free = newlen - len;
+ return newsh->buf;
+}
+
+sds sdscatlen(sds s, void *t, size_t len) {
+ struct sdshdr *sh;
+ size_t curlen = sdslen(s);
+
+ s = sdsMakeRoomFor(s,len);
+ if (s == NULL) return NULL;
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ memcpy(s+curlen, t, len);
+ sh->len = curlen+len;
+ sh->free = sh->free-len;
+ s[curlen+len] = '\0';
+ return s;
+}
+
+sds sdscat(sds s, char *t) {
+ return sdscatlen(s, t, strlen(t));
+}
+
+sds sdscpylen(sds s, char *t, size_t len) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ size_t totlen = sh->free+sh->len;
+
+ if (totlen < len) {
+ s = sdsMakeRoomFor(s,len-sh->len);
+ if (s == NULL) return NULL;
+ sh = (void*) (s-(sizeof(struct sdshdr)));
+ totlen = sh->free+sh->len;
+ }
+ memcpy(s, t, len);
+ s[len] = '\0';
+ sh->len = len;
+ sh->free = totlen-len;
+ return s;
+}
+
+sds sdscpy(sds s, char *t) {
+ return sdscpylen(s, t, strlen(t));
+}
+
+sds sdscatprintf(sds s, const char *fmt, ...) {
+ va_list ap;
+ char *buf, *t;
+ size_t buflen = 16;
+
+ while(1) {
+ buf = malloc(buflen);
+#ifdef SDS_ABORT_ON_OOM
+ if (buf == NULL) sdsOomAbort();
+#else
+ if (buf == NULL) return NULL;
+#endif
+ buf[buflen-2] = '\0';
+ va_start(ap, fmt);
+ vsnprintf(buf, buflen, fmt, ap);
+ va_end(ap);
+ if (buf[buflen-2] != '\0') {
+ free(buf);
+ buflen *= 2;
+ continue;
+ }
+ break;
+ }
+ t = sdscat(s, buf);
+ free(buf);
+ return t;
+}
+
+sds sdstrim(sds s, const char *cset) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ char *start, *end, *sp, *ep;
+ size_t len;
+
+ sp = start = s;
+ ep = end = s+sdslen(s)-1;
+ while(sp <= end && strchr(cset, *sp)) sp++;
+ while(ep > start && strchr(cset, *ep)) ep--;
+ len = (sp > ep) ? 0 : ((ep-sp)+1);
+ if (sh->buf != sp) memmove(sh->buf, sp, len);
+ sh->buf[len] = '\0';
+ sh->free = sh->free+(sh->len-len);
+ sh->len = len;
+ return s;
+}
+
+sds sdsrange(sds s, long start, long end) {
+ struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+ size_t newlen, len = sdslen(s);
+
+ if (len == 0) return s;
+ if (start < 0) {
+ start = len+start;
+ if (start < 0) start = 0;
+ }
+ if (end < 0) {
+ end = len+end;
+ if (end < 0) end = 0;
+ }
+ newlen = (start > end) ? 0 : (end-start)+1;
+ if (newlen != 0) {
+ if (start >= (signed)len) start = len-1;
+ if (end >= (signed)len) end = len-1;
+ newlen = (start > end) ? 0 : (end-start)+1;
+ } else {
+ start = 0;
+ }
+ if (start != 0) memmove(sh->buf, sh->buf+start, newlen);
+ sh->buf[newlen] = 0;
+ sh->free = sh->free+(sh->len-newlen);
+ sh->len = newlen;
+ return s;
+}
+
+void sdstolower(sds s) {
+ int len = sdslen(s), j;
+
+ for (j = 0; j < len; j++) s[j] = tolower(s[j]);
+}
+
+void sdstoupper(sds s) {
+ int len = sdslen(s), j;
+
+ for (j = 0; j < len; j++) s[j] = toupper(s[j]);
+}
+
+int sdscmp(sds s1, sds s2) {
+ size_t l1, l2, minlen;
+ int cmp;
+
+ l1 = sdslen(s1);
+ l2 = sdslen(s2);
+ minlen = (l1 < l2) ? l1 : l2;
+ cmp = memcmp(s1,s2,minlen);
+ if (cmp == 0) return l1-l2;
+ return cmp;
+}
+
+/* Split 's' with separator in 'sep'. An array
+ * of sds strings is returned. *count will be set
+ * by reference to the number of tokens returned.
+ *
+ * On out of memory, zero length string, zero length
+ * separator, NULL is returned.
+ *
+ * Note that 'sep' is able to split a string using
+ * a multi-character separator. For example
+ * sdssplit("foo_-_bar","_-_"); will return two
+ * elements "foo" and "bar".
+ *
+ * This version of the function is binary-safe but
+ * requires length arguments. sdssplit() is just the
+ * same function but for zero-terminated strings.
+ */
+sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
+ int elements = 0, slots = 5, start = 0, j;
+
+ sds *tokens = malloc(sizeof(sds)*slots);
+#ifdef SDS_ABORT_ON_OOM
+ if (tokens == NULL) sdsOomAbort();
+#endif
+ if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
+ if (len == 0) {
+ *count = 0;
+ return tokens;
+ }
+ for (j = 0; j < (len-(seplen-1)); j++) {
+ /* make sure there is room for the next element and the final one */
+ if (slots < elements+2) {
+ sds *newtokens;
+
+ slots *= 2;
+ newtokens = realloc(tokens,sizeof(sds)*slots);
+ if (newtokens == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+ sdsOomAbort();
+#else
+ goto cleanup;
+#endif
+ }
+ tokens = newtokens;
+ }
+ /* search the separator */
+ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
+ tokens[elements] = sdsnewlen(s+start,j-start);
+ if (tokens[elements] == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+ sdsOomAbort();
+#else
+ goto cleanup;
+#endif
+ }
+ elements++;
+ start = j+seplen;
+ j = j+seplen-1; /* skip the separator */
+ }
+ }
+ /* Add the final element. We are sure there is room in the tokens array. */
+ tokens[elements] = sdsnewlen(s+start,len-start);
+ if (tokens[elements] == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+ sdsOomAbort();
+#else
+ goto cleanup;
+#endif
+ }
+ elements++;
+ *count = elements;
+ return tokens;
+
+#ifndef SDS_ABORT_ON_OOM
+cleanup:
+ {
+ int i;
+ for (i = 0; i < elements; i++) sdsfree(tokens[i]);
+ free(tokens);
+ return NULL;
+ }
+#endif
+}
+
+void sdsfreesplitres(sds *tokens, int count) {
+ if (!tokens) return;
+ while(count--)
+ sdsfree(tokens[count]);
+ free(tokens);
+}
+
+sds sdsfromlonglong(long long value) {
+ char buf[32], *p;
+ unsigned long long v;
+
+ v = (value < 0) ? -value : value;
+ p = buf+31; /* point to the last character */
+ do {
+ *p-- = '0'+(v%10);
+ v /= 10;
+ } while(v);
+ if (value < 0) *p-- = '-';
+ p++;
+ return sdsnewlen(p,32-(p-buf));
+}
diff --git a/sds.h b/sds.h
new file mode 100644
index 0000000..8b632ff
--- /dev/null
+++ b/sds.h
@@ -0,0 +1,73 @@
+/* SDSLib, A C dynamic strings library
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#include <sys/types.h>
+
+typedef char *sds;
+
+struct sdshdr {
+ long len;
+ long free;
+ char buf[];
+};
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty();
+size_t sdslen(const sds s);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+size_t sdsavail(sds s);
+sds sdscatlen(sds s, void *t, size_t len);
+sds sdscat(sds s, char *t);
+sds sdscpylen(sds s, char *t, size_t len);
+sds sdscpy(sds s, char *t);
+
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdstrim(sds s, const char *cset);
+sds sdsrange(sds s, long start, long end);
+void sdsupdatelen(sds s);
+int sdscmp(sds s1, sds s2);
+sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
+void sdsfreesplitres(sds *tokens, int count);
+void sdstolower(sds s);
+void sdstoupper(sds s);
+sds sdsfromlonglong(long long value);
+
+#endif