Unverified Commit f63e81c2 authored by Chris Lamb's avatar Chris Lamb Committed by GitHub
Browse files

Merge branch 'unstable' into config-set-maxmemory-grammar

parents eaeba1b2 39c70e72
...@@ -103,6 +103,8 @@ typedef struct redisAsyncContext { ...@@ -103,6 +103,8 @@ typedef struct redisAsyncContext {
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr);
redisAsyncContext *redisAsyncConnectUnix(const char *path); redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
...@@ -118,6 +120,7 @@ void redisAsyncHandleWrite(redisAsyncContext *ac); ...@@ -118,6 +120,7 @@ void redisAsyncHandleWrite(redisAsyncContext *ac);
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
#ifdef __cplusplus #ifdef __cplusplus
} }
......
...@@ -161,7 +161,7 @@ static int dictReplace(dict *ht, void *key, void *val) { ...@@ -161,7 +161,7 @@ static int dictReplace(dict *ht, void *key, void *val) {
dictEntry *entry, auxentry; dictEntry *entry, auxentry;
/* Try to add the element. If the key /* Try to add the element. If the key
* does not exists dictAdd will suceed. */ * does not exists dictAdd will succeed. */
if (dictAdd(ht, key, val) == DICT_OK) if (dictAdd(ht, key, val) == DICT_OK)
return 1; return 1;
/* It already exists, get the entry */ /* It already exists, get the entry */
...@@ -293,7 +293,7 @@ static void dictReleaseIterator(dictIterator *iter) { ...@@ -293,7 +293,7 @@ static void dictReleaseIterator(dictIterator *iter) {
/* Expand the hash table if needed */ /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht) { static int _dictExpandIfNeeded(dict *ht) {
/* If the hash table is empty expand it to the intial size, /* If the hash table is empty expand it to the initial size,
* if the table is "full" dobule its size. */ * if the table is "full" dobule its size. */
if (ht->size == 0) if (ht->size == 0)
return dictExpand(ht, DICT_HT_INITIAL_SIZE); return dictExpand(ht, DICT_HT_INITIAL_SIZE);
......
#include <stdlib.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/glib.h>
static GMainLoop *mainloop;
static void
connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
int status)
{
if (status != REDIS_OK) {
g_printerr("Failed to connect: %s\n", ac->errstr);
g_main_loop_quit(mainloop);
} else {
g_printerr("Connected...\n");
}
}
static void
disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
int status)
{
if (status != REDIS_OK) {
g_error("Failed to disconnect: %s", ac->errstr);
} else {
g_printerr("Disconnected...\n");
g_main_loop_quit(mainloop);
}
}
static void
command_cb(redisAsyncContext *ac,
gpointer r,
gpointer user_data G_GNUC_UNUSED)
{
redisReply *reply = r;
if (reply) {
g_print("REPLY: %s\n", reply->str);
}
redisAsyncDisconnect(ac);
}
gint
main (gint argc G_GNUC_UNUSED,
gchar *argv[] G_GNUC_UNUSED)
{
redisAsyncContext *ac;
GMainContext *context = NULL;
GSource *source;
ac = redisAsyncConnect("127.0.0.1", 6379);
if (ac->err) {
g_printerr("%s\n", ac->errstr);
exit(EXIT_FAILURE);
}
source = redis_source_new(ac);
mainloop = g_main_loop_new(context, FALSE);
g_source_attach(source, context);
redisAsyncSetConnectCallback(ac, connect_cb);
redisAsyncSetDisconnectCallback(ac, disconnect_cb);
redisAsyncCommand(ac, command_cb, NULL, "SET key 1234");
redisAsyncCommand(ac, command_cb, NULL, "GET key");
g_main_loop_run(mainloop);
return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/ivykis.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
iv_init();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisIvykisAttach(c);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
iv_main();
iv_deinit();
return 0;
}
//
// Created by Дмитрий Бахвалов on 13.07.15.
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
//
#include <stdio.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/macosx.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
CFRunLoopStop(CFRunLoopGetCurrent());
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
CFRunLoopRef loop = CFRunLoopGetCurrent();
if( !loop ) {
printf("Error: Cannot get current run loop\n");
return 1;
}
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisMacOSAttach(c, loop);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
CFRunLoopRun();
return 0;
}
#include <iostream>
using namespace std;
#include <QCoreApplication>
#include <QTimer>
#include "example-qt.h"
void getCallback(redisAsyncContext *, void * r, void * privdata) {
redisReply * reply = static_cast<redisReply *>(r);
ExampleQt * ex = static_cast<ExampleQt *>(privdata);
if (reply == nullptr || ex == nullptr) return;
cout << "key: " << reply->str << endl;
ex->finish();
}
void ExampleQt::run() {
m_ctx = redisAsyncConnect("localhost", 6379);
if (m_ctx->err) {
cerr << "Error: " << m_ctx->errstr << endl;
redisAsyncFree(m_ctx);
emit finished();
}
m_adapter.setContext(m_ctx);
redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value);
redisAsyncCommand(m_ctx, getCallback, this, "GET key");
}
int main (int argc, char **argv) {
QCoreApplication app(argc, argv);
ExampleQt example(argv[argc-1]);
QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));
QTimer::singleShot(0, &example, SLOT(run()));
return app.exec();
}
#ifndef __HIREDIS_EXAMPLE_QT_H
#define __HIREDIS_EXAMPLE_QT_H
#include <adapters/qt.h>
class ExampleQt : public QObject {
Q_OBJECT
public:
ExampleQt(const char * value, QObject * parent = 0)
: QObject(parent), m_value(value) {}
signals:
void finished();
public slots:
void run();
private:
void finish() { emit finished(); }
private:
const char * m_value;
redisAsyncContext * m_ctx;
RedisQtAdapter m_adapter;
friend
void getCallback(redisAsyncContext *, void *, void *);
};
#endif /* !__HIREDIS_EXAMPLE_QT_H */
...@@ -57,7 +57,7 @@ int main(int argc, char **argv) { ...@@ -57,7 +57,7 @@ int main(int argc, char **argv) {
for (j = 0; j < 10; j++) { for (j = 0; j < 10; j++) {
char buf[64]; char buf[64];
snprintf(buf,64,"%d",j); snprintf(buf,64,"%u",j);
reply = redisCommand(c,"LPUSH mylist element-%s", buf); reply = redisCommand(c,"LPUSH mylist element-%s", buf);
freeReplyObject(reply); freeReplyObject(reply);
} }
......
#ifndef __HIREDIS_FMACRO_H #ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H
#if !defined(_BSD_SOURCE) #if defined(__linux__)
#define _BSD_SOURCE #define _BSD_SOURCE
#define _DEFAULT_SOURCE
#endif #endif
#if defined(_AIX) #if defined(__CYGWIN__)
#define _ALL_SOURCE #include <sys/cdefs.h>
#endif #endif
#if defined(__sun__) #if defined(__sun__)
#define _POSIX_C_SOURCE 200112L #define _POSIX_C_SOURCE 200112L
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
#define _XOPEN_SOURCE 600
#else #else
#define _XOPEN_SOURCE #if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
#define _XOPEN_SOURCE 600
#endif
#endif #endif
#if __APPLE__ && __MACH__ #if defined(__APPLE__) && defined(__MACH__)
#define _OSX #define _OSX
#endif #endif
......
/* /*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -73,6 +75,9 @@ void freeReplyObject(void *reply) { ...@@ -73,6 +75,9 @@ void freeReplyObject(void *reply) {
redisReply *r = reply; redisReply *r = reply;
size_t j; size_t j;
if (r == NULL)
return;
switch(r->type) { switch(r->type) {
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
break; /* Nothing to free */ break; /* Nothing to free */
...@@ -183,504 +188,23 @@ static void *createNilObject(const redisReadTask *task) { ...@@ -183,504 +188,23 @@ static void *createNilObject(const redisReadTask *task) {
return r; return r;
} }
static void __redisReaderSetError(redisReader *r, int type, const char *str) { /* Return the number of digits of 'v' when converted to string in radix 10.
size_t len; * Implementation borrowed from link in redis/src/util.c:string2ll(). */
static uint32_t countDigits(uint64_t v) {
if (r->reply != NULL && r->fn && r->fn->freeObject) { uint32_t result = 1;
r->fn->freeObject(r->reply); for (;;) {
r->reply = NULL; if (v < 10) return result;
} if (v < 100) return result + 1;
if (v < 1000) return result + 2;
/* Clear input buffer on errors. */ if (v < 10000) return result + 3;
if (r->buf != NULL) { v /= 10000U;
sdsfree(r->buf); result += 4;
r->buf = NULL;
r->pos = r->len = 0;
}
/* Reset task stack. */
r->ridx = -1;
/* Set error. */
r->err = type;
len = strlen(str);
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
memcpy(r->errstr,str,len);
r->errstr[len] = '\0';
}
static size_t chrtos(char *buf, size_t size, char byte) {
size_t len = 0;
switch(byte) {
case '\\':
case '"':
len = snprintf(buf,size,"\"\\%c\"",byte);
break;
case '\n': len = snprintf(buf,size,"\"\\n\""); break;
case '\r': len = snprintf(buf,size,"\"\\r\""); break;
case '\t': len = snprintf(buf,size,"\"\\t\""); break;
case '\a': len = snprintf(buf,size,"\"\\a\""); break;
case '\b': len = snprintf(buf,size,"\"\\b\""); break;
default:
if (isprint(byte))
len = snprintf(buf,size,"\"%c\"",byte);
else
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
break;
}
return len;
}
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
char cbuf[8], sbuf[128];
chrtos(cbuf,sizeof(cbuf),byte);
snprintf(sbuf,sizeof(sbuf),
"Protocol error, got %s as reply type byte", cbuf);
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
}
static void __redisReaderSetErrorOOM(redisReader *r) {
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
}
static char *readBytes(redisReader *r, unsigned int bytes) {
char *p;
if (r->len-r->pos >= bytes) {
p = r->buf+r->pos;
r->pos += bytes;
return p;
}
return NULL;
}
/* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) {
int pos = 0;
int _len = len-1;
/* Position should be < len-1 because the character at "pos" should be
* followed by a \n. Note that strchr cannot be used because it doesn't
* allow to search a limited length and the buffer that is being searched
* might not have a trailing NULL character. */
while (pos < _len) {
while(pos < _len && s[pos] != '\r') pos++;
if (s[pos] != '\r') {
/* Not found. */
return NULL;
} else {
if (s[pos+1] == '\n') {
/* Found. */
return s+pos;
} else {
/* Continue searching. */
pos++;
}
}
}
return NULL;
}
/* Read a long long value starting at *s, under the assumption that it will be
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
static long long readLongLong(char *s) {
long long v = 0;
int dec, mult = 1;
char c;
if (*s == '-') {
mult = -1;
s++;
} else if (*s == '+') {
mult = 1;
s++;
}
while ((c = *(s++)) != '\r') {
dec = c - '0';
if (dec >= 0 && dec < 10) {
v *= 10;
v += dec;
} else {
/* Should not happen... */
return -1;
}
}
return mult*v;
}
static char *readLine(redisReader *r, int *_len) {
char *p, *s;
int len;
p = r->buf+r->pos;
s = seekNewline(p,(r->len-r->pos));
if (s != NULL) {
len = s-(r->buf+r->pos);
r->pos += len+2; /* skip \r\n */
if (_len) *_len = len;
return p;
}
return NULL;
}
static void moveToNextTask(redisReader *r) {
redisReadTask *cur, *prv;
while (r->ridx >= 0) {
/* Return a.s.a.p. when the stack is now empty. */
if (r->ridx == 0) {
r->ridx--;
return;
}
cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
/* Reset the type because the next item can be anything */
assert(cur->idx < prv->elements);
cur->type = -1;
cur->elements = -1;
cur->idx++;
return;
}
}
}
static int processLineItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
int len;
if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger)
obj = r->fn->createInteger(cur,readLongLong(p));
else
obj = (void*)REDIS_REPLY_INTEGER;
} else {
/* Type will be error or status. */
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len);
else
obj = (void*)(size_t)(cur->type);
}
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return REDIS_OK;
}
return REDIS_ERR;
}
static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj = NULL;
char *p, *s;
long len;
unsigned long bytelen;
int success = 0;
p = r->buf+r->pos;
s = seekNewline(p,r->len-r->pos);
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
len = readLongLong(p);
if (len < 0) {
/* The nil object can always be created. */
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
success = 1;
} else {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) {
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len);
else
obj = (void*)REDIS_REPLY_STRING;
success = 1;
}
}
/* Proceed when obj was created. */
if (success) {
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->pos += bytelen;
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return REDIS_OK;
}
}
return REDIS_ERR;
}
static int processMultiBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
long elements;
int root = 0;
/* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"No support for nested multi bulk replies with depth > 7");
return REDIS_ERR;
}
if ((p = readLine(r,NULL)) != NULL) {
elements = readLongLong(p);
root = (r->ridx == 0);
if (elements == -1) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
moveToNextTask(r);
} else {
if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements);
else
obj = (void*)REDIS_REPLY_ARRAY;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
} }
/* Modify task stack when there are more than 0 elements. */
if (elements > 0) {
cur->elements = elements;
cur->obj = obj;
r->ridx++;
r->rstack[r->ridx].type = -1;
r->rstack[r->ridx].elements = -1;
r->rstack[r->ridx].idx = 0;
r->rstack[r->ridx].obj = NULL;
r->rstack[r->ridx].parent = cur;
r->rstack[r->ridx].privdata = r->privdata;
} else {
moveToNextTask(r);
}
}
/* Set reply if this is the root object. */
if (root) r->reply = obj;
return REDIS_OK;
}
return REDIS_ERR;
}
static int processItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
char *p;
/* check if we need to read type */
if (cur->type < 0) {
if ((p = readBytes(r,1)) != NULL) {
switch (p[0]) {
case '-':
cur->type = REDIS_REPLY_ERROR;
break;
case '+':
cur->type = REDIS_REPLY_STATUS;
break;
case ':':
cur->type = REDIS_REPLY_INTEGER;
break;
case '$':
cur->type = REDIS_REPLY_STRING;
break;
case '*':
cur->type = REDIS_REPLY_ARRAY;
break;
default:
__redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR;
}
} else {
/* could not consume 1 byte */
return REDIS_ERR;
}
}
/* process typed item */
switch(cur->type) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER:
return processLineItem(r);
case REDIS_REPLY_STRING:
return processBulkItem(r);
case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r);
default:
assert(NULL);
return REDIS_ERR; /* Avoid warning. */
}
}
redisReader *redisReaderCreate(void) {
redisReader *r;
r = calloc(sizeof(redisReader),1);
if (r == NULL)
return NULL;
r->err = 0;
r->errstr[0] = '\0';
r->fn = &defaultFunctions;
r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF;
if (r->buf == NULL) {
free(r);
return NULL;
}
r->ridx = -1;
return r;
}
void redisReaderFree(redisReader *r) {
if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply);
if (r->buf != NULL)
sdsfree(r->buf);
free(r);
}
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
sds newbuf;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* Copy the provided buffer. */
if (buf != NULL && len >= 1) {
/* Destroy internal buffer when it is empty and is quite large. */
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf);
r->buf = sdsempty();
r->pos = 0;
/* r->buf should not be NULL since we just free'd a larger one. */
assert(r->buf != NULL);
}
newbuf = sdscatlen(r->buf,buf,len);
if (newbuf == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->buf = newbuf;
r->len = sdslen(r->buf);
}
return REDIS_OK;
}
int redisReaderGetReply(redisReader *r, void **reply) {
/* Default target pointer to NULL. */
if (reply != NULL)
*reply = NULL;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* When the buffer is empty, there will never be a reply. */
if (r->len == 0)
return REDIS_OK;
/* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
r->rstack[0].type = -1;
r->rstack[0].elements = -1;
r->rstack[0].idx = -1;
r->rstack[0].obj = NULL;
r->rstack[0].parent = NULL;
r->rstack[0].privdata = r->privdata;
r->ridx = 0;
}
/* Process items in reply. */
while (r->ridx >= 0)
if (processItem(r) != REDIS_OK)
break;
/* Return ASAP when an error occurred. */
if (r->err)
return REDIS_ERR;
/* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) {
sdsrange(r->buf,r->pos,-1);
r->pos = 0;
r->len = sdslen(r->buf);
}
/* Emit a reply when there is one. */
if (r->ridx == -1) {
if (reply != NULL)
*reply = r->reply;
r->reply = NULL;
}
return REDIS_OK;
}
/* Calculate the number of bytes needed to represent an integer as string. */
static int intlen(int i) {
int len = 0;
if (i < 0) {
len++;
i = -i;
}
do {
len++;
i /= 10;
} while(i);
return len;
} }
/* Helper that calculates the bulk length given a certain string length. */ /* Helper that calculates the bulk length given a certain string length. */
static size_t bulklen(size_t len) { static size_t bulklen(size_t len) {
return 1+intlen(len)+2+len+2; return 1+countDigits(len)+2+len+2;
} }
int redisvFormatCommand(char **target, const char *format, va_list ap) { int redisvFormatCommand(char **target, const char *format, va_list ap) {
...@@ -692,6 +216,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -692,6 +216,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
char **curargv = NULL, **newargv = NULL; char **curargv = NULL, **newargv = NULL;
int argc = 0; int argc = 0;
int totlen = 0; int totlen = 0;
int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */
int j; int j;
/* Abort if there is not target to set */ /* Abort if there is not target to set */
...@@ -708,19 +233,19 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -708,19 +233,19 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
if (*c == ' ') { if (*c == ' ') {
if (touched) { if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1)); newargv = realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto err; if (newargv == NULL) goto memory_err;
curargv = newargv; curargv = newargv;
curargv[argc++] = curarg; curargv[argc++] = curarg;
totlen += bulklen(sdslen(curarg)); totlen += bulklen(sdslen(curarg));
/* curarg is put in argv so it can be overwritten. */ /* curarg is put in argv so it can be overwritten. */
curarg = sdsempty(); curarg = sdsempty();
if (curarg == NULL) goto err; if (curarg == NULL) goto memory_err;
touched = 0; touched = 0;
} }
} else { } else {
newarg = sdscatlen(curarg,c,1); newarg = sdscatlen(curarg,c,1);
if (newarg == NULL) goto err; if (newarg == NULL) goto memory_err;
curarg = newarg; curarg = newarg;
touched = 1; touched = 1;
} }
...@@ -751,17 +276,14 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -751,17 +276,14 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
/* Try to detect printf format */ /* Try to detect printf format */
{ {
static const char intfmts[] = "diouxX"; static const char intfmts[] = "diouxX";
static const char flags[] = "#0-+ ";
char _format[16]; char _format[16];
const char *_p = c+1; const char *_p = c+1;
size_t _l = 0; size_t _l = 0;
va_list _cpy; va_list _cpy;
/* Flags */ /* Flags */
if (*_p != '\0' && *_p == '#') _p++; while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++;
if (*_p != '\0' && *_p == '0') _p++;
if (*_p != '\0' && *_p == '-') _p++;
if (*_p != '\0' && *_p == ' ') _p++;
if (*_p != '\0' && *_p == '+') _p++;
/* Field width */ /* Field width */
while (*_p != '\0' && isdigit(*_p)) _p++; while (*_p != '\0' && isdigit(*_p)) _p++;
...@@ -829,7 +351,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -829,7 +351,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
fmt_invalid: fmt_invalid:
va_end(_cpy); va_end(_cpy);
goto err; goto format_err;
fmt_valid: fmt_valid:
_l = (_p+1)-c; _l = (_p+1)-c;
...@@ -848,7 +370,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -848,7 +370,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
} }
} }
if (newarg == NULL) goto err; if (newarg == NULL) goto memory_err;
curarg = newarg; curarg = newarg;
touched = 1; touched = 1;
...@@ -860,7 +382,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -860,7 +382,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
/* Add the last argument if needed */ /* Add the last argument if needed */
if (touched) { if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1)); newargv = realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto err; if (newargv == NULL) goto memory_err;
curargv = newargv; curargv = newargv;
curargv[argc++] = curarg; curargv[argc++] = curarg;
totlen += bulklen(sdslen(curarg)); totlen += bulklen(sdslen(curarg));
...@@ -872,11 +394,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -872,11 +394,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
curarg = NULL; curarg = NULL;
/* Add bytes needed to hold multi bulk count */ /* Add bytes needed to hold multi bulk count */
totlen += 1+intlen(argc)+2; totlen += 1+countDigits(argc)+2;
/* Build the command at protocol level */ /* Build the command at protocol level */
cmd = malloc(totlen+1); cmd = malloc(totlen+1);
if (cmd == NULL) goto err; if (cmd == NULL) goto memory_err;
pos = sprintf(cmd,"*%d\r\n",argc); pos = sprintf(cmd,"*%d\r\n",argc);
for (j = 0; j < argc; j++) { for (j = 0; j < argc; j++) {
...@@ -894,12 +416,21 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { ...@@ -894,12 +416,21 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
*target = cmd; *target = cmd;
return totlen; return totlen;
err: format_err:
error_type = -2;
goto cleanup;
memory_err:
error_type = -1;
goto cleanup;
cleanup:
if (curargv) {
while(argc--) while(argc--)
sdsfree(curargv[argc]); sdsfree(curargv[argc]);
free(curargv); free(curargv);
}
if (curarg != NULL)
sdsfree(curarg); sdsfree(curarg);
/* No need to check cmd since it is the last statement that can fail, /* No need to check cmd since it is the last statement that can fail,
...@@ -907,7 +438,7 @@ err: ...@@ -907,7 +438,7 @@ err:
if (cmd != NULL) if (cmd != NULL)
free(cmd); free(cmd);
return -1; return error_type;
} }
/* Format a command according to the Redis protocol. This function /* Format a command according to the Redis protocol. This function
...@@ -928,9 +459,69 @@ int redisFormatCommand(char **target, const char *format, ...) { ...@@ -928,9 +459,69 @@ int redisFormatCommand(char **target, const char *format, ...) {
va_start(ap,format); va_start(ap,format);
len = redisvFormatCommand(target,format,ap); len = redisvFormatCommand(target,format,ap);
va_end(ap); va_end(ap);
/* The API says "-1" means bad result, but we now also return "-2" in some
* cases. Force the return value to always be -1. */
if (len < 0)
len = -1;
return len; return len;
} }
/* Format a command according to the Redis protocol using an sds string and
* sdscatfmt for the processing of arguments. This function takes the
* number of arguments, an array with arguments and an array with their
* lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths.
*/
int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
const size_t *argvlen)
{
sds cmd;
unsigned long long totlen;
int j;
size_t len;
/* Abort on a NULL target */
if (target == NULL)
return -1;
/* Calculate our total size */
totlen = 1+countDigits(argc)+2;
for (j = 0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
totlen += bulklen(len);
}
/* Use an SDS string for command construction */
cmd = sdsempty();
if (cmd == NULL)
return -1;
/* We already know how much storage we need */
cmd = sdsMakeRoomFor(cmd, totlen);
if (cmd == NULL)
return -1;
/* Construct command */
cmd = sdscatfmt(cmd, "*%i\r\n", argc);
for (j=0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
cmd = sdscatfmt(cmd, "$%u\r\n", len);
cmd = sdscatlen(cmd, argv[j], len);
cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
}
assert(sdslen(cmd)==totlen);
*target = cmd;
return totlen;
}
void redisFreeSdsCommand(sds cmd) {
sdsfree(cmd);
}
/* Format a command according to the Redis protocol. This function takes the /* Format a command according to the Redis protocol. This function takes the
* number of arguments, an array with arguments and an array with their * number of arguments, an array with arguments and an array with their
* lengths. If the latter is set to NULL, strlen will be used to compute the * lengths. If the latter is set to NULL, strlen will be used to compute the
...@@ -942,8 +533,12 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz ...@@ -942,8 +533,12 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
size_t len; size_t len;
int totlen, j; int totlen, j;
/* Abort on a NULL target */
if (target == NULL)
return -1;
/* Calculate number of bytes needed for the command */ /* Calculate number of bytes needed for the command */
totlen = 1+intlen(argc)+2; totlen = 1+countDigits(argc)+2;
for (j = 0; j < argc; j++) { for (j = 0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]); len = argvlen ? argvlen[j] : strlen(argv[j]);
totlen += bulklen(len); totlen += bulklen(len);
...@@ -970,6 +565,10 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz ...@@ -970,6 +565,10 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
return totlen; return totlen;
} }
void redisFreeCommand(char *cmd) {
free(cmd);
}
void __redisSetError(redisContext *c, int type, const char *str) { void __redisSetError(redisContext *c, int type, const char *str) {
size_t len; size_t len;
...@@ -982,10 +581,14 @@ void __redisSetError(redisContext *c, int type, const char *str) { ...@@ -982,10 +581,14 @@ void __redisSetError(redisContext *c, int type, const char *str) {
} else { } else {
/* Only REDIS_ERR_IO may lack a description! */ /* Only REDIS_ERR_IO may lack a description! */
assert(type == REDIS_ERR_IO); assert(type == REDIS_ERR_IO);
strerror_r(errno,c->errstr,sizeof(c->errstr)); __redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
} }
} }
redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions);
}
static redisContext *redisContextInit(void) { static redisContext *redisContextInit(void) {
redisContext *c; redisContext *c;
...@@ -997,16 +600,36 @@ static redisContext *redisContextInit(void) { ...@@ -997,16 +600,36 @@ static redisContext *redisContextInit(void) {
c->errstr[0] = '\0'; c->errstr[0] = '\0';
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->tcp.host = NULL;
c->tcp.source_addr = NULL;
c->unix_sock.path = NULL;
c->timeout = NULL;
if (c->obuf == NULL || c->reader == NULL) {
redisFree(c);
return NULL;
}
return c; return c;
} }
void redisFree(redisContext *c) { void redisFree(redisContext *c) {
if (c == NULL)
return;
if (c->fd > 0) if (c->fd > 0)
close(c->fd); close(c->fd);
if (c->obuf != NULL) if (c->obuf != NULL)
sdsfree(c->obuf); sdsfree(c->obuf);
if (c->reader != NULL) if (c->reader != NULL)
redisReaderFree(c->reader); redisReaderFree(c->reader);
if (c->tcp.host)
free(c->tcp.host);
if (c->tcp.source_addr)
free(c->tcp.source_addr);
if (c->unix_sock.path)
free(c->unix_sock.path);
if (c->timeout)
free(c->timeout);
free(c); free(c);
} }
...@@ -1017,6 +640,34 @@ int redisFreeKeepFd(redisContext *c) { ...@@ -1017,6 +640,34 @@ int redisFreeKeepFd(redisContext *c) {
return fd; return fd;
} }
int redisReconnect(redisContext *c) {
c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr));
if (c->fd > 0) {
close(c->fd);
}
sdsfree(c->obuf);
redisReaderFree(c->reader);
c->obuf = sdsempty();
c->reader = redisReaderCreate();
if (c->connection_type == REDIS_CONN_TCP) {
return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
c->timeout, c->tcp.source_addr);
} else if (c->connection_type == REDIS_CONN_UNIX) {
return redisContextConnectUnix(c, c->unix_sock.path, c->timeout);
} else {
/* Something bad happened here and shouldn't have. There isn't
enough information in the context to reconnect. */
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
}
return REDIS_ERR;
}
/* Connect to a Redis instance. On error the field error in the returned /* Connect to a Redis instance. On error the field error in the returned
* context will be set to the return value of the error function. * context will be set to the return value of the error function.
* When no set of reply functions is given, the default set will be used. */ * When no set of reply functions is given, the default set will be used. */
...@@ -1064,6 +715,15 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port, ...@@ -1064,6 +715,15 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
return c; return c;
} }
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) {
redisContext *c = redisContextInit();
c->flags &= ~REDIS_BLOCK;
c->flags |= REDIS_REUSEADDR;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
return c;
}
redisContext *redisConnectUnix(const char *path) { redisContext *redisConnectUnix(const char *path) {
redisContext *c; redisContext *c;
...@@ -1162,10 +822,10 @@ int redisBufferRead(redisContext *c) { ...@@ -1162,10 +822,10 @@ int redisBufferRead(redisContext *c) {
/* Write the output buffer to the socket. /* Write the output buffer to the socket.
* *
* Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
* succesfully written to the socket. When the buffer is empty after the * successfully written to the socket. When the buffer is empty after the
* write operation, "done" is set to 1 (if given). * write operation, "done" is set to 1 (if given).
* *
* Returns REDIS_ERR if an error occured trying to write and sets * Returns REDIS_ERR if an error occurred trying to write and sets
* c->errstr to hold the appropriate error string. * c->errstr to hold the appropriate error string.
*/ */
int redisBufferWrite(redisContext *c, int *done) { int redisBufferWrite(redisContext *c, int *done) {
...@@ -1274,6 +934,9 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { ...@@ -1274,6 +934,9 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
if (len == -1) { if (len == -1) {
__redisSetError(c,REDIS_ERR_OOM,"Out of memory"); __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
return REDIS_ERR; return REDIS_ERR;
} else if (len == -2) {
__redisSetError(c,REDIS_ERR_OTHER,"Invalid format string");
return REDIS_ERR;
} }
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
...@@ -1296,21 +959,21 @@ int redisAppendCommand(redisContext *c, const char *format, ...) { ...@@ -1296,21 +959,21 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
} }
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
char *cmd; sds cmd;
int len; int len;
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len == -1) { if (len == -1) {
__redisSetError(c,REDIS_ERR_OOM,"Out of memory"); __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
return REDIS_ERR; return REDIS_ERR;
} }
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
free(cmd); sdsfree(cmd);
return REDIS_ERR; return REDIS_ERR;
} }
free(cmd); sdsfree(cmd);
return REDIS_OK; return REDIS_OK;
} }
...@@ -1321,7 +984,7 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s ...@@ -1321,7 +984,7 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s
* context is non-blocking, the "reply" pointer will not be used and the * context is non-blocking, the "reply" pointer will not be used and the
* command is simply appended to the write buffer. * command is simply appended to the write buffer.
* *
* Returns the reply when a reply was succesfully retrieved. Returns NULL * Returns the reply when a reply was successfully retrieved. Returns NULL
* otherwise. When NULL is returned in a blocking context, the error field * otherwise. When NULL is returned in a blocking context, the error field
* in the context will be set. * in the context will be set.
*/ */
......
/* /*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -31,26 +33,16 @@ ...@@ -31,26 +33,16 @@
#ifndef __HIREDIS_H #ifndef __HIREDIS_H
#define __HIREDIS_H #define __HIREDIS_H
#include <stdio.h> /* for size_t */ #include "read.h"
#include <stdarg.h> /* for va_list */ #include <stdarg.h> /* for va_list */
#include <sys/time.h> /* for struct timeval */ #include <sys/time.h> /* for struct timeval */
#include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */
#define HIREDIS_MAJOR 0 #define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 11 #define HIREDIS_MINOR 13
#define HIREDIS_PATCH 0 #define HIREDIS_PATCH 3
#define HIREDIS_SONAME 0.13
#define REDIS_ERR -1
#define REDIS_OK 0
/* When an error occurs, the err flag in a context is set to hold the type of
* error that occured. REDIS_ERR_IO means there was an I/O error and you
* should use the "errno" variable to find out what is wrong.
* For other values, the "errstr" field will hold a description. */
#define REDIS_ERR_IO 1 /* Error in read or write */
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_OTHER 2 /* Everything else... */
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
...@@ -79,17 +71,39 @@ ...@@ -79,17 +71,39 @@
/* Flag that is set when monitor mode is active */ /* Flag that is set when monitor mode is active */
#define REDIS_MONITORING 0x40 #define REDIS_MONITORING 0x40
#define REDIS_REPLY_STRING 1 /* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REPLY_ARRAY 2 #define REDIS_REUSEADDR 0x80
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
* SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10
/* strerror_r has two completely different prototypes and behaviors
* depending on system issues, so we need to operate on the error buffer
* differently depending on which strerror_r we're using. */
#ifndef _GNU_SOURCE
/* "regular" POSIX strerror_r that does the right thing. */
#define __redis_strerror_r(errno, buf, len) \
do { \
strerror_r((errno), (buf), (len)); \
} while (0)
#else
/* "bad" GNU strerror_r we need to clean up after. */
#define __redis_strerror_r(errno, buf, len) \
do { \
char *err_str = strerror_r((errno), (buf), (len)); \
/* If return value _isn't_ the start of the buffer we passed in, \
* then GNU strerror_r returned an internal static buffer and we \
* need to copy the result into our private buffer. */ \
if (err_str != (buf)) { \
strncpy((buf), err_str, ((len) - 1)); \
buf[(len)-1] = '\0'; \
} \
} while (0)
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
...@@ -98,61 +112,13 @@ extern "C" { ...@@ -98,61 +112,13 @@ extern "C" {
typedef struct redisReply { typedef struct redisReply {
int type; /* REDIS_REPLY_* */ int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
int len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply; } redisReply;
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createNil)(const redisReadTask*);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
/* State for the protocol parser */
typedef struct redisReader {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
char *buf; /* Read buffer */
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */
redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
/* Public API for the protocol parser. */
redisReader *redisReaderCreate(void); redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *r);
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
int redisReaderGetReply(redisReader *r, void **reply);
/* Backwards compatibility, can be removed on big version bump. */
#define redisReplyReaderCreate redisReaderCreate
#define redisReplyReaderFree redisReaderFree
#define redisReplyReaderFeed redisReaderFeed
#define redisReplyReaderGetReply redisReaderGetReply
#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
/* Function to free the reply objects hiredis returns by default. */ /* Function to free the reply objects hiredis returns by default. */
void freeReplyObject(void *reply); void freeReplyObject(void *reply);
...@@ -161,6 +127,14 @@ void freeReplyObject(void *reply); ...@@ -161,6 +127,14 @@ void freeReplyObject(void *reply);
int redisvFormatCommand(char **target, const char *format, va_list ap); int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommand(char **target, const char *format, ...);
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
void redisFreeCommand(char *cmd);
void redisFreeSdsCommand(sds cmd);
enum redisConnectionType {
REDIS_CONN_TCP,
REDIS_CONN_UNIX
};
/* Context for a connection to Redis */ /* Context for a connection to Redis */
typedef struct redisContext { typedef struct redisContext {
...@@ -170,16 +144,45 @@ typedef struct redisContext { ...@@ -170,16 +144,45 @@ typedef struct redisContext {
int flags; int flags;
char *obuf; /* Write buffer */ char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */ redisReader *reader; /* Protocol reader */
enum redisConnectionType connection_type;
struct timeval *timeout;
struct {
char *host;
char *source_addr;
int port;
} tcp;
struct {
char *path;
} unix_sock;
} redisContext; } redisContext;
redisContext *redisConnect(const char *ip, int port); redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port); redisContext *redisConnectNonBlock(const char *ip, int port);
redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr); redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr);
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr);
redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectUnixNonBlock(const char *path);
redisContext *redisConnectFd(int fd); redisContext *redisConnectFd(int fd);
/**
* Reconnect the given context using the saved information.
*
* This re-uses the exact same connect options as in the initial connection.
* host, ip (or path), timeout and bind address are reused,
* flags are used unmodified from the existing context.
*
* Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
*/
int redisReconnect(redisContext *c);
int redisSetTimeout(redisContext *c, const struct timeval tv); int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c); void redisFree(redisContext *c);
......
/* Extracted from anet.c to work properly with Hiredis error reporting. /* Extracted from anet.c to work properly with Hiredis error reporting.
* *
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -47,6 +49,7 @@ ...@@ -47,6 +49,7 @@
#include <stdio.h> #include <stdio.h>
#include <poll.h> #include <poll.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h>
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
...@@ -67,7 +70,7 @@ static void __redisSetErrorFromErrno(redisContext *c, int type, const char *pref ...@@ -67,7 +70,7 @@ static void __redisSetErrorFromErrno(redisContext *c, int type, const char *pref
if (prefix != NULL) if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix); len = snprintf(buf,sizeof(buf),"%s: ",prefix);
strerror_r(errno,buf+len,sizeof(buf)-len); __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf); __redisSetError(c,type,buf);
} }
...@@ -138,7 +141,7 @@ int redisKeepAlive(redisContext *c, int interval) { ...@@ -138,7 +141,7 @@ int redisKeepAlive(redisContext *c, int interval) {
return REDIS_ERR; return REDIS_ERR;
} }
#else #else
#ifndef __sun #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
val = interval; val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
...@@ -175,19 +178,15 @@ static int redisSetTcpNoDelay(redisContext *c) { ...@@ -175,19 +178,15 @@ static int redisSetTcpNoDelay(redisContext *c) {
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) #define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { static int redisContextTimeoutMsec(redisContext *c, long *result)
struct pollfd wfd[1]; {
long msec; const struct timeval *timeout = c->timeout;
long msec = -1;
msec = -1;
wfd[0].fd = c->fd;
wfd[0].events = POLLOUT;
/* Only use timeout when not NULL. */ /* Only use timeout when not NULL. */
if (timeout != NULL) { if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); *result = msec;
redisContextCloseFd(c);
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -198,6 +197,16 @@ static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) ...@@ -198,6 +197,16 @@ static int redisContextWaitReady(redisContext *c, const struct timeval *timeout)
} }
} }
*result = msec;
return REDIS_OK;
}
static int redisContextWaitReady(redisContext *c, long msec) {
struct pollfd wfd[1];
wfd[0].fd = c->fd;
wfd[0].events = POLLOUT;
if (errno == EINPROGRESS) { if (errno == EINPROGRESS) {
int res; int res;
...@@ -256,10 +265,57 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { ...@@ -256,10 +265,57 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout, const struct timeval *timeout,
const char *source_addr) { const char *source_addr) {
int s, rv; int s, rv, n;
char _port[6]; /* strlen("65535"); */ char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *bservinfo, *p, *b; struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
int reuseaddr = (c->flags & REDIS_REUSEADDR);
int reuses = 0;
long timeout_msec = -1;
servinfo = NULL;
c->connection_type = REDIS_CONN_TCP;
c->tcp.port = port;
/* We need to take possession of the passed parameters
* to make them reusable for a reconnect.
* We also carefully check we don't free data we already own,
* as in the case of the reconnect method.
*
* This is a bit ugly, but atleast it works and doesn't leak memory.
**/
if (c->tcp.host != addr) {
if (c->tcp.host)
free(c->tcp.host);
c->tcp.host = strdup(addr);
}
if (timeout) {
if (c->timeout != timeout) {
if (c->timeout == NULL)
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
if (c->timeout)
free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
goto error;
}
if (source_addr == NULL) {
free(c->tcp.source_addr);
c->tcp.source_addr = NULL;
} else if (c->tcp.source_addr != source_addr) {
free(c->tcp.source_addr);
c->tcp.source_addr = strdup(source_addr);
}
snprintf(_port, 6, "%d", port); snprintf(_port, 6, "%d", port);
memset(&hints,0,sizeof(hints)); memset(&hints,0,sizeof(hints));
...@@ -271,7 +327,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -271,7 +327,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* as this would add latency to every connect. Otherwise a more sensible * as this would add latency to every connect. Otherwise a more sensible
* route could be: Use IPv6 if both addresses are available and there is IPv6 * route could be: Use IPv6 if both addresses are available and there is IPv6
* connectivity. */ * connectivity. */
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
hints.ai_family = AF_INET6; hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
...@@ -279,21 +335,31 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -279,21 +335,31 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
} }
} }
for (p = servinfo; p != NULL; p = p->ai_next) { for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry:
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue; continue;
c->fd = s; c->fd = s;
if (redisSetBlocking(c,0) != REDIS_OK) if (redisSetBlocking(c,0) != REDIS_OK)
goto error; goto error;
if (source_addr) { if (c->tcp.source_addr) {
int bound = 0; int bound = 0;
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) { if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
char buf[128]; char buf[128];
snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
__redisSetError(c,REDIS_ERR_OTHER,buf); __redisSetError(c,REDIS_ERR_OTHER,buf);
goto error; goto error;
} }
if (reuseaddr) {
n = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
sizeof(n)) < 0) {
goto error;
}
}
for (b = bservinfo; b != NULL; b = b->ai_next) { for (b = bservinfo; b != NULL; b = b->ai_next) {
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
bound = 1; bound = 1;
...@@ -314,8 +380,15 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -314,8 +380,15 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
continue; continue;
} else if (errno == EINPROGRESS && !blocking) { } else if (errno == EINPROGRESS && !blocking) {
/* This is ok. */ /* This is ok. */
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error;
} else { } else {
if (redisContextWaitReady(c,timeout) != REDIS_OK) redisContextCloseFd(c);
goto addrretry;
}
} else {
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error; goto error;
} }
} }
...@@ -356,19 +429,40 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, ...@@ -356,19 +429,40 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
int blocking = (c->flags & REDIS_BLOCK); int blocking = (c->flags & REDIS_BLOCK);
struct sockaddr_un sa; struct sockaddr_un sa;
long timeout_msec = -1;
if (redisCreateSocket(c,AF_LOCAL) < 0) if (redisCreateSocket(c,AF_LOCAL) < 0)
return REDIS_ERR; return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK) if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
c->connection_type = REDIS_CONN_UNIX;
if (c->unix_sock.path != path)
c->unix_sock.path = strdup(path);
if (timeout) {
if (c->timeout != timeout) {
if (c->timeout == NULL)
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
if (c->timeout)
free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR;
sa.sun_family = AF_LOCAL; sa.sun_family = AF_LOCAL;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS && !blocking) { if (errno == EINPROGRESS && !blocking) {
/* This is ok. */ /* This is ok. */
} else { } else {
if (redisContextWaitReady(c,timeout) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
} }
} }
......
/* Extracted from anet.c to work properly with Hiredis error reporting. /* Extracted from anet.c to work properly with Hiredis error reporting.
* *
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
* Jan-Erik Rediger <janerik at fnordig dot com>
* *
* All rights reserved. * All rights reserved.
* *
...@@ -35,7 +37,7 @@ ...@@ -35,7 +37,7 @@
#include "hiredis.h" #include "hiredis.h"
#if defined(__sun) || defined(_AIX) #if defined(__sun)
#define AF_LOCAL AF_UNIX #define AF_LOCAL AF_UNIX
#endif #endif
......
/*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include <string.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include "read.h"
#include "sds.h"
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len;
if (r->reply != NULL && r->fn && r->fn->freeObject) {
r->fn->freeObject(r->reply);
r->reply = NULL;
}
/* Clear input buffer on errors. */
if (r->buf != NULL) {
sdsfree(r->buf);
r->buf = NULL;
r->pos = r->len = 0;
}
/* Reset task stack. */
r->ridx = -1;
/* Set error. */
r->err = type;
len = strlen(str);
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
memcpy(r->errstr,str,len);
r->errstr[len] = '\0';
}
static size_t chrtos(char *buf, size_t size, char byte) {
size_t len = 0;
switch(byte) {
case '\\':
case '"':
len = snprintf(buf,size,"\"\\%c\"",byte);
break;
case '\n': len = snprintf(buf,size,"\"\\n\""); break;
case '\r': len = snprintf(buf,size,"\"\\r\""); break;
case '\t': len = snprintf(buf,size,"\"\\t\""); break;
case '\a': len = snprintf(buf,size,"\"\\a\""); break;
case '\b': len = snprintf(buf,size,"\"\\b\""); break;
default:
if (isprint(byte))
len = snprintf(buf,size,"\"%c\"",byte);
else
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
break;
}
return len;
}
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
char cbuf[8], sbuf[128];
chrtos(cbuf,sizeof(cbuf),byte);
snprintf(sbuf,sizeof(sbuf),
"Protocol error, got %s as reply type byte", cbuf);
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
}
static void __redisReaderSetErrorOOM(redisReader *r) {
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
}
static char *readBytes(redisReader *r, unsigned int bytes) {
char *p;
if (r->len-r->pos >= bytes) {
p = r->buf+r->pos;
r->pos += bytes;
return p;
}
return NULL;
}
/* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) {
int pos = 0;
int _len = len-1;
/* Position should be < len-1 because the character at "pos" should be
* followed by a \n. Note that strchr cannot be used because it doesn't
* allow to search a limited length and the buffer that is being searched
* might not have a trailing NULL character. */
while (pos < _len) {
while(pos < _len && s[pos] != '\r') pos++;
if (pos==_len) {
/* Not found. */
return NULL;
} else {
if (s[pos+1] == '\n') {
/* Found. */
return s+pos;
} else {
/* Continue searching. */
pos++;
}
}
}
return NULL;
}
/* Read a long long value starting at *s, under the assumption that it will be
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
static long long readLongLong(char *s) {
long long v = 0;
int dec, mult = 1;
char c;
if (*s == '-') {
mult = -1;
s++;
} else if (*s == '+') {
mult = 1;
s++;
}
while ((c = *(s++)) != '\r') {
dec = c - '0';
if (dec >= 0 && dec < 10) {
v *= 10;
v += dec;
} else {
/* Should not happen... */
return -1;
}
}
return mult*v;
}
static char *readLine(redisReader *r, int *_len) {
char *p, *s;
int len;
p = r->buf+r->pos;
s = seekNewline(p,(r->len-r->pos));
if (s != NULL) {
len = s-(r->buf+r->pos);
r->pos += len+2; /* skip \r\n */
if (_len) *_len = len;
return p;
}
return NULL;
}
static void moveToNextTask(redisReader *r) {
redisReadTask *cur, *prv;
while (r->ridx >= 0) {
/* Return a.s.a.p. when the stack is now empty. */
if (r->ridx == 0) {
r->ridx--;
return;
}
cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
/* Reset the type because the next item can be anything */
assert(cur->idx < prv->elements);
cur->type = -1;
cur->elements = -1;
cur->idx++;
return;
}
}
}
static int processLineItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
int len;
if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger)
obj = r->fn->createInteger(cur,readLongLong(p));
else
obj = (void*)REDIS_REPLY_INTEGER;
} else {
/* Type will be error or status. */
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len);
else
obj = (void*)(size_t)(cur->type);
}
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return REDIS_OK;
}
return REDIS_ERR;
}
static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj = NULL;
char *p, *s;
long len;
unsigned long bytelen;
int success = 0;
p = r->buf+r->pos;
s = seekNewline(p,r->len-r->pos);
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
len = readLongLong(p);
if (len < 0) {
/* The nil object can always be created. */
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
success = 1;
} else {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) {
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len);
else
obj = (void*)REDIS_REPLY_STRING;
success = 1;
}
}
/* Proceed when obj was created. */
if (success) {
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->pos += bytelen;
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return REDIS_OK;
}
}
return REDIS_ERR;
}
static int processMultiBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
long elements;
int root = 0;
/* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"No support for nested multi bulk replies with depth > 7");
return REDIS_ERR;
}
if ((p = readLine(r,NULL)) != NULL) {
elements = readLongLong(p);
root = (r->ridx == 0);
if (elements == -1) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
moveToNextTask(r);
} else {
if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements);
else
obj = (void*)REDIS_REPLY_ARRAY;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
/* Modify task stack when there are more than 0 elements. */
if (elements > 0) {
cur->elements = elements;
cur->obj = obj;
r->ridx++;
r->rstack[r->ridx].type = -1;
r->rstack[r->ridx].elements = -1;
r->rstack[r->ridx].idx = 0;
r->rstack[r->ridx].obj = NULL;
r->rstack[r->ridx].parent = cur;
r->rstack[r->ridx].privdata = r->privdata;
} else {
moveToNextTask(r);
}
}
/* Set reply if this is the root object. */
if (root) r->reply = obj;
return REDIS_OK;
}
return REDIS_ERR;
}
static int processItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
char *p;
/* check if we need to read type */
if (cur->type < 0) {
if ((p = readBytes(r,1)) != NULL) {
switch (p[0]) {
case '-':
cur->type = REDIS_REPLY_ERROR;
break;
case '+':
cur->type = REDIS_REPLY_STATUS;
break;
case ':':
cur->type = REDIS_REPLY_INTEGER;
break;
case '$':
cur->type = REDIS_REPLY_STRING;
break;
case '*':
cur->type = REDIS_REPLY_ARRAY;
break;
default:
__redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR;
}
} else {
/* could not consume 1 byte */
return REDIS_ERR;
}
}
/* process typed item */
switch(cur->type) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER:
return processLineItem(r);
case REDIS_REPLY_STRING:
return processBulkItem(r);
case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r);
default:
assert(NULL);
return REDIS_ERR; /* Avoid warning. */
}
}
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r;
r = calloc(sizeof(redisReader),1);
if (r == NULL)
return NULL;
r->err = 0;
r->errstr[0] = '\0';
r->fn = fn;
r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF;
if (r->buf == NULL) {
free(r);
return NULL;
}
r->ridx = -1;
return r;
}
void redisReaderFree(redisReader *r) {
if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply);
if (r->buf != NULL)
sdsfree(r->buf);
free(r);
}
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
sds newbuf;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* Copy the provided buffer. */
if (buf != NULL && len >= 1) {
/* Destroy internal buffer when it is empty and is quite large. */
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf);
r->buf = sdsempty();
r->pos = 0;
/* r->buf should not be NULL since we just free'd a larger one. */
assert(r->buf != NULL);
}
newbuf = sdscatlen(r->buf,buf,len);
if (newbuf == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->buf = newbuf;
r->len = sdslen(r->buf);
}
return REDIS_OK;
}
int redisReaderGetReply(redisReader *r, void **reply) {
/* Default target pointer to NULL. */
if (reply != NULL)
*reply = NULL;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* When the buffer is empty, there will never be a reply. */
if (r->len == 0)
return REDIS_OK;
/* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
r->rstack[0].type = -1;
r->rstack[0].elements = -1;
r->rstack[0].idx = -1;
r->rstack[0].obj = NULL;
r->rstack[0].parent = NULL;
r->rstack[0].privdata = r->privdata;
r->ridx = 0;
}
/* Process items in reply. */
while (r->ridx >= 0)
if (processItem(r) != REDIS_OK)
break;
/* Return ASAP when an error occurred. */
if (r->err)
return REDIS_ERR;
/* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) {
sdsrange(r->buf,r->pos,-1);
r->pos = 0;
r->len = sdslen(r->buf);
}
/* Emit a reply when there is one. */
if (r->ridx == -1) {
if (reply != NULL)
*reply = r->reply;
r->reply = NULL;
}
return REDIS_OK;
}
/*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __HIREDIS_READ_H
#define __HIREDIS_READ_H
#include <stdio.h> /* for size_t */
#define REDIS_ERR -1
#define REDIS_OK 0
/* When an error occurs, the err flag in a context is set to hold the type of
* error that occurred. REDIS_ERR_IO means there was an I/O error and you
* should use the "errno" variable to find out what is wrong.
* For other values, the "errstr" field will hold a description. */
#define REDIS_ERR_IO 1 /* Error in read or write */
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
#ifdef __cplusplus
extern "C" {
#endif
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createNil)(const redisReadTask*);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
typedef struct redisReader {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
char *buf; /* Read buffer */
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */
redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
/* Public API for the protocol parser. */
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
void redisReaderFree(redisReader *r);
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
int redisReaderGetReply(redisReader *r, void **reply);
#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
#ifdef __cplusplus
}
#endif
#endif
/* SDSLib, A C dynamic strings library /* SDSLib 2.0 -- A C dynamic strings library
* *
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2015, Oran Agra
* Copyright (c) 2015, Redis Labs, Inc
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
...@@ -34,7 +36,35 @@ ...@@ -34,7 +36,35 @@
#include <ctype.h> #include <ctype.h>
#include <assert.h> #include <assert.h>
#include "sds.h" #include "sds.h"
#include "zmalloc.h" #include "sdsalloc.h"
static inline int sdsHdrSize(char type) {
switch(type&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return sizeof(struct sdshdr5);
case SDS_TYPE_8:
return sizeof(struct sdshdr8);
case SDS_TYPE_16:
return sizeof(struct sdshdr16);
case SDS_TYPE_32:
return sizeof(struct sdshdr32);
case SDS_TYPE_64:
return sizeof(struct sdshdr64);
}
return 0;
}
static inline char sdsReqType(size_t string_size) {
if (string_size < 32)
return SDS_TYPE_5;
if (string_size < 0xff)
return SDS_TYPE_8;
if (string_size < 0xffff)
return SDS_TYPE_16;
if (string_size < 0xffffffff)
return SDS_TYPE_32;
return SDS_TYPE_64;
}
/* Create a new sds string with the content specified by the 'init' pointer /* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'. * and 'initlen'.
...@@ -43,26 +73,65 @@ ...@@ -43,26 +73,65 @@
* The string is always null-termined (all the sds strings are, always) so * The string is always null-termined (all the sds strings are, always) so
* even if you create an sds string with: * even if you create an sds string with:
* *
* mystring = sdsnewlen("abc",3"); * mystring = sdsnewlen("abc",3);
* *
* You can print the string with printf() as there is an implicit \0 at the * You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain * end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */ * \0 characters in the middle, as the length is stored in the sds header. */
sds sdsnewlen(const void *init, size_t initlen) { sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh; void *sh;
sds s;
if (init) { char type = sdsReqType(initlen);
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); /* Empty strings are usually created in order to append. Use type 8
} else { * since type 5 is not good at this. */
sh = zcalloc(sizeof(struct sdshdr)+initlen+1); if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
} int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */
sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL; if (sh == NULL) return NULL;
if (!init)
memset(sh, 0, hdrlen+initlen+1);
s = (char*)sh+hdrlen;
fp = ((unsigned char*)s)-1;
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen; sh->len = initlen;
sh->free = 0; sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init) if (initlen && init)
memcpy(sh->buf, init, initlen); memcpy(s, init, initlen);
sh->buf[initlen] = '\0'; s[initlen] = '\0';
return (char*)sh->buf; return s;
} }
/* Create an empty (zero length) sds string. Even in this case the string /* Create an empty (zero length) sds string. Even in this case the string
...@@ -71,7 +140,7 @@ sds sdsempty(void) { ...@@ -71,7 +140,7 @@ sds sdsempty(void) {
return sdsnewlen("",0); return sdsnewlen("",0);
} }
/* Create a new sds string starting from a null termined C string. */ /* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) { sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init); size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen); return sdsnewlen(init, initlen);
...@@ -85,7 +154,7 @@ sds sdsdup(const sds s) { ...@@ -85,7 +154,7 @@ sds sdsdup(const sds s) {
/* Free an sds string. No operation is performed if 's' is NULL. */ /* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) { void sdsfree(sds s) {
if (s == NULL) return; if (s == NULL) return;
zfree(s-sizeof(struct sdshdr)); s_free((char*)s-sdsHdrSize(s[-1]));
} }
/* Set the sds string length to the length as obtained with strlen(), so /* Set the sds string length to the length as obtained with strlen(), so
...@@ -103,21 +172,17 @@ void sdsfree(sds s) { ...@@ -103,21 +172,17 @@ void sdsfree(sds s) {
* the output will be "6" as the string was modified but the logical length * the output will be "6" as the string was modified but the logical length
* remains 6 bytes. */ * remains 6 bytes. */
void sdsupdatelen(sds s) { void sdsupdatelen(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
int reallen = strlen(s); int reallen = strlen(s);
sh->free += (sh->len-reallen); sdssetlen(s, reallen);
sh->len = reallen;
} }
/* Modify an sds string on-place to make it empty (zero length). /* Modify an sds string in-place to make it empty (zero length).
* However all the existing buffer is not discarded but set as free space * However all the existing buffer is not discarded but set as free space
* so that next append operations will not require allocations up to the * so that next append operations will not require allocations up to the
* number of bytes previously available. */ * number of bytes previously available. */
void sdsclear(sds s) { void sdsclear(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); sdssetlen(s, 0);
sh->free += sh->len; s[0] = '\0';
sh->len = 0;
sh->buf[0] = '\0';
} }
/* Enlarge the free space at the end of the sds string so that the caller /* Enlarge the free space at the end of the sds string so that the caller
...@@ -127,23 +192,48 @@ void sdsclear(sds s) { ...@@ -127,23 +192,48 @@ void sdsclear(sds s) {
* Note: this does not change the *length* of the sds string as returned * Note: this does not change the *length* of the sds string as returned
* by sdslen(), but only the free buffer space we have. */ * by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) { sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh; void *sh, *newsh;
size_t free = sdsavail(s); size_t avail = sdsavail(s);
size_t len, newlen; size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;
if (free >= addlen) return s;
len = sdslen(s); len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr))); sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen); newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC) if (newlen < SDS_MAX_PREALLOC)
newlen *= 2; newlen *= 2;
else else
newlen += SDS_MAX_PREALLOC; newlen += SDS_MAX_PREALLOC;
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
newsh->free = newlen - len; type = sdsReqType(newlen);
return newsh->buf;
/* Don't use type 5: the user is appending to the string and type 5 is
* not able to remember empty space, so sdsMakeRoomFor() must be called
* at every appending operation. */
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, newlen);
return s;
} }
/* Reallocate the sds string so that it has no free space at the end. The /* Reallocate the sds string so that it has no free space at the end. The
...@@ -153,12 +243,29 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { ...@@ -153,12 +243,29 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
* After the call, the passed sds string is no longer valid and all the * After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */ * references must be substituted with the new pointer returned by the call. */
sds sdsRemoveFreeSpace(sds s) { sds sdsRemoveFreeSpace(sds s) {
struct sdshdr *sh; void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
sh = (void*) (s-(sizeof(struct sdshdr))); int hdrlen;
sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1); size_t len = sdslen(s);
sh->free = 0; sh = (char*)s-sdsHdrSize(oldtype);
return sh->buf;
type = sdsReqType(len);
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+len+1);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
newsh = s_malloc(hdrlen+len+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, len);
return s;
} }
/* Return the total size of the allocation of the specifed sds string, /* Return the total size of the allocation of the specifed sds string,
...@@ -169,9 +276,14 @@ sds sdsRemoveFreeSpace(sds s) { ...@@ -169,9 +276,14 @@ sds sdsRemoveFreeSpace(sds s) {
* 4) The implicit null term. * 4) The implicit null term.
*/ */
size_t sdsAllocSize(sds s) { size_t sdsAllocSize(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); size_t alloc = sdsalloc(s);
return sdsHdrSize(s[-1])+alloc+1;
}
return sizeof(*sh)+sh->len+sh->free+1; /* Return the pointer of the actual SDS allocation (normally SDS strings
* are referenced by the start of the string buffer). */
void *sdsAllocPtr(sds s) {
return (void*) (s-sdsHdrSize(s[-1]));
} }
/* Increment the sds length and decrements the left free space at the /* Increment the sds length and decrements the left free space at the
...@@ -198,15 +310,44 @@ size_t sdsAllocSize(sds s) { ...@@ -198,15 +310,44 @@ size_t sdsAllocSize(sds s) {
* sdsIncrLen(s, nread); * sdsIncrLen(s, nread);
*/ */
void sdsIncrLen(sds s, int incr) { void sdsIncrLen(sds s, int incr) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); unsigned char flags = s[-1];
size_t len;
if (incr >= 0) switch(flags&SDS_TYPE_MASK) {
assert(sh->free >= (unsigned int)incr); case SDS_TYPE_5: {
else unsigned char *fp = ((unsigned char*)s)-1;
assert(sh->len >= (unsigned int)(-incr)); unsigned char oldlen = SDS_TYPE_5_LEN(flags);
sh->len += incr; assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
sh->free -= incr; *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);
s[sh->len] = '\0'; len = oldlen+incr;
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
len = (sh->len += incr);
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
len = (sh->len += incr);
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
len = (sh->len += incr);
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
len = (sh->len += incr);
break;
}
default: len = 0; /* Just to avoid compilation warnings. */
}
s[len] = '\0';
} }
/* Grow the sds to have the specified length. Bytes that were not part of /* Grow the sds to have the specified length. Bytes that were not part of
...@@ -215,19 +356,15 @@ void sdsIncrLen(sds s, int incr) { ...@@ -215,19 +356,15 @@ void sdsIncrLen(sds s, int incr) {
* if the specified length is smaller than the current length, no operation * if the specified length is smaller than the current length, no operation
* is performed. */ * is performed. */
sds sdsgrowzero(sds s, size_t len) { sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); size_t curlen = sdslen(s);
size_t totlen, curlen = sh->len;
if (len <= curlen) return s; if (len <= curlen) return s;
s = sdsMakeRoomFor(s,len-curlen); s = sdsMakeRoomFor(s,len-curlen);
if (s == NULL) return NULL; if (s == NULL) return NULL;
/* Make sure added region doesn't contain garbage */ /* Make sure added region doesn't contain garbage */
sh = (void*)(s-(sizeof(struct sdshdr)));
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
totlen = sh->len+sh->free; sdssetlen(s, len);
sh->len = len;
sh->free = totlen-sh->len;
return s; return s;
} }
...@@ -237,15 +374,12 @@ sds sdsgrowzero(sds s, size_t len) { ...@@ -237,15 +374,12 @@ sds sdsgrowzero(sds s, size_t len) {
* After the call, the passed sds string is no longer valid and all the * After the call, the passed sds string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */ * references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) { sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s); size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len); s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL; if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len); memcpy(s+curlen, t, len);
sh->len = curlen+len; sdssetlen(s, curlen+len);
sh->free = sh->free-len;
s[curlen+len] = '\0'; s[curlen+len] = '\0';
return s; return s;
} }
...@@ -269,19 +403,13 @@ sds sdscatsds(sds s, const sds t) { ...@@ -269,19 +403,13 @@ sds sdscatsds(sds s, const sds t) {
/* Destructively modify the sds string 's' to hold the specified binary /* Destructively modify the sds string 's' to hold the specified binary
* safe string pointed by 't' of length 'len' bytes. */ * safe string pointed by 't' of length 'len' bytes. */
sds sdscpylen(sds s, const char *t, size_t len) { sds sdscpylen(sds s, const char *t, size_t len) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); if (sdsalloc(s) < len) {
size_t totlen = sh->free+sh->len; s = sdsMakeRoomFor(s,len-sdslen(s));
if (totlen < len) {
s = sdsMakeRoomFor(s,len-sh->len);
if (s == NULL) return NULL; if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
totlen = sh->free+sh->len;
} }
memcpy(s, t, len); memcpy(s, t, len);
s[len] = '\0'; s[len] = '\0';
sh->len = len; sdssetlen(s, len);
sh->free = totlen-len;
return s; return s;
} }
...@@ -295,7 +423,7 @@ sds sdscpy(sds s, const char *t) { ...@@ -295,7 +423,7 @@ sds sdscpy(sds s, const char *t) {
* conversion. 's' must point to a string with room for at least * conversion. 's' must point to a string with room for at least
* SDS_LLSTR_SIZE bytes. * SDS_LLSTR_SIZE bytes.
* *
* The function returns the lenght of the null-terminated string * The function returns the length of the null-terminated string
* representation stored at 's'. */ * representation stored at 's'. */
#define SDS_LLSTR_SIZE 21 #define SDS_LLSTR_SIZE 21
int sdsll2str(char *s, long long value) { int sdsll2str(char *s, long long value) {
...@@ -369,7 +497,7 @@ sds sdsfromlonglong(long long value) { ...@@ -369,7 +497,7 @@ sds sdsfromlonglong(long long value) {
return sdsnewlen(buf,len); return sdsnewlen(buf,len);
} }
/* Like sdscatpritf() but gets va_list instead of being variadic. */ /* Like sdscatprintf() but gets va_list instead of being variadic. */
sds sdscatvprintf(sds s, const char *fmt, va_list ap) { sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy; va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t; char staticbuf[1024], *buf = staticbuf, *t;
...@@ -378,7 +506,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { ...@@ -378,7 +506,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
/* We try to start using a static buffer for speed. /* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */ * If not possible we revert to heap allocation. */
if (buflen > sizeof(staticbuf)) { if (buflen > sizeof(staticbuf)) {
buf = zmalloc(buflen); buf = s_malloc(buflen);
if (buf == NULL) return NULL; if (buf == NULL) return NULL;
} else { } else {
buflen = sizeof(staticbuf); buflen = sizeof(staticbuf);
...@@ -390,11 +518,11 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { ...@@ -390,11 +518,11 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
buf[buflen-2] = '\0'; buf[buflen-2] = '\0';
va_copy(cpy,ap); va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy); vsnprintf(buf, buflen, fmt, cpy);
va_end(ap); va_end(cpy);
if (buf[buflen-2] != '\0') { if (buf[buflen-2] != '\0') {
if (buf != staticbuf) zfree(buf); if (buf != staticbuf) s_free(buf);
buflen *= 2; buflen *= 2;
buf = zmalloc(buflen); buf = s_malloc(buflen);
if (buf == NULL) return NULL; if (buf == NULL) return NULL;
continue; continue;
} }
...@@ -403,7 +531,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { ...@@ -403,7 +531,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
/* Finally concat the obtained string to the SDS string and return it. */ /* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf); t = sdscat(s, buf);
if (buf != staticbuf) zfree(buf); if (buf != staticbuf) s_free(buf);
return t; return t;
} }
...@@ -415,7 +543,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { ...@@ -415,7 +543,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
* *
* Example: * Example:
* *
* s = sdsempty("Sum is: "); * s = sdsnew("Sum is: ");
* s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
* *
* Often you need to create a string from scratch with the printf-alike * Often you need to create a string from scratch with the printf-alike
...@@ -449,25 +577,21 @@ sds sdscatprintf(sds s, const char *fmt, ...) { ...@@ -449,25 +577,21 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
* %% - Verbatim "%" character. * %% - Verbatim "%" character.
*/ */
sds sdscatfmt(sds s, char const *fmt, ...) { sds sdscatfmt(sds s, char const *fmt, ...) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t initlen = sdslen(s);
const char *f = fmt; const char *f = fmt;
int i; int i;
va_list ap; va_list ap;
va_start(ap,fmt); va_start(ap,fmt);
f = fmt; /* Next format specifier byte to process. */ i = sdslen(s); /* Position of the next byte to write to dest str. */
i = initlen; /* Position of the next byte to write to dest str. */
while(*f) { while(*f) {
char next, *str; char next, *str;
unsigned int l; size_t l;
long long num; long long num;
unsigned long long unum; unsigned long long unum;
/* Make sure there is always space for at least 1 char. */ /* Make sure there is always space for at least 1 char. */
if (sh->free == 0) { if (sdsavail(s)==0) {
s = sdsMakeRoomFor(s,1); s = sdsMakeRoomFor(s,1);
sh = (void*) (s-(sizeof(struct sdshdr)));
} }
switch(*f) { switch(*f) {
...@@ -479,13 +603,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -479,13 +603,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
case 'S': case 'S':
str = va_arg(ap,char*); str = va_arg(ap,char*);
l = (next == 's') ? strlen(str) : sdslen(str); l = (next == 's') ? strlen(str) : sdslen(str);
if (sh->free < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
} }
memcpy(s+i,str,l); memcpy(s+i,str,l);
sh->len += l; sdsinclen(s,l);
sh->free -= l;
i += l; i += l;
break; break;
case 'i': case 'i':
...@@ -497,13 +619,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -497,13 +619,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
{ {
char buf[SDS_LLSTR_SIZE]; char buf[SDS_LLSTR_SIZE];
l = sdsll2str(buf,num); l = sdsll2str(buf,num);
if (sh->free < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
} }
memcpy(s+i,buf,l); memcpy(s+i,buf,l);
sh->len += l; sdsinclen(s,l);
sh->free -= l;
i += l; i += l;
} }
break; break;
...@@ -516,27 +636,23 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -516,27 +636,23 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
{ {
char buf[SDS_LLSTR_SIZE]; char buf[SDS_LLSTR_SIZE];
l = sdsull2str(buf,unum); l = sdsull2str(buf,unum);
if (sh->free < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
sh = (void*) (s-(sizeof(struct sdshdr)));
} }
memcpy(s+i,buf,l); memcpy(s+i,buf,l);
sh->len += l; sdsinclen(s,l);
sh->free -= l;
i += l; i += l;
} }
break; break;
default: /* Handle %% and generally %<unknown>. */ default: /* Handle %% and generally %<unknown>. */
s[i++] = next; s[i++] = next;
sh->len += 1; sdsinclen(s,1);
sh->free -= 1;
break; break;
} }
break; break;
default: default:
s[i++] = *f; s[i++] = *f;
sh->len += 1; sdsinclen(s,1);
sh->free -= 1;
break; break;
} }
f++; f++;
...@@ -557,25 +673,23 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -557,25 +673,23 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
* Example: * Example:
* *
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); * s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
* s = sdstrim(s,"A. :"); * s = sdstrim(s,"Aa. :");
* printf("%s\n", s); * printf("%s\n", s);
* *
* Output will be just "Hello World". * Output will be just "Hello World".
*/ */
sds sdstrim(sds s, const char *cset) { sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep; char *start, *end, *sp, *ep;
size_t len; size_t len;
sp = start = s; sp = start = s;
ep = end = s+sdslen(s)-1; ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++; while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > start && strchr(cset, *ep)) ep--; while(ep > sp && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1); len = (sp > ep) ? 0 : ((ep-sp)+1);
if (sh->buf != sp) memmove(sh->buf, sp, len); if (s != sp) memmove(s, sp, len);
sh->buf[len] = '\0'; s[len] = '\0';
sh->free = sh->free+(sh->len-len); sdssetlen(s,len);
sh->len = len;
return s; return s;
} }
...@@ -596,7 +710,6 @@ sds sdstrim(sds s, const char *cset) { ...@@ -596,7 +710,6 @@ sds sdstrim(sds s, const char *cset) {
* sdsrange(s,1,-1); => "ello World" * sdsrange(s,1,-1); => "ello World"
*/ */
void sdsrange(sds s, int start, int end) { void sdsrange(sds s, int start, int end) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t newlen, len = sdslen(s); size_t newlen, len = sdslen(s);
if (len == 0) return; if (len == 0) return;
...@@ -619,10 +732,9 @@ void sdsrange(sds s, int start, int end) { ...@@ -619,10 +732,9 @@ void sdsrange(sds s, int start, int end) {
} else { } else {
start = 0; start = 0;
} }
if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); if (start && newlen) memmove(s, s+start, newlen);
sh->buf[newlen] = 0; s[newlen] = 0;
sh->free = sh->free+(sh->len-newlen); sdssetlen(s,newlen);
sh->len = newlen;
} }
/* Apply tolower() to every character of the sds string 's'. */ /* Apply tolower() to every character of the sds string 's'. */
...@@ -643,8 +755,8 @@ void sdstoupper(sds s) { ...@@ -643,8 +755,8 @@ void sdstoupper(sds s) {
* *
* Return value: * Return value:
* *
* 1 if s1 > s2. * positive if s1 > s2.
* -1 if s1 < s2. * negative if s1 < s2.
* 0 if s1 and s2 are exactly the same binary string. * 0 if s1 and s2 are exactly the same binary string.
* *
* If two strings share exactly the same prefix, but one of the two has * If two strings share exactly the same prefix, but one of the two has
...@@ -684,7 +796,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count ...@@ -684,7 +796,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
if (seplen < 1 || len < 0) return NULL; if (seplen < 1 || len < 0) return NULL;
tokens = zmalloc(sizeof(sds)*slots); tokens = s_malloc(sizeof(sds)*slots);
if (tokens == NULL) return NULL; if (tokens == NULL) return NULL;
if (len == 0) { if (len == 0) {
...@@ -697,7 +809,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count ...@@ -697,7 +809,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
sds *newtokens; sds *newtokens;
slots *= 2; slots *= 2;
newtokens = zrealloc(tokens,sizeof(sds)*slots); newtokens = s_realloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) goto cleanup; if (newtokens == NULL) goto cleanup;
tokens = newtokens; tokens = newtokens;
} }
...@@ -721,7 +833,7 @@ cleanup: ...@@ -721,7 +833,7 @@ cleanup:
{ {
int i; int i;
for (i = 0; i < elements; i++) sdsfree(tokens[i]); for (i = 0; i < elements; i++) sdsfree(tokens[i]);
zfree(tokens); s_free(tokens);
*count = 0; *count = 0;
return NULL; return NULL;
} }
...@@ -732,7 +844,7 @@ void sdsfreesplitres(sds *tokens, int count) { ...@@ -732,7 +844,7 @@ void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return; if (!tokens) return;
while(count--) while(count--)
sdsfree(tokens[count]); sdsfree(tokens[count]);
zfree(tokens); s_free(tokens);
} }
/* Append to the sds string "s" an escaped string representation where /* Append to the sds string "s" an escaped string representation where
...@@ -906,13 +1018,13 @@ sds *sdssplitargs(const char *line, int *argc) { ...@@ -906,13 +1018,13 @@ sds *sdssplitargs(const char *line, int *argc) {
if (*p) p++; if (*p) p++;
} }
/* add the token to the vector */ /* add the token to the vector */
vector = zrealloc(vector,((*argc)+1)*sizeof(char*)); vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
vector[*argc] = current; vector[*argc] = current;
(*argc)++; (*argc)++;
current = NULL; current = NULL;
} else { } else {
/* Even on empty input string return something not NULL. */ /* Even on empty input string return something not NULL. */
if (vector == NULL) vector = zmalloc(sizeof(void*)); if (vector == NULL) vector = s_malloc(sizeof(void*));
return vector; return vector;
} }
} }
...@@ -920,7 +1032,7 @@ sds *sdssplitargs(const char *line, int *argc) { ...@@ -920,7 +1032,7 @@ sds *sdssplitargs(const char *line, int *argc) {
err: err:
while((*argc)--) while((*argc)--)
sdsfree(vector[*argc]); sdsfree(vector[*argc]);
zfree(vector); s_free(vector);
if (current) sdsfree(current); if (current) sdsfree(current);
*argc = 0; *argc = 0;
return NULL; return NULL;
...@@ -962,14 +1074,35 @@ sds sdsjoin(char **argv, int argc, char *sep) { ...@@ -962,14 +1074,35 @@ sds sdsjoin(char **argv, int argc, char *sep) {
return join; return join;
} }
#ifdef SDS_TEST_MAIN /* Like sdsjoin, but joins an array of SDS strings. */
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
sds join = sdsempty();
int j;
for (j = 0; j < argc; j++) {
join = sdscatsds(join, argv[j]);
if (j != argc-1) join = sdscatlen(join,sep,seplen);
}
return join;
}
/* Wrappers to the allocators used by SDS. Note that SDS will actually
* just use the macros defined into sdsalloc.h in order to avoid to pay
* the overhead of function calls. Here we define these wrappers only for
* the programs SDS is linked to, if they want to touch the SDS internals
* even if they use a different allocator. */
void *sds_malloc(size_t size) { return s_malloc(size); }
void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); }
void sds_free(void *ptr) { s_free(ptr); }
#if defined(SDS_TEST_MAIN)
#include <stdio.h> #include <stdio.h>
#include "testhelp.h" #include "testhelp.h"
#include "limits.h" #include "limits.h"
int main(void) { #define UNUSED(x) (void)(x)
int sdsTest(void) {
{ {
struct sdshdr *sh;
sds x = sdsnew("foo"), y; sds x = sdsnew("foo"), y;
test_cond("Create a string and obtain the length", test_cond("Create a string and obtain the length",
...@@ -1005,6 +1138,7 @@ int main(void) { ...@@ -1005,6 +1138,7 @@ int main(void) {
sdslen(x) == 60 && sdslen(x) == 60 &&
memcmp(x,"--Hello Hi! World -9223372036854775808," memcmp(x,"--Hello Hi! World -9223372036854775808,"
"9223372036854775807--",60) == 0) "9223372036854775807--",60) == 0)
printf("[%s]\n",x);
sdsfree(x); sdsfree(x);
x = sdsnew("--"); x = sdsnew("--");
...@@ -1013,6 +1147,18 @@ int main(void) { ...@@ -1013,6 +1147,18 @@ int main(void) {
sdslen(x) == 35 && sdslen(x) == 35 &&
memcmp(x,"--4294967295,18446744073709551615--",35) == 0) memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
sdsfree(x);
x = sdsnew(" x ");
sdstrim(x," x");
test_cond("sdstrim() works when all chars match",
sdslen(x) == 0)
sdsfree(x);
x = sdsnew(" x ");
sdstrim(x," ");
test_cond("sdstrim() works when a single char remains",
sdslen(x) == 1 && x[0] == 'x')
sdsfree(x); sdsfree(x);
x = sdsnew("xxciaoyyy"); x = sdsnew("xxciaoyyy");
sdstrim(x,"xy"); sdstrim(x,"xy");
...@@ -1080,24 +1226,47 @@ int main(void) { ...@@ -1080,24 +1226,47 @@ int main(void) {
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
{ {
int oldfree; unsigned int oldfree;
char *p;
int step = 10, j, i;
sdsfree(x); sdsfree(x);
sdsfree(y);
x = sdsnew("0"); x = sdsnew("0");
sh = (void*) (x-(sizeof(struct sdshdr))); test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0);
test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
x = sdsMakeRoomFor(x,1); /* Run the test a few times in order to hit the first two
sh = (void*) (x-(sizeof(struct sdshdr))); * SDS header types. */
test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); for (i = 0; i < 10; i++) {
oldfree = sh->free; int oldlen = sdslen(x);
x[1] = '1'; x = sdsMakeRoomFor(x,step);
sdsIncrLen(x,1); int type = x[-1]&SDS_TYPE_MASK;
test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
test_cond("sdsIncrLen() -- len", sh->len == 2); test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen);
test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); if (type != SDS_TYPE_5) {
test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step);
oldfree = sdsavail(x);
}
p = x+oldlen;
for (j = 0; j < step; j++) {
p[j] = 'A'+j;
}
sdsIncrLen(x,step);
}
test_cond("sdsMakeRoomFor() content",
memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0);
test_cond("sdsMakeRoomFor() final length",sdslen(x)==101);
sdsfree(x);
} }
} }
test_report() test_report()
return 0; return 0;
} }
#endif #endif
#ifdef SDS_TEST_MAIN
int main(void) {
return sdsTest();
}
#endif
/* SDSLib, A C dynamic strings library /* SDSLib 2.0 -- A C dynamic strings library
* *
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2015, Oran Agra
* Copyright (c) 2015, Redis Labs, Inc
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
...@@ -35,32 +37,188 @@ ...@@ -35,32 +37,188 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h>
typedef char *sds; typedef char *sds;
struct sdshdr { /* Note: sdshdr5 is never used, we just access the flags byte directly.
unsigned int len; * However is here to document the layout of type 5 SDS strings. */
unsigned int free; struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[]; char buf[];
}; };
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
static inline size_t sdslen(const sds s) { static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); unsigned char flags = s[-1];
return sh->len; switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
} }
static inline size_t sdsavail(const sds s) { static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); unsigned char flags = s[-1];
return sh->free; switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5: {
return 0;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
return sh->alloc - sh->len;
}
}
return 0;
}
static inline void sdssetlen(sds s, size_t newlen) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen;
break;
}
}
static inline void sdsinclen(sds s, size_t inc) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc;
break;
}
}
/* sdsalloc() = sdsavail() + sdslen() */
static inline size_t sdsalloc(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->alloc;
case SDS_TYPE_16:
return SDS_HDR(16,s)->alloc;
case SDS_TYPE_32:
return SDS_HDR(32,s)->alloc;
case SDS_TYPE_64:
return SDS_HDR(64,s)->alloc;
}
return 0;
}
static inline void sdssetalloc(sds s, size_t newlen) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
/* Nothing to do, this type has no total allocation info. */
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen;
break;
}
} }
sds sdsnewlen(const void *init, size_t initlen); sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init); sds sdsnew(const char *init);
sds sdsempty(void); sds sdsempty(void);
size_t sdslen(const sds s);
sds sdsdup(const sds s); sds sdsdup(const sds s);
void sdsfree(sds s); void sdsfree(sds s);
size_t sdsavail(const sds s);
sds sdsgrowzero(sds s, size_t len); sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len); sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t); sds sdscat(sds s, const char *t);
...@@ -91,11 +249,25 @@ sds sdscatrepr(sds s, const char *p, size_t len); ...@@ -91,11 +249,25 @@ sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc); sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep); sds sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
/* Low level functions exposed to the user API */ /* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen); sds sdsMakeRoomFor(sds s, size_t addlen);
void sdsIncrLen(sds s, int incr); void sdsIncrLen(sds s, int incr);
sds sdsRemoveFreeSpace(sds s); sds sdsRemoveFreeSpace(sds s);
size_t sdsAllocSize(sds s); size_t sdsAllocSize(sds s);
void *sdsAllocPtr(sds s);
/* Export the allocator used by SDS to the program using SDS.
* Sometimes the program SDS is linked to, may use a different set of
* allocators, but may want to allocate or free things that SDS will
* respectively free or allocate. */
void *sds_malloc(size_t size);
void *sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr);
#ifdef REDIS_TEST
int sdsTest(int argc, char *argv[]);
#endif
#endif #endif
/* SDSLib 2.0 -- A C dynamic strings library
*
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2015, Oran Agra
* Copyright (c) 2015, Redis Labs, Inc
* 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.
*/
/* SDS allocator selection.
*
* This file is used in order to change the SDS allocator at compile time.
* Just define the following defines to what you want to use. Also add
* the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */
#define s_malloc malloc
#define s_realloc realloc
#define s_free free
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <limits.h> #include <limits.h>
#include "hiredis.h" #include "hiredis.h"
#include "net.h"
enum connection_type { enum connection_type {
CONN_TCP, CONN_TCP,
...@@ -29,7 +30,7 @@ struct config { ...@@ -29,7 +30,7 @@ struct config {
struct { struct {
const char *path; const char *path;
} unix; } unix_sock;
}; };
/* The following lines make up our testing "framework" :) */ /* The following lines make up our testing "framework" :) */
...@@ -43,6 +44,13 @@ static long long usec(void) { ...@@ -43,6 +44,13 @@ static long long usec(void) {
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
} }
/* The assert() calls below have side effects, so we need assert()
* even if we are compiling without asserts (-DNDEBUG). */
#ifdef NDEBUG
#undef assert
#define assert(e) (void)(e)
#endif
static redisContext *select_database(redisContext *c) { static redisContext *select_database(redisContext *c) {
redisReply *reply; redisReply *reply;
...@@ -51,7 +59,7 @@ static redisContext *select_database(redisContext *c) { ...@@ -51,7 +59,7 @@ static redisContext *select_database(redisContext *c) {
assert(reply != NULL); assert(reply != NULL);
freeReplyObject(reply); freeReplyObject(reply);
/* Make sure the DB is empty */ /* Make sure the DB is emtpy */
reply = redisCommand(c,"DBSIZE"); reply = redisCommand(c,"DBSIZE");
assert(reply != NULL); assert(reply != NULL);
if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
...@@ -89,10 +97,10 @@ static redisContext *connect(struct config config) { ...@@ -89,10 +97,10 @@ static redisContext *connect(struct config config) {
if (config.type == CONN_TCP) { if (config.type == CONN_TCP) {
c = redisConnect(config.tcp.host, config.tcp.port); c = redisConnect(config.tcp.host, config.tcp.port);
} else if (config.type == CONN_UNIX) { } else if (config.type == CONN_UNIX) {
c = redisConnectUnix(config.unix.path); c = redisConnectUnix(config.unix_sock.path);
} else if (config.type == CONN_FD) { } else if (config.type == CONN_FD) {
/* Create a dummy connection just to get an fd to inherit */ /* Create a dummy connection just to get an fd to inherit */
redisContext *dummy_ctx = redisConnectUnix(config.unix.path); redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
if (dummy_ctx) { if (dummy_ctx) {
int fd = disconnect(dummy_ctx, 1); int fd = disconnect(dummy_ctx, 1);
printf("Connecting to inherited fd %d\n", fd); printf("Connecting to inherited fd %d\n", fd);
...@@ -107,6 +115,7 @@ static redisContext *connect(struct config config) { ...@@ -107,6 +115,7 @@ static redisContext *connect(struct config config) {
exit(1); exit(1);
} else if (c->err) { } else if (c->err) {
printf("Connection error: %s\n", c->errstr); printf("Connection error: %s\n", c->errstr);
redisFree(c);
exit(1); exit(1);
} }
...@@ -215,6 +224,22 @@ static void test_format_commands(void) { ...@@ -215,6 +224,22 @@ static void test_format_commands(void) {
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2)); len == 4+4+(3+2)+4+(7+2)+4+(3+2));
free(cmd); free(cmd);
sds sds_cmd;
sds_cmd = sdsempty();
test("Format command into sds by passing argc/argv without lengths: ");
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
sdsfree(sds_cmd);
sds_cmd = sdsempty();
test("Format command into sds by passing argc/argv with lengths: ");
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
sdsfree(sds_cmd);
} }
static void test_append_formatted_commands(struct config config) { static void test_append_formatted_commands(struct config config) {
...@@ -318,16 +343,31 @@ static void test_reply_reader(void) { ...@@ -318,16 +343,31 @@ static void test_reply_reader(void) {
redisReaderFree(reader); redisReaderFree(reader);
} }
static void test_free_null(void) {
void *redisCtx = NULL;
void *reply = NULL;
test("Don't fail when redisFree is passed a NULL value: ");
redisFree(redisCtx);
test_cond(redisCtx == NULL);
test("Don't fail when freeReplyObject is passed a NULL value: ");
freeReplyObject(reply);
test_cond(reply == NULL);
}
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c; redisContext *c;
test("Returns error when host cannot be resolved: "); test("Returns error when host cannot be resolved: ");
c = redisConnect((char*)"idontexist.local", 6379); c = redisConnect((char*)"idontexist.test", 6379);
test_cond(c->err == REDIS_ERR_OTHER && test_cond(c->err == REDIS_ERR_OTHER &&
(strcmp(c->errstr,"Name or service not known") == 0 || (strcmp(c->errstr,"Name or service not known") == 0 ||
strcmp(c->errstr,"Can't resolve: idontexist.local") == 0 || strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr,"No address associated with hostname") == 0 || strcmp(c->errstr,"No address associated with hostname") == 0 ||
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
strcmp(c->errstr,"no address associated with name") == 0)); strcmp(c->errstr,"no address associated with name") == 0));
redisFree(c); redisFree(c);
...@@ -337,7 +377,7 @@ static void test_blocking_connection_errors(void) { ...@@ -337,7 +377,7 @@ static void test_blocking_connection_errors(void) {
strcmp(c->errstr,"Connection refused") == 0); strcmp(c->errstr,"Connection refused") == 0);
redisFree(c); redisFree(c);
test("Returns error when the unix socket path doesn't accept connections: "); test("Returns error when the unix_sock socket path doesn't accept connections: ");
c = redisConnectUnix((char*)"/tmp/idontexist.sock"); c = redisConnectUnix((char*)"/tmp/idontexist.sock");
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
redisFree(c); redisFree(c);
...@@ -421,6 +461,52 @@ static void test_blocking_connection(struct config config) { ...@@ -421,6 +461,52 @@ static void test_blocking_connection(struct config config) {
disconnect(c, 0); disconnect(c, 0);
} }
static void test_blocking_connection_timeouts(struct config config) {
redisContext *c;
redisReply *reply;
ssize_t s;
const char *cmd = "DEBUG SLEEP 3\r\n";
struct timeval tv;
c = connect(config);
test("Successfully completes a command when the timeout is not exceeded: ");
reply = redisCommand(c,"SET foo fast");
freeReplyObject(reply);
tv.tv_sec = 0;
tv.tv_usec = 10000;
redisSetTimeout(c, tv);
reply = redisCommand(c, "GET foo");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
freeReplyObject(reply);
disconnect(c, 0);
c = connect(config);
test("Does not return a reply when the command times out: ");
s = write(c->fd, cmd, strlen(cmd));
tv.tv_sec = 0;
tv.tv_usec = 10000;
redisSetTimeout(c, tv);
reply = redisCommand(c, "GET foo");
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
freeReplyObject(reply);
test("Reconnect properly reconnects after a timeout: ");
redisReconnect(c);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply);
test("Reconnect properly uses owned parameters: ");
config.tcp.host = "foo";
config.unix_sock.path = "foo";
redisReconnect(c);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply);
disconnect(c, 0);
}
static void test_blocking_io_errors(struct config config) { static void test_blocking_io_errors(struct config config) {
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
...@@ -444,7 +530,7 @@ static void test_blocking_io_errors(struct config config) { ...@@ -444,7 +530,7 @@ static void test_blocking_io_errors(struct config config) {
test("Returns I/O error when the connection is lost: "); test("Returns I/O error when the connection is lost: ");
reply = redisCommand(c,"QUIT"); reply = redisCommand(c,"QUIT");
if (major >= 2 && minor > 0) { if (major > 2 || (major == 2 && minor > 0)) {
/* > 2.0 returns OK on QUIT and read() should be issued once more /* > 2.0 returns OK on QUIT and read() should be issued once more
* to know the descriptor is at EOF. */ * to know the descriptor is at EOF. */
test_cond(strcasecmp(reply->str,"OK") == 0 && test_cond(strcasecmp(reply->str,"OK") == 0 &&
...@@ -482,7 +568,8 @@ static void test_invalid_timeout_errors(struct config config) { ...@@ -482,7 +568,8 @@ static void test_invalid_timeout_errors(struct config config) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
test_cond(c->err == REDIS_ERR_IO); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c);
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
...@@ -491,8 +578,7 @@ static void test_invalid_timeout_errors(struct config config) { ...@@ -491,8 +578,7 @@ static void test_invalid_timeout_errors(struct config config) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
test_cond(c->err == REDIS_ERR_IO); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
} }
...@@ -666,7 +752,7 @@ int main(int argc, char **argv) { ...@@ -666,7 +752,7 @@ int main(int argc, char **argv) {
.host = "127.0.0.1", .host = "127.0.0.1",
.port = 6379 .port = 6379
}, },
.unix = { .unix_sock = {
.path = "/tmp/redis.sock" .path = "/tmp/redis.sock"
} }
}; };
...@@ -687,7 +773,7 @@ int main(int argc, char **argv) { ...@@ -687,7 +773,7 @@ int main(int argc, char **argv) {
cfg.tcp.port = atoi(argv[0]); cfg.tcp.port = atoi(argv[0]);
} else if (argc >= 2 && !strcmp(argv[0],"-s")) { } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
argv++; argc--; argv++; argc--;
cfg.unix.path = argv[0]; cfg.unix_sock.path = argv[0];
} else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
throughput = 0; throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
...@@ -702,27 +788,31 @@ int main(int argc, char **argv) { ...@@ -702,27 +788,31 @@ int main(int argc, char **argv) {
test_format_commands(); test_format_commands();
test_reply_reader(); test_reply_reader();
test_blocking_connection_errors(); test_blocking_connection_errors();
test_free_null();
printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
cfg.type = CONN_TCP; cfg.type = CONN_TCP;
test_blocking_connection(cfg); test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg); test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_UNIX; cfg.type = CONN_UNIX;
test_blocking_connection(cfg); test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
if (test_inherit_fd) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_FD; cfg.type = CONN_FD;
test_blocking_connection(cfg); test_blocking_connection(cfg);
} }
if (fails) { if (fails) {
printf("*** %d TESTS FAILED ***\n", fails); printf("*** %d TESTS FAILED ***\n", fails);
return 1; return 1;
......
#ifndef _WIN32_HELPER_INCLUDE
#define _WIN32_HELPER_INCLUDE
#ifdef _MSC_VER
#ifndef inline
#define inline __inline
#endif
#ifndef va_copy
#define va_copy(d,s) ((d) = (s))
#endif
#ifndef snprintf
#define snprintf c99_snprintf
__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
{
int count = -1;
if (size != 0)
count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
if (count == -1)
count = _vscprintf(format, ap);
return count;
}
__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
{
int count;
va_list ap;
va_start(ap, format);
count = c99_vsnprintf(str, size, format, ap);
va_end(ap);
return count;
}
#endif
#endif
#endif
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment