From 4f6fc6dfb13ba65a88b3976814f41f5f5ce24dc0 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 18 May 2010 17:11:09 +0200 Subject: 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 --- Makefile | 53 ++++++++++ anet.c | 270 +++++++++++++++++++++++++++++++++++++++++++++++ anet.h | 49 +++++++++ config.h | 45 ++++++++ example.c | 33 ++++++ fmacros.h | 15 +++ hiredis.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++ hiredis.h | 25 +++++ sds.c | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sds.h | 73 +++++++++++++ 10 files changed, 1190 insertions(+) create mode 100644 Makefile create mode 100644 anet.c create mode 100644 anet.h create mode 100644 config.h create mode 100644 example.c create mode 100644 fmacros.h create mode 100644 hiredis.c create mode 100644 hiredis.h create mode 100644 sds.c create mode 100644 sds.h 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 +# 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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * 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 +#endif + +/* test for malloc_size() */ +#ifdef __APPLE__ +#include +#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 +#include +#include + +#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 +#include +#include +#include +#include + +#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 + * 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 +#include +#include +#include +#include + +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 + * 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 + +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 -- cgit v1.2.3