commit 4f6fc6dfb13ba65a88b3976814f41f5f5ce24dc0 Author: antirez Date: Tue May 18 17:11:09 2010 +0200 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 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