diff --git a/.gitignore b/.gitignore index 62fba48..e6cb2c5 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,8 @@ dkms.conf *.xo .idea +CMakeCache.txt +cmake_install.cmake +CMakeFiles +build +*.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5944c61 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.4.0) + +project(migrate) + +set(build_type "Release") + +if(DEBUG STREQUAL "true") + message(NOTICE "-- Open debug mod") + set(build_type "Debug") + set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb") + set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") +endif() + +set(CMAKE_BUILD_TYPE ${build_type}) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_DEBUG_POSTFIX d) + +set(migrate_sources + src/hiredis/alloc.c + src/hiredis/async.c + src/hiredis/dict.c + src/hiredis/hiredis.c + src/hiredis/net.c + src/hiredis/read.c + src/hiredis/sds.c + src/hiredis/sockcompat.c + src/log.cpp + src/redis-migrate.cpp) + +set(migrate_sources ${migrate_sources}) + +add_library(migrate SHARED ${migrate_sources}) + + diff --git a/Makefile b/Makefile deleted file mode 100644 index 9bb6ec2..0000000 --- a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ - -default: all - -.DEFAULT: - cd src && $(MAKE) $@ - - diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index ade1381..0000000 --- a/src/Makefile +++ /dev/null @@ -1,51 +0,0 @@ - -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') - -TARGET_NAME=redis-migrate.so -WARN=-Wall -W -Wno-missing-field-initializers -NODEPS:=clean -# Compile flags for linux / osx -ifeq ($(uname_S),Linux) - SHOBJ_CFLAGS ?= -W -Wall -fno-common -fPIC $(DEBUG) -std=gnu99 -O2 $(WARN) $(OPTIMIZATION) - SHOBJ_LDFLAGS ?= -shared -else - SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -fPIC $(DEBUG) -std=gnu99 -O2 $(WARN) $(OPTIMIZATION) - SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup -endif - -FINAL_CFLAGS+= $(SHOBJ_CFLAGS) - -.PHONY: all - -.SUFFIXES: .c .so .xo .o - -all: $(TARGET_NAME) - @echo "" - @echo "Hint: build redis-migrate success ;)" - @echo "" - -.c.xo: - $(CC) -I. $(CFLAGS) $(FINAL_CFLAGS) -fPIC -c $< -o $@ - -sds.xo: fmacros.h sds.h sdsalloc.h -ssl.xo: hiredis.h async.h -async.xo: fmacros.h alloc.h net.h sds.h async.h -net.xo: fmacros.h net.h sds.h -ae.xo: ae.h hiredis.h async.h -read.xo: fmacros.h alloc.h sds.h -alloc.xo: fmacros.h alloc.h -dict.xo: fmacros.h alloc.h dict.h -ae.xo: redismodule.h -hiredis.xo:fmacros.h hiredis.h net.h sds.h alloc.h async.h -syncio.xo:redis-migrate.c -log.xo: log.h -rdbLoad.xo: rdbLoad.h -redis-migrate.xo: redismodule.h - -XO_LIBS=redis-migrate.xo hiredis.xo sds.xo ssl.xo read.xo alloc.xo dict.xo async.xo net.xo ae.xo syncio.xo log.xo rdbLoad.xo - -redis-migrate.so: $(XO_LIBS) - $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc - -clean: - rm -rf *.xo *.so *.o diff --git a/src/ae.c b/src/ae.c deleted file mode 100644 index d12189a..0000000 --- a/src/ae.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "ae.h" -#include -#include - -int aeWait(int fd, int mask, long long milliseconds) { - struct pollfd pfd; - int retmask = 0, retval; - - memset(&pfd, 0, sizeof(pfd)); - pfd.fd = fd; - if (mask & AE_READABLE) pfd.events |= POLLIN; - if (mask & AE_WRITABLE) pfd.events |= POLLOUT; - - if ((retval = poll(&pfd, 1, milliseconds))== 1) { - if (pfd.revents & POLLIN) retmask |= AE_READABLE; - if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; - if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; - if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; - return retmask; - } else { - return retval; - } -} - - diff --git a/src/ae.h b/src/ae.h deleted file mode 100644 index 90f460a..0000000 --- a/src/ae.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef __HIREDIS_AE_H__ -#define __HIREDIS_AE_H__ - -#include "hiredis.h" -#include "async.h" - -#define AE_READABLE 1 /* Fire when descriptor is readable. */ -#define AE_WRITABLE 2 /* Fire when descriptor is writable. */ - - -int aeWait(int fd, int mask, long long milliseconds); - -#endif diff --git a/src/alloc.c b/src/alloc.c deleted file mode 100644 index 7c2382f..0000000 --- a/src/alloc.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2020, Michael Grunder - * - * 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 "alloc.h" -#include -#include - -hiredisAllocFuncs hiredisAllocFns = { - .mallocFn = malloc, - .callocFn = calloc, - .reallocFn = realloc, - .strdupFn = strdup, - .freeFn = free, -}; - -/* Override hiredis' allocators with ones supplied by the user */ -hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) -{ - hiredisAllocFuncs orig = hiredisAllocFns; - - hiredisAllocFns = *override; - - return orig; -} - -/* Reset allocators to use libc defaults */ -void hiredisResetAllocators(void) -{ - hiredisAllocFns = (hiredisAllocFuncs){ - .mallocFn = malloc, - .callocFn = calloc, - .reallocFn = realloc, - .strdupFn = strdup, - .freeFn = free, - }; -} - -#ifdef _WIN32 - -void *hi_malloc(size_t size) -{ - return hiredisAllocFns.mallocFn(size); -} - -void *hi_calloc(size_t nmemb, size_t size) -{ - /* Overflow check as the user can specify any arbitrary allocator */ - if (SIZE_MAX / size < nmemb) - return NULL; - - return hiredisAllocFns.callocFn(nmemb, size); -} - -void *hi_realloc(void *ptr, size_t size) -{ - return hiredisAllocFns.reallocFn(ptr, size); -} - -char *hi_strdup(const char *str) -{ - return hiredisAllocFns.strdupFn(str); -} - -void hi_free(void *ptr) -{ - hiredisAllocFns.freeFn(ptr); -} - -#endif diff --git a/src/alloc.h b/src/alloc.h deleted file mode 100644 index 771f9fe..0000000 --- a/src/alloc.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2020, Michael Grunder - * - * 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_ALLOC_H -#define HIREDIS_ALLOC_H - -#include /* for size_t */ -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Structure pointing to our actually configured allocators */ -typedef struct hiredisAllocFuncs { - void *(*mallocFn)(size_t); - void *(*callocFn)(size_t,size_t); - void *(*reallocFn)(void*,size_t); - char *(*strdupFn)(const char*); - void (*freeFn)(void*); -} hiredisAllocFuncs; - -hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha); -void hiredisResetAllocators(void); - -#ifndef _WIN32 - -/* Hiredis' configured allocator function pointer struct */ -extern hiredisAllocFuncs hiredisAllocFns; - -static inline void *hi_malloc(size_t size) { - return hiredisAllocFns.mallocFn(size); -} - -static inline void *hi_calloc(size_t nmemb, size_t size) { - /* Overflow check as the user can specify any arbitrary allocator */ - if (SIZE_MAX / size < nmemb) - return NULL; - - return hiredisAllocFns.callocFn(nmemb, size); -} - -static inline void *hi_realloc(void *ptr, size_t size) { - return hiredisAllocFns.reallocFn(ptr, size); -} - -static inline char *hi_strdup(const char *str) { - return hiredisAllocFns.strdupFn(str); -} - -static inline void hi_free(void *ptr) { - hiredisAllocFns.freeFn(ptr); -} - -#else - -void *hi_malloc(size_t size); -void *hi_calloc(size_t nmemb, size_t size); -void *hi_realloc(void *ptr, size_t size); -char *hi_strdup(const char *str); -void hi_free(void *ptr); - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* HIREDIS_ALLOC_H */ diff --git a/src/async.c b/src/async.c deleted file mode 100644 index 9d1486f..0000000 --- a/src/async.c +++ /dev/null @@ -1,908 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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 "alloc.h" -#include -#include -#ifndef _MSC_VER -#include -#endif -#include -#include -#include -#include "async.h" -#include "net.h" -#include "dict.c" -#include "sds.h" - -#include "async_private.h" - -#ifdef NDEBUG -#undef assert -#define assert(e) (void)(e) -#endif - -/* Forward declarations of hiredis.c functions */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); -void __redisSetError(redisContext *c, int type, const char *str); - -/* Functions managing dictionary of callbacks for pub/sub. */ -static unsigned int callbackHash(const void *key) { - return dictGenHashFunction((const unsigned char *)key, - hi_sdslen((const hisds)key)); -} - -static void *callbackValDup(void *privdata, const void *src) { - ((void) privdata); - redisCallback *dup; - - dup = hi_malloc(sizeof(*dup)); - if (dup == NULL) - return NULL; - - memcpy(dup,src,sizeof(*dup)); - return dup; -} - -static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { - int l1, l2; - ((void) privdata); - - l1 = hi_sdslen((const hisds)key1); - l2 = hi_sdslen((const hisds)key2); - if (l1 != l2) return 0; - return memcmp(key1,key2,l1) == 0; -} - -static void callbackKeyDestructor(void *privdata, void *key) { - ((void) privdata); - hi_sdsfree((hisds)key); -} - -static void callbackValDestructor(void *privdata, void *val) { - ((void) privdata); - hi_free(val); -} - -static dictType callbackDict = { - callbackHash, - NULL, - callbackValDup, - callbackKeyCompare, - callbackKeyDestructor, - callbackValDestructor -}; - -static redisAsyncContext *redisAsyncInitialize(redisContext *c) { - redisAsyncContext *ac; - dict *channels = NULL, *patterns = NULL; - - channels = dictCreate(&callbackDict,NULL); - if (channels == NULL) - goto oom; - - patterns = dictCreate(&callbackDict,NULL); - if (patterns == NULL) - goto oom; - - ac = hi_realloc(c,sizeof(redisAsyncContext)); - if (ac == NULL) - goto oom; - - c = &(ac->c); - - /* The regular connect functions will always set the flag REDIS_CONNECTED. - * For the async API, we want to wait until the first write event is - * received up before setting this flag, so reset it here. */ - c->flags &= ~REDIS_CONNECTED; - - ac->err = 0; - ac->errstr = NULL; - ac->data = NULL; - ac->dataCleanup = NULL; - - ac->ev.data = NULL; - ac->ev.addRead = NULL; - ac->ev.delRead = NULL; - ac->ev.addWrite = NULL; - ac->ev.delWrite = NULL; - ac->ev.cleanup = NULL; - ac->ev.scheduleTimer = NULL; - - ac->onConnect = NULL; - ac->onDisconnect = NULL; - - ac->replies.head = NULL; - ac->replies.tail = NULL; - ac->sub.replies.head = NULL; - ac->sub.replies.tail = NULL; - ac->sub.channels = channels; - ac->sub.patterns = patterns; - - return ac; -oom: - if (channels) dictRelease(channels); - if (patterns) dictRelease(patterns); - return NULL; -} - -/* We want the error field to be accessible directly instead of requiring - * an indirection to the redisContext struct. */ -static void __redisAsyncCopyError(redisAsyncContext *ac) { - if (!ac) - return; - - redisContext *c = &(ac->c); - ac->err = c->err; - ac->errstr = c->errstr; -} - -redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) { - redisOptions myOptions = *options; - redisContext *c; - redisAsyncContext *ac; - - /* Clear any erroneously set sync callback and flag that we don't want to - * use freeReplyObject by default. */ - myOptions.push_cb = NULL; - myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE; - - myOptions.options |= REDIS_OPT_NONBLOCK; - c = redisConnectWithOptions(&myOptions); - if (c == NULL) { - return NULL; - } - - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - - /* Set any configured async push handler */ - redisAsyncSetPushCallback(ac, myOptions.async_push_cb); - - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - return redisAsyncConnectWithOptions(&options); -} - -redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.endpoint.tcp.source_addr = source_addr; - return redisAsyncConnectWithOptions(&options); -} - -redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.options |= REDIS_OPT_REUSEADDR; - options.endpoint.tcp.source_addr = source_addr; - return redisAsyncConnectWithOptions(&options); -} - -redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - return redisAsyncConnectWithOptions(&options); -} - -int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { - if (ac->onConnect == NULL) { - ac->onConnect = fn; - - /* The common way to detect an established connection is to wait for - * the first write event to be fired. This assumes the related event - * library functions are already set. */ - _EL_ADD_WRITE(ac); - return REDIS_OK; - } - return REDIS_ERR; -} - -int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { - if (ac->onDisconnect == NULL) { - ac->onDisconnect = fn; - return REDIS_OK; - } - return REDIS_ERR; -} - -/* Helper functions to push/shift callbacks */ -static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { - redisCallback *cb; - - /* Copy callback from stack to heap */ - cb = hi_malloc(sizeof(*cb)); - if (cb == NULL) - return REDIS_ERR_OOM; - - if (source != NULL) { - memcpy(cb,source,sizeof(*cb)); - cb->next = NULL; - } - - /* Store callback in list */ - if (list->head == NULL) - list->head = cb; - if (list->tail != NULL) - list->tail->next = cb; - list->tail = cb; - return REDIS_OK; -} - -static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { - redisCallback *cb = list->head; - if (cb != NULL) { - list->head = cb->next; - if (cb == list->tail) - list->tail = NULL; - - /* Copy callback from heap to stack */ - if (target != NULL) - memcpy(target,cb,sizeof(*cb)); - hi_free(cb); - return REDIS_OK; - } - return REDIS_ERR; -} - -static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { - redisContext *c = &(ac->c); - if (cb->fn != NULL) { - c->flags |= REDIS_IN_CALLBACK; - cb->fn(ac,reply,cb->privdata); - c->flags &= ~REDIS_IN_CALLBACK; - } -} - -static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) { - if (ac->push_cb != NULL) { - ac->c.flags |= REDIS_IN_CALLBACK; - ac->push_cb(ac, reply); - ac->c.flags &= ~REDIS_IN_CALLBACK; - } -} - -/* Helper function to free the context. */ -static void __redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - dictIterator it; - dictEntry *de; - - /* Execute pending callbacks with NULL reply. */ - while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) - __redisRunCallback(ac,&cb,NULL); - - /* Run subscription callbacks with NULL reply */ - if (ac->sub.channels) { - dictInitIterator(&it,ac->sub.channels); - while ((de = dictNext(&it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - - dictRelease(ac->sub.channels); - } - - if (ac->sub.patterns) { - dictInitIterator(&it,ac->sub.patterns); - while ((de = dictNext(&it)) != NULL) - __redisRunCallback(ac,dictGetEntryVal(de),NULL); - - dictRelease(ac->sub.patterns); - } - - /* Signal event lib to clean up */ - _EL_CLEANUP(ac); - - /* Execute disconnect callback. When redisAsyncFree() initiated destroying - * this context, the status will always be REDIS_OK. */ - if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { - if (c->flags & REDIS_FREEING) { - ac->onDisconnect(ac,REDIS_OK); - } else { - ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); - } - } - - if (ac->dataCleanup) { - ac->dataCleanup(ac->data); - } - - /* Cleanup self */ - redisFree(c); -} - -/* Free the async context. When this function is called from a callback, - * control needs to be returned to redisProcessCallbacks() before actual - * free'ing. To do so, a flag is set on the context which is picked up by - * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ -void redisAsyncFree(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_FREEING; - if (!(c->flags & REDIS_IN_CALLBACK)) - __redisAsyncFree(ac); -} - -/* Helper function to make the disconnect happen and clean up. */ -void __redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - /* Make sure error is accessible if there is any */ - __redisAsyncCopyError(ac); - - if (ac->err == 0) { - /* For clean disconnects, there should be no pending callbacks. */ - int ret = __redisShiftCallback(&ac->replies,NULL); - assert(ret == REDIS_ERR); - } else { - /* Disconnection is caused by an error, make sure that pending - * callbacks cannot call new commands. */ - c->flags |= REDIS_DISCONNECTING; - } - - /* cleanup event library on disconnect. - * this is safe to call multiple times */ - _EL_CLEANUP(ac); - - /* For non-clean disconnects, __redisAsyncFree() will execute pending - * callbacks with a NULL-reply. */ - if (!(c->flags & REDIS_NO_AUTO_FREE)) { - __redisAsyncFree(ac); - } -} - -/* Tries to do a clean disconnect from Redis, meaning it stops new commands - * from being issued, but tries to flush the output buffer and execute - * callbacks for all remaining replies. When this function is called from a - * callback, there might be more replies and we can safely defer disconnecting - * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately - * when there are no pending callbacks. */ -void redisAsyncDisconnect(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - c->flags |= REDIS_DISCONNECTING; - - /** unset the auto-free flag here, because disconnect undoes this */ - c->flags &= ~REDIS_NO_AUTO_FREE; - if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) - __redisAsyncDisconnect(ac); -} - -static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { - redisContext *c = &(ac->c); - dict *callbacks; - redisCallback *cb; - dictEntry *de; - int pvariant; - char *stype; - hisds sname; - - /* Match reply with the expected format of a pushed message. - * The type and number of elements (3 to 4) are specified at: - * https://redis.io/topics/pubsub#format-of-pushed-messages */ - if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) || - reply->type == REDIS_REPLY_PUSH) { - assert(reply->element[0]->type == REDIS_REPLY_STRING); - stype = reply->element[0]->str; - pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; - - if (pvariant) - callbacks = ac->sub.patterns; - else - callbacks = ac->sub.channels; - - /* Locate the right callback */ - assert(reply->element[1]->type == REDIS_REPLY_STRING); - sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len); - if (sname == NULL) - goto oom; - - de = dictFind(callbacks,sname); - if (de != NULL) { - cb = dictGetEntryVal(de); - - /* If this is an subscribe reply decrease pending counter. */ - if (strcasecmp(stype+pvariant,"subscribe") == 0) { - cb->pending_subs -= 1; - } - - memcpy(dstcb,cb,sizeof(*dstcb)); - - /* If this is an unsubscribe message, remove it. */ - if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { - if (cb->pending_subs == 0) - dictDelete(callbacks,sname); - - /* If this was the last unsubscribe message, revert to - * non-subscribe mode. */ - assert(reply->element[2]->type == REDIS_REPLY_INTEGER); - - /* Unset subscribed flag only when no pipelined pending subscribe. */ - if (reply->element[2]->integer == 0 - && dictSize(ac->sub.channels) == 0 - && dictSize(ac->sub.patterns) == 0) { - c->flags &= ~REDIS_SUBSCRIBED; - - /* Move ongoing regular command callbacks. */ - redisCallback cb; - while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) { - __redisPushCallback(&ac->replies,&cb); - } - } - } - } - hi_sdsfree(sname); - } else { - /* Shift callback for pending command in subscribed context. */ - __redisShiftCallback(&ac->sub.replies,dstcb); - } - return REDIS_OK; -oom: - __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory"); - return REDIS_ERR; -} - -#define redisIsSpontaneousPushReply(r) \ - (redisIsPushReply(r) && !redisIsSubscribeReply(r)) - -static int redisIsSubscribeReply(redisReply *reply) { - char *str; - size_t len, off; - - /* We will always have at least one string with the subscribe/message type */ - if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING || - reply->element[0]->len < sizeof("message") - 1) - { - return 0; - } - - /* Get the string/len moving past 'p' if needed */ - off = tolower(reply->element[0]->str[0]) == 'p'; - str = reply->element[0]->str + off; - len = reply->element[0]->len - off; - - return !strncasecmp(str, "subscribe", len) || - !strncasecmp(str, "message", len) || - !strncasecmp(str, "unsubscribe", len); -} - -void redisProcessCallbacks(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - void *reply = NULL; - int status; - - while((status = redisGetReply(c,&reply)) == REDIS_OK) { - if (reply == NULL) { - /* When the connection is being disconnected and there are - * no more replies, this is the cue to really disconnect. */ - if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0 - && ac->replies.head == NULL) { - __redisAsyncDisconnect(ac); - return; - } - /* When the connection is not being disconnected, simply stop - * trying to get replies and wait for the next loop tick. */ - break; - } - - /* Keep track of push message support for subscribe handling */ - if (redisIsPushReply(reply)) c->flags |= REDIS_SUPPORTS_PUSH; - - /* Send any non-subscribe related PUSH messages to our PUSH handler - * while allowing subscribe related PUSH messages to pass through. - * This allows existing code to be backward compatible and work in - * either RESP2 or RESP3 mode. */ - if (redisIsSpontaneousPushReply(reply)) { - __redisRunPushCallback(ac, reply); - c->reader->fn->freeObject(reply); - continue; - } - - /* Even if the context is subscribed, pending regular - * callbacks will get a reply before pub/sub messages arrive. */ - redisCallback cb = {NULL, NULL, 0, NULL}; - if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { - /* - * A spontaneous reply in a not-subscribed context can be the error - * reply that is sent when a new connection exceeds the maximum - * number of allowed connections on the server side. - * - * This is seen as an error instead of a regular reply because the - * server closes the connection after sending it. - * - * To prevent the error from being overwritten by an EOF error the - * connection is closed here. See issue #43. - * - * Another possibility is that the server is loading its dataset. - * In this case we also want to close the connection, and have the - * user wait until the server is ready to take our request. - */ - if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { - c->err = REDIS_ERR_OTHER; - snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); - c->reader->fn->freeObject(reply); - __redisAsyncDisconnect(ac); - return; - } - /* No more regular callbacks and no errors, the context *must* be subscribed. */ - assert(c->flags & REDIS_SUBSCRIBED); - if (c->flags & REDIS_SUBSCRIBED) - __redisGetSubscribeCallback(ac,reply,&cb); - } - - if (cb.fn != NULL) { - __redisRunCallback(ac,&cb,reply); - if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){ - c->reader->fn->freeObject(reply); - } - - /* Proceed with free'ing when redisAsyncFree() was called. */ - if (c->flags & REDIS_FREEING) { - __redisAsyncFree(ac); - return; - } - } else { - /* No callback for this reply. This can either be a NULL callback, - * or there were no callbacks to begin with. Either way, don't - * abort with an error, but simply ignore it because the client - * doesn't know what the server will spit out over the wire. */ - c->reader->fn->freeObject(reply); - } - - /* If in monitor mode, repush the callback */ - if (c->flags & REDIS_MONITORING) { - __redisPushCallback(&ac->replies,&cb); - } - } - - /* Disconnect when there was an error reading the reply */ - if (status != REDIS_OK) - __redisAsyncDisconnect(ac); -} - -static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) { - if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); - __redisAsyncDisconnect(ac); -} - -/* Internal helper function to detect socket status the first time a read or - * write event fires. When connecting was not successful, the connect callback - * is called with a REDIS_ERR status and the context is free'd. */ -static int __redisAsyncHandleConnect(redisAsyncContext *ac) { - int completed = 0; - redisContext *c = &(ac->c); - - if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { - /* Error! */ - if (redisCheckSocketError(c) == REDIS_ERR) - __redisAsyncCopyError(ac); - __redisAsyncHandleConnectFailure(ac); - return REDIS_ERR; - } else if (completed == 1) { - /* connected! */ - if (c->connection_type == REDIS_CONN_TCP && - redisSetTcpNoDelay(c) == REDIS_ERR) { - __redisAsyncHandleConnectFailure(ac); - return REDIS_ERR; - } - - if (ac->onConnect) ac->onConnect(ac, REDIS_OK); - c->flags |= REDIS_CONNECTED; - return REDIS_OK; - } else { - return REDIS_OK; - } -} - -void redisAsyncRead(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (redisBufferRead(c) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Always re-schedule reads */ - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -/* This function should be called when the socket is readable. - * It processes all replies that can be read and executes their callbacks. - */ -void redisAsyncHandleRead(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - c->funcs->async_read(ac); -} - -void redisAsyncWrite(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - int done = 0; - - if (redisBufferWrite(c,&done) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Continue writing when not done, stop writing otherwise */ - if (!done) - _EL_ADD_WRITE(ac); - else - _EL_DEL_WRITE(ac); - - /* Always schedule reads after writes */ - _EL_ADD_READ(ac); - } -} - -void redisAsyncHandleWrite(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (!(c->flags & REDIS_CONNECTED)) { - /* Abort connect was not successful. */ - if (__redisAsyncHandleConnect(ac) != REDIS_OK) - return; - /* Try again later when the context is still not connected. */ - if (!(c->flags & REDIS_CONNECTED)) - return; - } - - c->funcs->async_write(ac); -} - -void redisAsyncHandleTimeout(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - - if ((c->flags & REDIS_CONNECTED)) { - if (ac->replies.head == NULL && ac->sub.replies.head == NULL) { - /* Nothing to do - just an idle timeout */ - return; - } - - if (!ac->c.command_timeout || - (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) { - /* A belated connect timeout arriving, ignore */ - return; - } - } - - if (!c->err) { - __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); - __redisAsyncCopyError(ac); - } - - if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { - ac->onConnect(ac, REDIS_ERR); - } - - while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { - __redisRunCallback(ac, &cb, NULL); - } - - /** - * TODO: Don't automatically sever the connection, - * rather, allow to ignore responses before the queue is clear - */ - __redisAsyncDisconnect(ac); -} - -/* Sets a pointer to the first argument and its length starting at p. Returns - * the number of bytes to skip to get to the following argument. */ -static const char *nextArgument(const char *start, const char **str, size_t *len) { - const char *p = start; - if (p[0] != '$') { - p = strchr(p,'$'); - if (p == NULL) return NULL; - } - - *len = (int)strtol(p+1,NULL,10); - p = strchr(p,'\r'); - assert(p); - *str = p+2; - return p+2+(*len)+2; -} - -/* Helper function for the redisAsyncCommand* family of functions. Writes a - * formatted command to the output buffer and registers the provided callback - * function with the context. */ -static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { - redisContext *c = &(ac->c); - redisCallback cb; - struct dict *cbdict; - dictEntry *de; - redisCallback *existcb; - int pvariant, hasnext; - const char *cstr, *astr; - size_t clen, alen; - const char *p; - hisds sname; - int ret; - - /* Don't accept new commands when the connection is about to be closed. */ - if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; - - /* Setup callback */ - cb.fn = fn; - cb.privdata = privdata; - cb.pending_subs = 1; - - /* Find out which command will be appended. */ - p = nextArgument(cmd,&cstr,&clen); - assert(p != NULL); - hasnext = (p[0] == '$'); - pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; - cstr += pvariant; - clen -= pvariant; - - if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { - c->flags |= REDIS_SUBSCRIBED; - - /* Add every channel/pattern to the list of subscription callbacks. */ - while ((p = nextArgument(p,&astr,&alen)) != NULL) { - sname = hi_sdsnewlen(astr,alen); - if (sname == NULL) - goto oom; - - if (pvariant) - cbdict = ac->sub.patterns; - else - cbdict = ac->sub.channels; - - de = dictFind(cbdict,sname); - - if (de != NULL) { - existcb = dictGetEntryVal(de); - cb.pending_subs = existcb->pending_subs + 1; - } - - ret = dictReplace(cbdict,sname,&cb); - - if (ret == 0) hi_sdsfree(sname); - } - } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { - /* It is only useful to call (P)UNSUBSCRIBE when the context is - * subscribed to one or more channels or patterns. */ - if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; - - /* (P)UNSUBSCRIBE does not have its own response: every channel or - * pattern that is unsubscribed will redisReceive a message. This means we - * should not append a callback function for this command. */ - } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) { - /* Set monitor flag and push callback */ - c->flags |= REDIS_MONITORING; - if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) - goto oom; - } else { - if (c->flags & REDIS_SUBSCRIBED) { - if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK) - goto oom; - } else { - if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) - goto oom; - } - } - - __redisAppendCommand(c,cmd,len); - - /* Always schedule a write when the write buffer is non-empty */ - _EL_ADD_WRITE(ac); - - return REDIS_OK; -oom: - __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory"); - __redisAsyncCopyError(ac); - return REDIS_ERR; -} - -int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { - char *cmd; - int len; - int status; - len = redisvFormatCommand(&cmd,format,ap); - - /* We don't want to pass -1 or -2 to future functions as a length. */ - if (len < 0) - return REDIS_ERR; - - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - hi_free(cmd); - return status; -} - -int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { - va_list ap; - int status; - va_start(ap,format); - status = redisvAsyncCommand(ac,fn,privdata,format,ap); - va_end(ap); - return status; -} - -int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { - hisds cmd; - long long len; - int status; - len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); - if (len < 0) - return REDIS_ERR; - status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - hi_sdsfree(cmd); - return status; -} - -int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { - int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); - return status; -} - -redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) { - redisAsyncPushFn *old = ac->push_cb; - ac->push_cb = fn; - return old; -} - -int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { - if (!ac->c.command_timeout) { - ac->c.command_timeout = hi_calloc(1, sizeof(tv)); - if (ac->c.command_timeout == NULL) { - __redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory"); - __redisAsyncCopyError(ac); - return REDIS_ERR; - } - } - - if (tv.tv_sec != ac->c.command_timeout->tv_sec || - tv.tv_usec != ac->c.command_timeout->tv_usec) - { - *ac->c.command_timeout = tv; - } - - return REDIS_OK; -} diff --git a/src/async.h b/src/async.h deleted file mode 100644 index 4c65203..0000000 --- a/src/async.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_ASYNC_H -#define __HIREDIS_ASYNC_H -#include "hiredis.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ -struct dict; /* dictionary header is included in async.c */ - -/* Reply callback prototype and container */ -typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); -typedef struct redisCallback { - struct redisCallback *next; /* simple singly linked list */ - redisCallbackFn *fn; - int pending_subs; - void *privdata; -} redisCallback; - -/* List of callbacks for either regular replies or pub/sub */ -typedef struct redisCallbackList { - redisCallback *head, *tail; -} redisCallbackList; - -/* Connection callback prototypes */ -typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); -typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); -typedef void(redisTimerCallback)(void *timer, void *privdata); - -/* Context for an async connection to Redis */ -typedef struct redisAsyncContext { - /* Hold the regular context, so it can be realloc'ed. */ - redisContext c; - - /* Setup error flags so they can be used directly. */ - int err; - char *errstr; - - /* Not used by hiredis */ - void *data; - void (*dataCleanup)(void *privdata); - - /* Event library data and hooks */ - struct { - void *data; - - /* Hooks that are called when the library expects to start - * reading/writing. These functions should be idempotent. */ - void (*addRead)(void *privdata); - void (*delRead)(void *privdata); - void (*addWrite)(void *privdata); - void (*delWrite)(void *privdata); - void (*cleanup)(void *privdata); - void (*scheduleTimer)(void *privdata, struct timeval tv); - } ev; - - /* Called when either the connection is terminated due to an error or per - * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ - redisDisconnectCallback *onDisconnect; - - /* Called when the first write event was received. */ - redisConnectCallback *onConnect; - - /* Regular command callbacks */ - redisCallbackList replies; - - /* Address used for connect() */ - struct sockaddr *saddr; - size_t addrlen; - - /* Subscription callbacks */ - struct { - redisCallbackList replies; - struct dict *channels; - struct dict *patterns; - } sub; - - /* Any configured RESP3 PUSH handler */ - redisAsyncPushFn *push_cb; -} redisAsyncContext; - -/* Functions that proxy to hiredis */ -redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); -redisAsyncContext *redisAsyncConnect(const char *ip, int port); -redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); -redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, - const char *source_addr); -redisAsyncContext *redisAsyncConnectUnix(const char *path); -int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); -int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); - -redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn); -int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); -void redisAsyncDisconnect(redisAsyncContext *ac); -void redisAsyncFree(redisAsyncContext *ac); - -/* Handle read/write events */ -void redisAsyncHandleRead(redisAsyncContext *ac); -void redisAsyncHandleWrite(redisAsyncContext *ac); -void redisAsyncHandleTimeout(redisAsyncContext *ac); -void redisAsyncRead(redisAsyncContext *ac); -void redisAsyncWrite(redisAsyncContext *ac); - -/* Command functions for an async context. Write the command to the - * output buffer and register the provided callback. */ -int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); -int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); -int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); -int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/async_private.h b/src/async_private.h deleted file mode 100644 index ea0558d..0000000 --- a/src/async_private.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_ASYNC_PRIVATE_H -#define __HIREDIS_ASYNC_PRIVATE_H - -#define _EL_ADD_READ(ctx) \ - do { \ - refreshTimeout(ctx); \ - if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ - } while (0) -#define _EL_DEL_READ(ctx) do { \ - if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ - } while(0) -#define _EL_ADD_WRITE(ctx) \ - do { \ - refreshTimeout(ctx); \ - if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ - } while (0) -#define _EL_DEL_WRITE(ctx) do { \ - if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ - } while(0) -#define _EL_CLEANUP(ctx) do { \ - if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ - ctx->ev.cleanup = NULL; \ - } while(0) - -static inline void refreshTimeout(redisAsyncContext *ctx) { - #define REDIS_TIMER_ISSET(tvp) \ - (tvp && ((tvp)->tv_sec || (tvp)->tv_usec)) - - #define REDIS_EL_TIMER(ac, tvp) \ - if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \ - (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \ - } - - if (ctx->c.flags & REDIS_CONNECTED) { - REDIS_EL_TIMER(ctx, ctx->c.command_timeout); - } else { - REDIS_EL_TIMER(ctx, ctx->c.connect_timeout); - } -} - -void __redisAsyncDisconnect(redisAsyncContext *ac); -void redisProcessCallbacks(redisAsyncContext *ac); - -#endif /* __HIREDIS_ASYNC_PRIVATE_H */ diff --git a/src/dict.c b/src/dict.c deleted file mode 100644 index b810eb3..0000000 --- a/src/dict.c +++ /dev/null @@ -1,341 +0,0 @@ -/* Hash table implementation. - * - * This file implements in memory hash tables with insert/del/replace/find/ - * get-random-element operations. Hash tables will auto resize if needed - * tables of power of two in size are used, collisions are handled by - * chaining. See the source code for more information... :) - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * 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 "alloc.h" -#include -#include -#include "dict.h" - -/* -------------------------- private prototypes ---------------------------- */ - -static int _dictExpandIfNeeded(dict *ht); -static unsigned long _dictNextPower(unsigned long size); -static int _dictKeyIndex(dict *ht, const void *key); -static int _dictInit(dict *ht, dictType *type, void *privDataPtr); - -/* -------------------------- hash functions -------------------------------- */ - -/* Generic hash function (a popular one from Bernstein). - * I tested a few and this was the best. */ -static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { - unsigned int hash = 5381; - - while (len--) - hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ - return hash; -} - -/* ----------------------------- API implementation ------------------------- */ - -/* Reset an hashtable already initialized with ht_init(). - * NOTE: This function should only called by ht_destroy(). */ -static void _dictReset(dict *ht) { - ht->table = NULL; - ht->size = 0; - ht->sizemask = 0; - ht->used = 0; -} - -/* Create a new hash table */ -static dict *dictCreate(dictType *type, void *privDataPtr) { - dict *ht = hi_malloc(sizeof(*ht)); - if (ht == NULL) - return NULL; - - _dictInit(ht,type,privDataPtr); - return ht; -} - -/* Initialize the hash table */ -static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { - _dictReset(ht); - ht->type = type; - ht->privdata = privDataPtr; - return DICT_OK; -} - -/* Expand or create the hashtable */ -static int dictExpand(dict *ht, unsigned long size) { - dict n; /* the new hashtable */ - unsigned long realsize = _dictNextPower(size), i; - - /* the size is invalid if it is smaller than the number of - * elements already inside the hashtable */ - if (ht->used > size) - return DICT_ERR; - - _dictInit(&n, ht->type, ht->privdata); - n.size = realsize; - n.sizemask = realsize-1; - n.table = hi_calloc(realsize,sizeof(dictEntry*)); - if (n.table == NULL) - return DICT_ERR; - - /* Copy all the elements from the old to the new table: - * note that if the old hash table is empty ht->size is zero, - * so dictExpand just creates an hash table. */ - n.used = ht->used; - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if (ht->table[i] == NULL) continue; - - /* For each hash entry on this slot... */ - he = ht->table[i]; - while(he) { - unsigned int h; - - nextHe = he->next; - /* Get the new element index */ - h = dictHashKey(ht, he->key) & n.sizemask; - he->next = n.table[h]; - n.table[h] = he; - ht->used--; - /* Pass to the next element */ - he = nextHe; - } - } - assert(ht->used == 0); - hi_free(ht->table); - - /* Remap the new hashtable in the old */ - *ht = n; - return DICT_OK; -} - -/* Add an element to the target hash table */ -static int dictAdd(dict *ht, void *key, void *val) { - int index; - dictEntry *entry; - - /* Get the index of the new element, or -1 if - * the element already exists. */ - if ((index = _dictKeyIndex(ht, key)) == -1) - return DICT_ERR; - - /* Allocates the memory and stores key */ - entry = hi_malloc(sizeof(*entry)); - if (entry == NULL) - return DICT_ERR; - - entry->next = ht->table[index]; - ht->table[index] = entry; - - /* Set the hash entry fields. */ - dictSetHashKey(ht, entry, key); - dictSetHashVal(ht, entry, val); - ht->used++; - return DICT_OK; -} - -/* Add an element, discarding the old if the key already exists. - * Return 1 if the key was added from scratch, 0 if there was already an - * element with such key and dictReplace() just performed a value update - * operation. */ -static int dictReplace(dict *ht, void *key, void *val) { - dictEntry *entry, auxentry; - - /* Try to add the element. If the key - * does not exists dictAdd will succeed. */ - if (dictAdd(ht, key, val) == DICT_OK) - return 1; - /* It already exists, get the entry */ - entry = dictFind(ht, key); - if (entry == NULL) - return 0; - - /* Free the old value and set the new one */ - /* Set the new value and free the old one. Note that it is important - * to do that in this order, as the value may just be exactly the same - * as the previous one. In this context, think to reference counting, - * you want to increment (set), and then decrement (free), and not the - * reverse. */ - auxentry = *entry; - dictSetHashVal(ht, entry, val); - dictFreeEntryVal(ht, &auxentry); - return 0; -} - -/* Search and remove an element */ -static int dictDelete(dict *ht, const void *key) { - unsigned int h; - dictEntry *de, *prevde; - - if (ht->size == 0) - return DICT_ERR; - h = dictHashKey(ht, key) & ht->sizemask; - de = ht->table[h]; - - prevde = NULL; - while(de) { - if (dictCompareHashKeys(ht,key,de->key)) { - /* Unlink the element from the list */ - if (prevde) - prevde->next = de->next; - else - ht->table[h] = de->next; - - dictFreeEntryKey(ht,de); - dictFreeEntryVal(ht,de); - hi_free(de); - ht->used--; - return DICT_OK; - } - prevde = de; - de = de->next; - } - return DICT_ERR; /* not found */ -} - -/* Destroy an entire hash table */ -static int _dictClear(dict *ht) { - unsigned long i; - - /* Free all the elements */ - for (i = 0; i < ht->size && ht->used > 0; i++) { - dictEntry *he, *nextHe; - - if ((he = ht->table[i]) == NULL) continue; - while(he) { - nextHe = he->next; - dictFreeEntryKey(ht, he); - dictFreeEntryVal(ht, he); - hi_free(he); - ht->used--; - he = nextHe; - } - } - /* Free the table and the allocated cache structure */ - hi_free(ht->table); - /* Re-initialize the table */ - _dictReset(ht); - return DICT_OK; /* never fails */ -} - -/* Clear & Release the hash table */ -static void dictRelease(dict *ht) { - _dictClear(ht); - hi_free(ht); -} - -static dictEntry *dictFind(dict *ht, const void *key) { - dictEntry *he; - unsigned int h; - - if (ht->size == 0) return NULL; - h = dictHashKey(ht, key) & ht->sizemask; - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return he; - he = he->next; - } - return NULL; -} - -static void dictInitIterator(dictIterator *iter, dict *ht) { - iter->ht = ht; - iter->index = -1; - iter->entry = NULL; - iter->nextEntry = NULL; -} - -static dictEntry *dictNext(dictIterator *iter) { - while (1) { - if (iter->entry == NULL) { - iter->index++; - if (iter->index >= - (signed)iter->ht->size) break; - iter->entry = iter->ht->table[iter->index]; - } else { - iter->entry = iter->nextEntry; - } - if (iter->entry) { - /* We need to save the 'next' here, the iterator user - * may delete the entry we are returning. */ - iter->nextEntry = iter->entry->next; - return iter->entry; - } - } - return NULL; -} - -/* ------------------------- private functions ------------------------------ */ - -/* Expand the hash table if needed */ -static int _dictExpandIfNeeded(dict *ht) { - /* If the hash table is empty expand it to the initial size, - * if the table is "full" double its size. */ - if (ht->size == 0) - return dictExpand(ht, DICT_HT_INITIAL_SIZE); - if (ht->used == ht->size) - return dictExpand(ht, ht->size*2); - return DICT_OK; -} - -/* Our hash table capability is a power of two */ -static unsigned long _dictNextPower(unsigned long size) { - unsigned long i = DICT_HT_INITIAL_SIZE; - - if (size >= LONG_MAX) return LONG_MAX; - while(1) { - if (i >= size) - return i; - i *= 2; - } -} - -/* Returns the index of a free slot that can be populated with - * an hash entry for the given 'key'. - * If the key already exists, -1 is returned. */ -static int _dictKeyIndex(dict *ht, const void *key) { - unsigned int h; - dictEntry *he; - - /* Expand the hashtable if needed */ - if (_dictExpandIfNeeded(ht) == DICT_ERR) - return -1; - /* Compute the key hash value */ - h = dictHashKey(ht, key) & ht->sizemask; - /* Search if this slot does not already contain the given key */ - he = ht->table[h]; - while(he) { - if (dictCompareHashKeys(ht, key, he->key)) - return -1; - he = he->next; - } - return h; -} - diff --git a/src/dict.h b/src/dict.h deleted file mode 100644 index 6ad0acd..0000000 --- a/src/dict.h +++ /dev/null @@ -1,125 +0,0 @@ -/* Hash table implementation. - * - * This file implements in memory hash tables with insert/del/replace/find/ - * get-random-element operations. Hash tables will auto resize if needed - * tables of power of two in size are used, collisions are handled by - * chaining. See the source code for more information... :) - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __DICT_H -#define __DICT_H - -#define DICT_OK 0 -#define DICT_ERR 1 - -/* Unused arguments generate annoying warnings... */ -#define DICT_NOTUSED(V) ((void) V) - -typedef struct dictEntry { - void *key; - void *val; - struct dictEntry *next; -} dictEntry; - -typedef struct dictType { - unsigned int (*hashFunction)(const void *key); - void *(*keyDup)(void *privdata, const void *key); - void *(*valDup)(void *privdata, const void *obj); - int (*keyCompare)(void *privdata, const void *key1, const void *key2); - void (*keyDestructor)(void *privdata, void *key); - void (*valDestructor)(void *privdata, void *obj); -} dictType; - -typedef struct dict { - dictEntry **table; - dictType *type; - unsigned long size; - unsigned long sizemask; - unsigned long used; - void *privdata; -} dict; - -typedef struct dictIterator { - dict *ht; - int index; - dictEntry *entry, *nextEntry; -} dictIterator; - -/* This is the initial size of every hash table */ -#define DICT_HT_INITIAL_SIZE 4 - -/* ------------------------------- Macros ------------------------------------*/ -#define dictFreeEntryVal(ht, entry) \ - if ((ht)->type->valDestructor) \ - (ht)->type->valDestructor((ht)->privdata, (entry)->val) - -#define dictSetHashVal(ht, entry, _val_) do { \ - if ((ht)->type->valDup) \ - entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ - else \ - entry->val = (_val_); \ -} while(0) - -#define dictFreeEntryKey(ht, entry) \ - if ((ht)->type->keyDestructor) \ - (ht)->type->keyDestructor((ht)->privdata, (entry)->key) - -#define dictSetHashKey(ht, entry, _key_) do { \ - if ((ht)->type->keyDup) \ - entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ - else \ - entry->key = (_key_); \ -} while(0) - -#define dictCompareHashKeys(ht, key1, key2) \ - (((ht)->type->keyCompare) ? \ - (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ - (key1) == (key2)) - -#define dictHashKey(ht, key) (ht)->type->hashFunction(key) - -#define dictGetEntryKey(he) ((he)->key) -#define dictGetEntryVal(he) ((he)->val) -#define dictSlots(ht) ((ht)->size) -#define dictSize(ht) ((ht)->used) - -/* API */ -static unsigned int dictGenHashFunction(const unsigned char *buf, int len); -static dict *dictCreate(dictType *type, void *privDataPtr); -static int dictExpand(dict *ht, unsigned long size); -static int dictAdd(dict *ht, void *key, void *val); -static int dictReplace(dict *ht, void *key, void *val); -static int dictDelete(dict *ht, const void *key); -static void dictRelease(dict *ht); -static dictEntry * dictFind(dict *ht, const void *key); -static void dictInitIterator(dictIterator *iter, dict *ht); -static dictEntry *dictNext(dictIterator *iter); - -#endif /* __DICT_H */ diff --git a/src/fmacros.h b/src/fmacros.h deleted file mode 100644 index 754a53c..0000000 --- a/src/fmacros.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __HIREDIS_FMACRO_H -#define __HIREDIS_FMACRO_H - -#ifndef _AIX -#define _XOPEN_SOURCE 600 -#define _POSIX_C_SOURCE 200112L -#endif - -#if defined(__APPLE__) && defined(__MACH__) -/* Enable TCP_KEEPALIVE */ -#define _DARWIN_C_SOURCE -#endif - -#endif diff --git a/src/hiredis b/src/hiredis new file mode 160000 index 0000000..b731283 --- /dev/null +++ b/src/hiredis @@ -0,0 +1 @@ +Subproject commit b731283245f3183af527237166261ad0768ba7d4 diff --git a/src/hiredis.c b/src/hiredis.c deleted file mode 100644 index 32480df..0000000 --- a/src/hiredis.c +++ /dev/null @@ -1,1211 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "net.h" -#include "sds.h" -#include "async.h" - -extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout); -extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); - -static redisContextFuncs redisContextDefaultFuncs = { - .free_privctx = NULL, - .async_read = redisAsyncRead, - .async_write = redisAsyncWrite, - .read = redisNetRead, - .write = redisNetWrite -}; - -static redisReply *createReplyObject(int type); -static void *createStringObject(const redisReadTask *task, char *str, size_t len); -static void *createArrayObject(const redisReadTask *task, size_t elements); -static void *createIntegerObject(const redisReadTask *task, long long value); -static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); -static void *createNilObject(const redisReadTask *task); -static void *createBoolObject(const redisReadTask *task, int bval); - -/* Default set of functions to build the reply. Keep in mind that such a - * function returning NULL is interpreted as OOM. */ -static redisReplyObjectFunctions defaultFunctions = { - createStringObject, - createArrayObject, - createIntegerObject, - createDoubleObject, - createNilObject, - createBoolObject, - freeReplyObject -}; - -/* Create a reply object */ -static redisReply *createReplyObject(int type) { - redisReply *r = hi_calloc(1,sizeof(*r)); - - if (r == NULL) - return NULL; - - r->type = type; - return r; -} - -/* Free a reply object */ -void freeReplyObject(void *reply) { - redisReply *r = reply; - size_t j; - - if (r == NULL) - return; - - switch(r->type) { - case REDIS_REPLY_INTEGER: - case REDIS_REPLY_NIL: - case REDIS_REPLY_BOOL: - break; /* Nothing to free */ - case REDIS_REPLY_ARRAY: - case REDIS_REPLY_MAP: - case REDIS_REPLY_SET: - case REDIS_REPLY_PUSH: - if (r->element != NULL) { - for (j = 0; j < r->elements; j++) - freeReplyObject(r->element[j]); - hi_free(r->element); - } - break; - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_STRING: - case REDIS_REPLY_DOUBLE: - case REDIS_REPLY_VERB: - case REDIS_REPLY_BIGNUM: - hi_free(r->str); - break; - } - hi_free(r); -} - -static void *createStringObject(const redisReadTask *task, char *str, size_t len) { - redisReply *r, *parent; - char *buf; - - r = createReplyObject(task->type); - if (r == NULL) - return NULL; - - assert(task->type == REDIS_REPLY_ERROR || - task->type == REDIS_REPLY_STATUS || - task->type == REDIS_REPLY_STRING || - task->type == REDIS_REPLY_VERB || - task->type == REDIS_REPLY_BIGNUM); - - /* Copy string value */ - if (task->type == REDIS_REPLY_VERB) { - buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */ - if (buf == NULL) goto oom; - - memcpy(r->vtype,str,3); - r->vtype[3] = '\0'; - memcpy(buf,str+4,len-4); - buf[len-4] = '\0'; - r->len = len - 4; - } else { - buf = hi_malloc(len+1); - if (buf == NULL) goto oom; - - memcpy(buf,str,len); - buf[len] = '\0'; - r->len = len; - } - r->str = buf; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET || - parent->type == REDIS_REPLY_PUSH); - parent->element[task->idx] = r; - } - return r; - -oom: - freeReplyObject(r); - return NULL; -} - -static void *createArrayObject(const redisReadTask *task, size_t elements) { - redisReply *r, *parent; - - r = createReplyObject(task->type); - if (r == NULL) - return NULL; - - if (elements > 0) { - r->element = hi_calloc(elements,sizeof(redisReply*)); - if (r->element == NULL) { - freeReplyObject(r); - return NULL; - } - } - - r->elements = elements; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET || - parent->type == REDIS_REPLY_PUSH); - parent->element[task->idx] = r; - } - return r; -} - -static void *createIntegerObject(const redisReadTask *task, long long value) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_INTEGER); - if (r == NULL) - return NULL; - - r->integer = value; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET || - parent->type == REDIS_REPLY_PUSH); - parent->element[task->idx] = r; - } - return r; -} - -static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_DOUBLE); - if (r == NULL) - return NULL; - - r->dval = value; - r->str = hi_malloc(len+1); - if (r->str == NULL) { - freeReplyObject(r); - return NULL; - } - - /* The double reply also has the original protocol string representing a - * double as a null terminated string. This way the caller does not need - * to format back for string conversion, especially since Redis does efforts - * to make the string more human readable avoiding the calssical double - * decimal string conversion artifacts. */ - memcpy(r->str, str, len); - r->str[len] = '\0'; - r->len = len; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET || - parent->type == REDIS_REPLY_PUSH); - parent->element[task->idx] = r; - } - return r; -} - -static void *createNilObject(const redisReadTask *task) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_NIL); - if (r == NULL) - return NULL; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET || - parent->type == REDIS_REPLY_PUSH); - parent->element[task->idx] = r; - } - return r; -} - -static void *createBoolObject(const redisReadTask *task, int bval) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_BOOL); - if (r == NULL) - return NULL; - - r->integer = bval != 0; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET || - parent->type == REDIS_REPLY_PUSH); - parent->element[task->idx] = r; - } - return r; -} - -/* Return the number of digits of 'v' when converted to string in radix 10. - * Implementation borrowed from link in redis/src/util.c:string2ll(). */ -static uint32_t countDigits(uint64_t v) { - uint32_t result = 1; - for (;;) { - if (v < 10) return result; - if (v < 100) return result + 1; - if (v < 1000) return result + 2; - if (v < 10000) return result + 3; - v /= 10000U; - result += 4; - } -} - -/* Helper that calculates the bulk length given a certain string length. */ -static size_t bulklen(size_t len) { - return 1+countDigits(len)+2+len+2; -} - -int redisvFormatCommand(char **target, const char *format, va_list ap) { - const char *c = format; - char *cmd = NULL; /* final command */ - int pos; /* position in final command */ - hisds curarg, newarg; /* current argument */ - int touched = 0; /* was the current argument touched? */ - char **curargv = NULL, **newargv = NULL; - int argc = 0; - int totlen = 0; - int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ - int j; - - /* Abort if there is not target to set */ - if (target == NULL) - return -1; - - /* Build the command string accordingly to protocol */ - curarg = hi_sdsempty(); - if (curarg == NULL) - return -1; - - while(*c != '\0') { - if (*c != '%' || c[1] == '\0') { - if (*c == ' ') { - if (touched) { - newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto memory_err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(hi_sdslen(curarg)); - - /* curarg is put in argv so it can be overwritten. */ - curarg = hi_sdsempty(); - if (curarg == NULL) goto memory_err; - touched = 0; - } - } else { - newarg = hi_sdscatlen(curarg,c,1); - if (newarg == NULL) goto memory_err; - curarg = newarg; - touched = 1; - } - } else { - char *arg; - size_t size; - - /* Set newarg so it can be checked even if it is not touched. */ - newarg = curarg; - - switch(c[1]) { - case 's': - arg = va_arg(ap,char*); - size = strlen(arg); - if (size > 0) - newarg = hi_sdscatlen(curarg,arg,size); - break; - case 'b': - arg = va_arg(ap,char*); - size = va_arg(ap,size_t); - if (size > 0) - newarg = hi_sdscatlen(curarg,arg,size); - break; - case '%': - newarg = hi_sdscat(curarg,"%"); - break; - default: - /* Try to detect printf format */ - { - static const char intfmts[] = "diouxX"; - static const char flags[] = "#0-+ "; - char _format[16]; - const char *_p = c+1; - size_t _l = 0; - va_list _cpy; - - /* Flags */ - while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; - - /* Field width */ - while (*_p != '\0' && isdigit(*_p)) _p++; - - /* Precision */ - if (*_p == '.') { - _p++; - while (*_p != '\0' && isdigit(*_p)) _p++; - } - - /* Copy va_list before consuming with va_arg */ - va_copy(_cpy,ap); - - /* Integer conversion (without modifiers) */ - if (strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); - goto fmt_valid; - } - - /* Double conversion (without modifiers) */ - if (strchr("eEfFgGaA",*_p) != NULL) { - va_arg(ap,double); - goto fmt_valid; - } - - /* Size: char */ - if (_p[0] == 'h' && _p[1] == 'h') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* char gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: short */ - if (_p[0] == 'h') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,int); /* short gets promoted to int */ - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long long */ - if (_p[0] == 'l' && _p[1] == 'l') { - _p += 2; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long long); - goto fmt_valid; - } - goto fmt_invalid; - } - - /* Size: long */ - if (_p[0] == 'l') { - _p += 1; - if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { - va_arg(ap,long); - goto fmt_valid; - } - goto fmt_invalid; - } - - fmt_invalid: - va_end(_cpy); - goto format_err; - - fmt_valid: - _l = (_p+1)-c; - if (_l < sizeof(_format)-2) { - memcpy(_format,c,_l); - _format[_l] = '\0'; - newarg = hi_sdscatvprintf(curarg,_format,_cpy); - - /* Update current position (note: outer blocks - * increment c twice so compensate here) */ - c = _p-1; - } - - va_end(_cpy); - break; - } - } - - if (newarg == NULL) goto memory_err; - curarg = newarg; - - touched = 1; - c++; - } - c++; - } - - /* Add the last argument if needed */ - if (touched) { - newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); - if (newargv == NULL) goto memory_err; - curargv = newargv; - curargv[argc++] = curarg; - totlen += bulklen(hi_sdslen(curarg)); - } else { - hi_sdsfree(curarg); - } - - /* Clear curarg because it was put in curargv or was free'd. */ - curarg = NULL; - - /* Add bytes needed to hold multi bulk count */ - totlen += 1+countDigits(argc)+2; - - /* Build the command at protocol level */ - cmd = hi_malloc(totlen+1); - if (cmd == NULL) goto memory_err; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j])); - memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j])); - pos += hi_sdslen(curargv[j]); - hi_sdsfree(curargv[j]); - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - hi_free(curargv); - *target = cmd; - return totlen; - -format_err: - error_type = -2; - goto cleanup; - -memory_err: - error_type = -1; - goto cleanup; - -cleanup: - if (curargv) { - while(argc--) - hi_sdsfree(curargv[argc]); - hi_free(curargv); - } - - hi_sdsfree(curarg); - hi_free(cmd); - - return error_type; -} - -/* Format a command according to the Redis protocol. This function - * takes a format similar to printf: - * - * %s represents a C null terminated string you want to interpolate - * %b represents a binary safe string - * - * When using %b you need to provide both the pointer to the string - * and the length in bytes as a size_t. Examples: - * - * len = redisFormatCommand(target, "GET %s", mykey); - * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); - */ -int redisFormatCommand(char **target, const char *format, ...) { - va_list ap; - int len; - va_start(ap,format); - len = redisvFormatCommand(target,format,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; -} - -/* Format a command according to the Redis protocol using an hisds string and - * hi_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. - */ -long long redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, - const size_t *argvlen) -{ - hisds cmd, aux; - unsigned long long totlen, len; - int j; - - /* 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 = hi_sdsempty(); - if (cmd == NULL) - return -1; - - /* We already know how much storage we need */ - aux = hi_sdsMakeRoomFor(cmd, totlen); - if (aux == NULL) { - hi_sdsfree(cmd); - return -1; - } - - cmd = aux; - - /* Construct command */ - cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc); - for (j=0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - cmd = hi_sdscatfmt(cmd, "$%U\r\n", len); - cmd = hi_sdscatlen(cmd, argv[j], len); - cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); - } - - assert(hi_sdslen(cmd)==totlen); - - *target = cmd; - return totlen; -} - -void redisFreeSdsCommand(hisds cmd) { - hi_sdsfree(cmd); -} - -/* Format a command according to the Redis protocol. 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. - */ -long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { - char *cmd = NULL; /* final command */ - size_t pos; /* position in final command */ - size_t len, totlen; - int j; - - /* Abort on a NULL target */ - if (target == NULL) - return -1; - - /* Calculate number of bytes needed for the command */ - totlen = 1+countDigits(argc)+2; - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - totlen += bulklen(len); - } - - /* Build the command at protocol level */ - cmd = hi_malloc(totlen+1); - if (cmd == NULL) - return -1; - - pos = sprintf(cmd,"*%d\r\n",argc); - for (j = 0; j < argc; j++) { - len = argvlen ? argvlen[j] : strlen(argv[j]); - pos += sprintf(cmd+pos,"$%zu\r\n",len); - memcpy(cmd+pos,argv[j],len); - pos += len; - cmd[pos++] = '\r'; - cmd[pos++] = '\n'; - } - assert(pos == totlen); - cmd[pos] = '\0'; - - *target = cmd; - return totlen; -} - -void redisFreeCommand(char *cmd) { - hi_free(cmd); -} - -void __redisSetError(redisContext *c, int type, const char *str) { - size_t len; - - c->err = type; - if (str != NULL) { - len = strlen(str); - len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); - memcpy(c->errstr,str,len); - c->errstr[len] = '\0'; - } else { - /* Only REDIS_ERR_IO may lack a description! */ - assert(type == REDIS_ERR_IO); - strerror_r(errno, c->errstr, sizeof(c->errstr)); - } -} - -redisReader *redisReaderCreate(void) { - return redisReaderCreateWithFunctions(&defaultFunctions); -} - -static void redisPushAutoFree(void *privdata, void *reply) { - (void)privdata; - freeReplyObject(reply); -} - -static redisContext *redisContextInit(void) { - redisContext *c; - - c = hi_calloc(1, sizeof(*c)); - if (c == NULL) - return NULL; - - c->funcs = &redisContextDefaultFuncs; - - c->obuf = hi_sdsempty(); - c->reader = redisReaderCreate(); - c->fd = REDIS_INVALID_FD; - - if (c->obuf == NULL || c->reader == NULL) { - redisFree(c); - return NULL; - } - - return c; -} - -void redisFree(redisContext *c) { - if (c == NULL) - return; - redisNetClose(c); - - hi_sdsfree(c->obuf); - redisReaderFree(c->reader); - hi_free(c->tcp.host); - hi_free(c->tcp.source_addr); - hi_free(c->unix_sock.path); - hi_free(c->connect_timeout); - hi_free(c->command_timeout); - hi_free(c->saddr); - - if (c->privdata && c->free_privdata) - c->free_privdata(c->privdata); - - if (c->funcs->free_privctx) - c->funcs->free_privctx(c->privctx); - - memset(c, 0xff, sizeof(*c)); - hi_free(c); -} - -redisFD redisFreeKeepFd(redisContext *c) { - redisFD fd = c->fd; - c->fd = REDIS_INVALID_FD; - redisFree(c); - return fd; -} - -int redisReconnect(redisContext *c) { - c->err = 0; - memset(c->errstr, '\0', strlen(c->errstr)); - - if (c->privctx && c->funcs->free_privctx) { - c->funcs->free_privctx(c->privctx); - c->privctx = NULL; - } - - redisNetClose(c); - - hi_sdsfree(c->obuf); - redisReaderFree(c->reader); - - c->obuf = hi_sdsempty(); - c->reader = redisReaderCreate(); - - if (c->obuf == NULL || c->reader == NULL) { - __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); - return REDIS_ERR; - } - - int ret = REDIS_ERR; - if (c->connection_type == REDIS_CONN_TCP) { - ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, - c->connect_timeout, c->tcp.source_addr); - } else if (c->connection_type == REDIS_CONN_UNIX) { - ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_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"); - ret = REDIS_ERR; - } - - if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { - redisContextSetTimeout(c, *c->command_timeout); - } - - return ret; -} - -redisContext *redisConnectWithOptions(const redisOptions *options) { - redisContext *c = redisContextInit(); - if (c == NULL) { - return NULL; - } - if (!(options->options & REDIS_OPT_NONBLOCK)) { - c->flags |= REDIS_BLOCK; - } - if (options->options & REDIS_OPT_REUSEADDR) { - c->flags |= REDIS_REUSEADDR; - } - if (options->options & REDIS_OPT_NOAUTOFREE) { - c->flags |= REDIS_NO_AUTO_FREE; - } - if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { - c->flags |= REDIS_NO_AUTO_FREE_REPLIES; - } - - /* Set any user supplied RESP3 PUSH handler or use freeReplyObject - * as a default unless specifically flagged that we don't want one. */ - if (options->push_cb != NULL) - redisSetPushCallback(c, options->push_cb); - else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE)) - redisSetPushCallback(c, redisPushAutoFree); - - c->privdata = options->privdata; - c->free_privdata = options->free_privdata; - - if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK || - redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) { - __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); - return c; - } - - if (options->type == REDIS_CONN_TCP) { - redisContextConnectBindTcp(c, options->endpoint.tcp.ip, - options->endpoint.tcp.port, options->connect_timeout, - options->endpoint.tcp.source_addr); - } else if (options->type == REDIS_CONN_UNIX) { - redisContextConnectUnix(c, options->endpoint.unix_socket, - options->connect_timeout); - } else if (options->type == REDIS_CONN_USERFD) { - c->fd = options->endpoint.fd; - c->flags |= REDIS_CONNECTED; - } else { - redisFree(c); - return NULL; - } - - if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { - redisContextSetTimeout(c, *options->command_timeout); - } - - return c; -} - -/* 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. - * When no set of reply functions is given, the default set will be used. */ -redisContext *redisConnect(const char *ip, int port) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.connect_timeout = &tv; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectNonBlock(const char *ip, int port) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.options |= REDIS_OPT_NONBLOCK; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectBindNonBlock(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.endpoint.tcp.source_addr = source_addr; - options.options |= REDIS_OPT_NONBLOCK; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.endpoint.tcp.source_addr = source_addr; - options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectUnix(const char *path) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - options.connect_timeout = &tv; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectUnixNonBlock(const char *path) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - options.options |= REDIS_OPT_NONBLOCK; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectFd(redisFD fd) { - redisOptions options = {0}; - options.type = REDIS_CONN_USERFD; - options.endpoint.fd = fd; - return redisConnectWithOptions(&options); -} - -/* Set read/write timeout on a blocking socket. */ -int redisSetTimeout(redisContext *c, const struct timeval tv) { - if (c->flags & REDIS_BLOCK) - return redisContextSetTimeout(c,tv); - return REDIS_ERR; -} - -/* Enable connection KeepAlive. */ -int redisEnableKeepAlive(redisContext *c) { - if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) - return REDIS_ERR; - return REDIS_OK; -} - -/* Set a user provided RESP3 PUSH handler and return any old one set. */ -redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) { - redisPushFn *old = c->push_cb; - c->push_cb = fn; - return old; -} - -/* Use this function to handle a read event on the descriptor. It will try - * and read some bytes from the socket and feed them to the reply parser. - * - * After this function is called, you may use redisGetReplyFromReader to - * see if there is a reply available. */ -int redisBufferRead(redisContext *c) { - char buf[1024*16]; - int nread; - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - nread = c->funcs->read(c, buf, sizeof(buf)); - if (nread < 0) { - return REDIS_ERR; - } - if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { - __redisSetError(c, c->reader->err, c->reader->errstr); - return REDIS_ERR; - } - return REDIS_OK; -} - -/* Write the output buffer to the socket. - * - * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was - * successfully written to the socket. When the buffer is empty after the - * write operation, "done" is set to 1 (if given). - * - * Returns REDIS_ERR if an error occurred trying to write and sets - * c->errstr to hold the appropriate error string. - */ -int redisBufferWrite(redisContext *c, int *done) { - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - if (hi_sdslen(c->obuf) > 0) { - ssize_t nwritten = c->funcs->write(c); - if (nwritten < 0) { - return REDIS_ERR; - } else if (nwritten > 0) { - if (nwritten == (ssize_t)hi_sdslen(c->obuf)) { - hi_sdsfree(c->obuf); - c->obuf = hi_sdsempty(); - if (c->obuf == NULL) - goto oom; - } else { - if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom; - } - } - } - if (done != NULL) *done = (hi_sdslen(c->obuf) == 0); - return REDIS_OK; - -oom: - __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); - return REDIS_ERR; -} - -/* Internal helper that returns 1 if the reply was a RESP3 PUSH - * message and we handled it with a user-provided callback. */ -static int redisHandledPushReply(redisContext *c, void *reply) { - if (reply && c->push_cb && redisIsPushReply(reply)) { - c->push_cb(c->privdata, reply); - return 1; - } - - return 0; -} - -/* Get a reply from our reader or set an error in the context. */ -int redisGetReplyFromReader(redisContext *c, void **reply) { - if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { - __redisSetError(c,c->reader->err,c->reader->errstr); - return REDIS_ERR; - } - - return REDIS_OK; -} - -/* Internal helper to get the next reply from our reader while handling - * any PUSH messages we encounter along the way. This is separate from - * redisGetReplyFromReader so as to not change its behavior. */ -static int redisNextInBandReplyFromReader(redisContext *c, void **reply) { - do { - if (redisGetReplyFromReader(c, reply) == REDIS_ERR) - return REDIS_ERR; - } while (redisHandledPushReply(c, *reply)); - - return REDIS_OK; -} - -int redisFlush(redisContext *c) { - int wdone = 0; - void *aux = NULL; - /* Try to read pending replies */ - if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - return REDIS_ERR; - } while (!wdone); - return REDIS_OK; -} - -int redisGetReply(redisContext *c, void **reply) { - int wdone = 0; - void *aux = NULL; - - /* Try to read pending replies */ - if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - - /* For the blocking context, flush output buffer and read reply */ - if (aux == NULL && c->flags & REDIS_BLOCK) { - /* Write until done */ - do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - return REDIS_ERR; - } while (!wdone); - - /* Read until there is a reply */ - do { - if (redisBufferRead(c) == REDIS_ERR) - return REDIS_ERR; - - if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) - return REDIS_ERR; - } while (aux == NULL); - } - - /* Set reply or free it if we were passed NULL */ - if (reply != NULL) { - *reply = aux; - } else { - freeReplyObject(aux); - } - - return REDIS_OK; -} - - -/* Helper function for the redisAppendCommand* family of functions. - * - * Write a formatted command to the output buffer. When this family - * is used, you need to call redisGetReply yourself to retrieve - * the reply (or replies in pub/sub). - */ -int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { - hisds newbuf; - - newbuf = hi_sdscatlen(c->obuf,cmd,len); - if (newbuf == NULL) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - c->obuf = newbuf; - return REDIS_OK; -} - -int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { - - if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { - char *cmd; - int len; - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - 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) { - hi_free(cmd); - return REDIS_ERR; - } - - hi_free(cmd); - return REDIS_OK; -} - -int redisAppendCommand(redisContext *c, const char *format, ...) { - va_list ap; - int ret; - - va_start(ap,format); - ret = redisvAppendCommand(c,format,ap); - va_end(ap); - return ret; -} - -int redisSendCommand(redisContext *c, const char *format, ...) { - va_list ap; - int ret; - - va_start(ap,format); - ret = redisvAppendCommand(c,format,ap); - va_end(ap); - return redisFlush(c); -} - -int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - hisds cmd; - long long len; - - len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); - if (len == -1) { - __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; - } - - if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { - hi_sdsfree(cmd); - return REDIS_ERR; - } - - hi_sdsfree(cmd); - return REDIS_OK; -} - -/* Helper function for the redisCommand* family of functions. - * - * Write a formatted command to the output buffer. If the given context is - * blocking, immediately read the reply into the "reply" pointer. When the - * context is non-blocking, the "reply" pointer will not be used and the - * command is simply appended to the write buffer. - * - * Returns the reply when a reply was successfully retrieved. Returns NULL - * otherwise. When NULL is returned in a blocking context, the error field - * in the context will be set. - */ -static void *__redisBlockForReply(redisContext *c) { - void *reply; - - if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) - return NULL; - return reply; - } - return NULL; -} - -void *redisvCommand(redisContext *c, const char *format, va_list ap) { - if (redisvAppendCommand(c,format,ap) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} - -void *redisCommand(redisContext *c, const char *format, ...) { - va_list ap; - va_start(ap,format); - void *reply = redisvCommand(c,format,ap); - va_end(ap); - return reply; -} - -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { - if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) - return NULL; - return __redisBlockForReply(c); -} diff --git a/src/hiredis.h b/src/hiredis.h deleted file mode 100644 index e3377d9..0000000 --- a/src/hiredis.h +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __HIREDIS_H -#define __HIREDIS_H -#include "read.h" -#include /* for va_list */ -#ifndef _MSC_VER -#include /* for struct timeval */ -#else -struct timeval; /* forward declaration */ -typedef long long ssize_t; -#endif -#include /* uintXX_t, etc */ -#include "sds.h" /* for hisds */ -#include "alloc.h" /* for allocation wrappers */ - -#define HIREDIS_MAJOR 1 -#define HIREDIS_MINOR 0 -#define HIREDIS_PATCH 3 -#define HIREDIS_SONAME 1.0.3-dev - -/* Connection type can be blocking or non-blocking and is set in the - * least significant bit of the flags field in redisContext. */ -#define REDIS_BLOCK 0x1 - -/* Connection may be disconnected before being free'd. The second bit - * in the flags field is set when the context is connected. */ -#define REDIS_CONNECTED 0x2 - -/* The async API might try to disconnect cleanly and flush the output - * buffer and read all subsequent replies before disconnecting. - * This flag means no new commands can come in and the connection - * should be terminated once all replies have been read. */ -#define REDIS_DISCONNECTING 0x4 - -/* Flag specific to the async API which means that the context should be clean - * up as soon as possible. */ -#define REDIS_FREEING 0x8 - -/* Flag that is set when an async callback is executed. */ -#define REDIS_IN_CALLBACK 0x10 - -/* Flag that is set when the async context has one or more subscriptions. */ -#define REDIS_SUBSCRIBED 0x20 - -/* Flag that is set when monitor mode is active */ -#define REDIS_MONITORING 0x40 - -/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ -#define REDIS_REUSEADDR 0x80 - -/* Flag that is set when the async connection supports push replies. */ -#define REDIS_SUPPORTS_PUSH 0x100 - -/** - * Flag that indicates the user does not want the context to - * be automatically freed upon error - */ -#define REDIS_NO_AUTO_FREE 0x200 - -/* Flag that indicates the user does not want replies to be automatically freed */ -#define REDIS_NO_AUTO_FREE_REPLIES 0x400 - -#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 - -/* Forward declarations for structs defined elsewhere */ -struct redisAsyncContext; -struct redisContext; - -/* RESP3 push helpers and callback prototypes */ -#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH) -typedef void (redisPushFn)(void *, void *); -typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *); - -#ifdef __cplusplus -extern "C" { -#endif - -/* This is the reply object returned by redisCommand() */ -typedef struct redisReply { - int type; /* REDIS_REPLY_* */ - long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ - double dval; /* The double when type is REDIS_REPLY_DOUBLE */ - size_t len; /* Length of string */ - char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING - REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval), - and REDIS_REPLY_BIGNUM. */ - char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null - terminated 3 character content type, such as "txt". */ - size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ - struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ -} redisReply; - -redisReader *redisReaderCreate(void); - -/* Function to free the reply objects hiredis returns by default. */ -void freeReplyObject(void *reply); - -/* Functions to format a command according to the protocol. */ -int redisvFormatCommand(char **target, const char *format, va_list ap); -int redisFormatCommand(char **target, const char *format, ...); -long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); -long long redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen); -void redisFreeCommand(char *cmd); -void redisFreeSdsCommand(hisds cmd); - -enum redisConnectionType { - REDIS_CONN_TCP, - REDIS_CONN_UNIX, - REDIS_CONN_USERFD -}; - -struct redisSsl; - -#define REDIS_OPT_NONBLOCK 0x01 -#define REDIS_OPT_REUSEADDR 0x02 - -/** - * Don't automatically free the async object on a connection failure, - * or other implicit conditions. Only free on an explicit call to disconnect() or free() - */ -#define REDIS_OPT_NOAUTOFREE 0x04 - -/* Don't automatically intercept and free RESP3 PUSH replies. */ -#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 - -/** - * Don't automatically free replies - */ -#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 - -/* In Unix systems a file descriptor is a regular signed int, with -1 - * representing an invalid descriptor. In Windows it is a SOCKET - * (32- or 64-bit unsigned integer depending on the architecture), where - * all bits set (~0) is INVALID_SOCKET. */ -#ifndef _WIN32 -typedef int redisFD; -#define REDIS_INVALID_FD -1 -#else -#ifdef _WIN64 -typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ -#else -typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ -#endif -#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ -#endif - -typedef struct { - /* - * the type of connection to use. This also indicates which - * `endpoint` member field to use - */ - int type; - /* bit field of REDIS_OPT_xxx */ - int options; - /* timeout value for connect operation. If NULL, no timeout is used */ - const struct timeval *connect_timeout; - /* timeout value for commands. If NULL, no timeout is used. This can be - * updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */ - const struct timeval *command_timeout; - union { - /** use this field for tcp/ip connections */ - struct { - const char *source_addr; - const char *ip; - int port; - } tcp; - /** use this field for unix domain sockets */ - const char *unix_socket; - /** - * use this field to have hiredis operate an already-open - * file descriptor */ - redisFD fd; - } endpoint; - - /* Optional user defined data/destructor */ - void *privdata; - void (*free_privdata)(void *); - - /* A user defined PUSH message callback */ - redisPushFn *push_cb; - redisAsyncPushFn *async_push_cb; -} redisOptions; - -/** - * Helper macros to initialize options to their specified fields. - */ -#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ - (opts)->type = REDIS_CONN_TCP; \ - (opts)->endpoint.tcp.ip = ip_; \ - (opts)->endpoint.tcp.port = port_; - -#define REDIS_OPTIONS_SET_UNIX(opts, path) \ - (opts)->type = REDIS_CONN_UNIX; \ - (opts)->endpoint.unix_socket = path; - -#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \ - (opts)->privdata = data; \ - (opts)->free_privdata = dtor; \ - -typedef struct redisContextFuncs { - void (*free_privctx)(void *); - void (*async_read)(struct redisAsyncContext *); - void (*async_write)(struct redisAsyncContext *); - ssize_t (*read)(struct redisContext *, char *, size_t); - ssize_t (*write)(struct redisContext *); -} redisContextFuncs; - -/* Context for a connection to Redis */ -typedef struct redisContext { - const redisContextFuncs *funcs; /* Function table */ - - int err; /* Error flags, 0 when there is no error */ - char errstr[128]; /* String representation of error when applicable */ - redisFD fd; - int flags; - char *obuf; /* Write buffer */ - redisReader *reader; /* Protocol reader */ - - enum redisConnectionType connection_type; - struct timeval *connect_timeout; - struct timeval *command_timeout; - - struct { - char *host; - char *source_addr; - int port; - } tcp; - - struct { - char *path; - } unix_sock; - - /* For non-blocking */ - struct sockaddr *saddr; - size_t addrlen; - - /* Optional data and corresponding destructor users can use to provide - * context to a given redisContext. Not used by hiredis. */ - void *privdata; - void (*free_privdata)(void *); - - /* Internal context pointer presently used by hiredis to manage - * SSL connections. */ - void *privctx; - - /* An optional RESP3 PUSH handler */ - redisPushFn *push_cb; -} redisContext; - -redisContext *redisConnectWithOptions(const redisOptions *options); -redisContext *redisConnect(const char *ip, int port); -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); -redisContext *redisConnectNonBlock(const char *ip, int port); -redisContext *redisConnectBindNonBlock(const char *ip, int port, - const char *source_addr); -redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, - const char *source_addr); -redisContext *redisConnectUnix(const char *path); -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); -redisContext *redisConnectUnixNonBlock(const char *path); -redisContext *redisConnectFd(redisFD 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); - -redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn); -int redisSetTimeout(redisContext *c, const struct timeval tv); -int redisEnableKeepAlive(redisContext *c); -void redisFree(redisContext *c); -redisFD redisFreeKeepFd(redisContext *c); -int redisBufferRead(redisContext *c); -int redisBufferWrite(redisContext *c, int *done); - -int redisFlush(redisContext *c); -/* In a blocking context, this function first checks if there are unconsumed - * replies to return and returns one if so. Otherwise, it flushes the output - * buffer to the socket and reads until it has a reply. In a non-blocking - * context, it will return unconsumed replies until there are no more. */ -int redisGetReply(redisContext *c, void **reply); -int redisGetReplyFromReader(redisContext *c, void **reply); - -/* Write a formatted command to the output buffer. Use these functions in blocking mode - * to get a pipeline of commands. */ -int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); - -/* Write a command to the output buffer. Use these functions in blocking mode - * to get a pipeline of commands. */ -int redisvAppendCommand(redisContext *c, const char *format, va_list ap); -int redisAppendCommand(redisContext *c, const char *format, ...); -int redisSendCommand(redisContext *c, const char *format, ...); -int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); - -/* Issue a command to Redis. In a blocking context, it is identical to calling - * redisAppendCommand, followed by redisGetReply. The function will return - * NULL if there was an error in performing the request, otherwise it will - * return the reply. In a non-blocking context, it is identical to calling - * only redisAppendCommand and will always return NULL. */ -void *redisvCommand(redisContext *c, const char *format, va_list ap); -void *redisCommand(redisContext *c, const char *format, ...); -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/hiredis_ssl.h b/src/hiredis_ssl.h deleted file mode 100644 index e3d3e1c..0000000 --- a/src/hiredis_ssl.h +++ /dev/null @@ -1,129 +0,0 @@ - -/* - * Copyright (c) 2019, Redis Labs - * - * 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_SSL_H -#define __HIREDIS_SSL_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* This is the underlying struct for SSL in ssl.h, which is not included to - * keep build dependencies short here. - */ -struct ssl_st; - -/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly - * calling OpenSSL. - */ -typedef struct redisSSLContext redisSSLContext; - -/** - * Initialization errors that redisCreateSSLContext() may return. - */ - -typedef enum { - REDIS_SSL_CTX_NONE = 0, /* No Error */ - REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */ - REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ - REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ - REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ - REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */ - REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certifcate store */ - REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */ -} redisSSLContextError; - -/** - * Return the error message corresponding with the specified error code. - */ - -const char *redisSSLContextGetError(redisSSLContextError error); - -/** - * Helper function to initialize the OpenSSL library. - * - * OpenSSL requires one-time initialization before it can be used. Callers should - * call this function only once, and only if OpenSSL is not directly initialized - * elsewhere. - */ -int redisInitOpenSSL(void); - -/** - * Helper function to initialize an OpenSSL context that can be used - * to initiate SSL connections. - * - * cacert_filename is an optional name of a CA certificate/bundle file to load - * and use for validation. - * - * capath is an optional directory path where trusted CA certificate files are - * stored in an OpenSSL-compatible structure. - * - * cert_filename and private_key_filename are optional names of a client side - * certificate and private key files to use for authentication. They need to - * be both specified or omitted. - * - * server_name is an optional and will be used as a server name indication - * (SNI) TLS extension. - * - * If error is non-null, it will be populated in case the context creation fails - * (returning a NULL). - */ - -redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath, - const char *cert_filename, const char *private_key_filename, - const char *server_name, redisSSLContextError *error); - -/** - * Free a previously created OpenSSL context. - */ -void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx); - -/** - * Initiate SSL on an existing redisContext. - * - * This is similar to redisInitiateSSL() but does not require the caller - * to directly interact with OpenSSL, and instead uses a redisSSLContext - * previously created using redisCreateSSLContext(). - */ - -int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx); - -/** - * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object. - */ - -int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); - -#ifdef __cplusplus -} -#endif - -#endif /* __HIREDIS_SSL_H */ diff --git a/src/log.c b/src/log.cpp similarity index 52% rename from src/log.c rename to src/log.cpp index f6ec670..bbc2015 100644 --- a/src/log.c +++ b/src/log.cpp @@ -1,77 +1,65 @@ -#include +#include +#include #include #include #include #include #include #include "log.h" -#include "hiredis.h" - - +#include "redis-migrate.h" +#include "hiredis/hiredis.h" static int is_leap_year(time_t year) { - if (year % 4) return 0; /* A year not divisible by 4 is not leap. */ - else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */ - else if (year % 400) return 0; /* If div by 100 *and* not by 400 is not leap. */ - else return 1; /* If div by 100 and 400 is leap. */ + if (year % 4) + return 0; /* A year not divisible by 4 is not leap. */ + else if (year % 100) + return 1; /* If div by 4 and not 100 is surely leap. */ + else if (year % 400) + return 0; /* If div by 100 *and* not by 400 is not leap. */ + else + return 1; /* If div by 100 and 400 is leap. */ } -void createLogObj(char *logfile) { - log = hi_malloc(sizeof(logObj)); - log->logfile = logfile; - log->loglevel = LL_NOTICE; -} - - -void _serverLog(int level, const char *fmt, ...) { +void _migrateLog(logObj mLog, int level, const char *fmt, ...) { va_list ap; char msg[LOG_MAX_LEN]; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); + migrateLogRaw(mLog, level, msg); } -void serverLogRaw(int level, const char *msg) { +void migrateLogRaw(logObj mLog, int level, const char *msg) { const char *c = ".-*#"; - FILE *fp; - char buf[64]; - int log_to_stdout = log->logfile[0] == '\0'; - if (level < log->loglevel) { + if (level < mLog.loglevel) { return; } - - fp = log_to_stdout ? stdout : fopen(log->logfile, "a"); - if (!fp) return; + char buf[64]; + std::ofstream outfile(mLog.logfile, std::ios::app); int off; struct timeval tv; - int role_char; pid_t pid = getpid(); gettimeofday(&tv, NULL); struct tm tm; nolocks_localtime(&tm, tv.tv_sec, timezone, 1); off = strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S.", &tm); - snprintf(buf + off, sizeof(buf) - off, "%03d", (int) tv.tv_usec / 1000); - role_char = 'm'; - fprintf(fp, "%d:%c %s %c %s\n", - (int) pid, role_char, buf, c[level], msg); - - fflush(fp); - - if (!log_to_stdout) fclose(fp); + snprintf(buf + off, sizeof(buf) - off, "%03d", (int)tv.tv_usec / 1000); + outfile << pid << ":" << "m" << " " << buf << " " << c[level] << " <" << MODULE_NAME << "> " << msg << std::endl; + outfile.close(); } void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) { const time_t secs_min = 60; const time_t secs_hour = 3600; - const time_t secs_day = 3600*24; + const time_t secs_day = 3600 * 24; - t -= tz; /* Adjust for timezone. */ - t += 3600*dst; /* Adjust for daylight time. */ - time_t days = t / secs_day; /* Days passed since epoch. */ - time_t seconds = t % secs_day; /* Remaining seconds. */ + t -= tz; /* Adjust for timezone. */ + t += 3600 * dst; /* Adjust for daylight time. */ + time_t days = t / secs_day; /* Days passed since epoch. */ + time_t seconds = t % secs_day; /* Remaining seconds. */ tmp->tm_isdst = dst; tmp->tm_hour = seconds / secs_hour; @@ -81,18 +69,18 @@ void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) { /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure * where sunday = 0, so to calculate the day of the week we have to add 4 * and take the modulo by 7. */ - tmp->tm_wday = (days+4)%7; + tmp->tm_wday = (days + 4) % 7; /* Calculate the current year. */ tmp->tm_year = 1970; - while(1) { + while (1) { /* Leap years have one day more. */ time_t days_this_year = 365 + is_leap_year(tmp->tm_year); if (days_this_year > days) break; days -= days_this_year; tmp->tm_year++; } - tmp->tm_yday = days; /* Number of day of the current year. */ + tmp->tm_yday = days; /* Number of day of the current year. */ /* We need to calculate in which month and day of the month we are. To do * so we need to skip days according to how many days there are in each @@ -101,11 +89,11 @@ void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) { mdays[1] += is_leap_year(tmp->tm_year); tmp->tm_mon = 0; - while(days >= mdays[tmp->tm_mon]) { + while (days >= mdays[tmp->tm_mon]) { days -= mdays[tmp->tm_mon]; tmp->tm_mon++; } - tmp->tm_mday = days+1; /* Add 1 since our 'days' is zero-based. */ - tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */ + tmp->tm_mday = days + 1; /* Add 1 since our 'days' is zero-based. */ + tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */ } diff --git a/src/log.h b/src/log.h index 7a783c9..822b311 100644 --- a/src/log.h +++ b/src/log.h @@ -14,20 +14,16 @@ #define LOG_MAX_LEN 1024 typedef struct logObj { - char *logfile; + const char *logfile; int loglevel; } logObj; -static logObj *log; +#define migrateLog(mLog, level, ...) _migrateLog(mLog, level, __VA_ARGS__); -void createLogObj(char *logfile); +void _migrateLog(logObj mLog, int level, const char *fmt, ...) +__attribute__((format(printf, 3, 4))); -#define serverLog(level, ...) _serverLog(level, __VA_ARGS__); - -void _serverLog(int level, const char *fmt, ...) -__attribute__((format(printf, 2, 3))); - -void serverLogRaw(int level, const char *msg); +void migrateLogRaw(logObj mLog, int level, const char *msg); void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst); diff --git a/src/net.c b/src/net.c deleted file mode 100644 index e552576..0000000 --- a/src/net.c +++ /dev/null @@ -1,556 +0,0 @@ -/* Extracted from anet.c to work properly with Hiredis error reporting. - * - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "net.h" -#include "sds.h" - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -void redisNetClose(redisContext *c) { - if (c && c->fd != REDIS_INVALID_FD) { - close(c->fd); - c->fd = REDIS_INVALID_FD; - } -} - -ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { - ssize_t nread = recv(c->fd, buf, bufcap, 0); - if (nread == -1) { - if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - return 0; - } else if (errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { - /* especially in windows */ - __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); - return -1; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } else if (nread == 0) { - __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); - return -1; - } else { - return nread; - } -} - -ssize_t redisNetWrite(redisContext *c) { - ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0); - if (nwritten < 0) { - if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } - return nwritten; -} - -static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { - int errorno = errno; /* snprintf() may change errno */ - char buf[128] = {0}; - size_t len = 0; - - if (prefix != NULL) - len = snprintf(buf, sizeof(buf), "%s: ", prefix); - strerror_r(errorno, (char *) (buf + len), sizeof(buf) - len); - __redisSetError(c, type, buf); -} - -static int redisSetReuseAddr(redisContext *c) { - int on = 1; - if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - redisNetClose(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int redisCreateSocket(redisContext *c, int type) { - redisFD s; - if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - return REDIS_ERR; - } - c->fd = s; - if (type == AF_INET) { - if (redisSetReuseAddr(c) == REDIS_ERR) { - return REDIS_ERR; - } - } - return REDIS_OK; -} - -static int redisSetBlocking(redisContext *c, int blocking) { -#ifndef _WIN32 - int flags; - - /* Set the socket nonblocking. - * Note that fcntl(2) for F_GETFL and F_SETFL can't be - * interrupted by a signal. */ - if ((flags = fcntl(c->fd, F_GETFL)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "fcntl(F_GETFL)"); - redisNetClose(c); - return REDIS_ERR; - } - - if (blocking) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - - if (fcntl(c->fd, F_SETFL, flags) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "fcntl(F_SETFL)"); - redisNetClose(c); - return REDIS_ERR; - } -#else - u_long mode = blocking ? 0 : 1; - if (ioctl(c->fd, FIONBIO, &mode) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); - redisNetClose(c); - return REDIS_ERR; - } -#endif /* _WIN32 */ - return REDIS_OK; -} - -int redisKeepAlive(redisContext *c, int interval) { - int val = 1; - redisFD fd = c->fd; - - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1) { - __redisSetError(c, REDIS_ERR_OTHER, strerror(errno)); - return REDIS_ERR; - } - - val = interval; - -#if defined(__APPLE__) && defined(__MACH__) - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { - __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); - return REDIS_ERR; - } -#else -#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { - __redisSetError(c, REDIS_ERR_OTHER, strerror(errno)); - return REDIS_ERR; - } - - val = interval / 3; - if (val == 0) val = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { - __redisSetError(c, REDIS_ERR_OTHER, strerror(errno)); - return REDIS_ERR; - } - - val = 3; - if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { - __redisSetError(c, REDIS_ERR_OTHER, strerror(errno)); - return REDIS_ERR; - } -#endif -#endif - - return REDIS_OK; -} - -int redisSetTcpNoDelay(redisContext *c) { - int yes = 1; - if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "setsockopt(TCP_NODELAY)"); - redisNetClose(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) - -static int redisContextTimeoutMsec(redisContext *c, long *result) { - const struct timeval *timeout = c->connect_timeout; - long msec = -1; - - /* Only use timeout when not NULL. */ - if (timeout != NULL) { - if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { - *result = msec; - return REDIS_ERR; - } - - msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); - - if (msec < 0 || msec > INT_MAX) { - msec = INT_MAX; - } - } - - *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) { - int res; - - if ((res = poll(wfd, 1, msec)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - redisNetClose(c); - return REDIS_ERR; - } else if (res == 0) { - errno = ETIMEDOUT; - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - redisNetClose(c); - return REDIS_ERR; - } - - if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { - redisCheckSocketError(c); - return REDIS_ERR; - } - - return REDIS_OK; - } - - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - redisNetClose(c); - return REDIS_ERR; -} - -int redisCheckConnectDone(redisContext *c, int *completed) { - int rc = connect(c->fd, (const struct sockaddr *) c->saddr, c->addrlen); - if (rc == 0) { - *completed = 1; - return REDIS_OK; - } - switch (errno) { - case EISCONN: - *completed = 1; - return REDIS_OK; - case EALREADY: - case EINPROGRESS: - case EWOULDBLOCK: - *completed = 0; - return REDIS_OK; - default: - return REDIS_ERR; - } -} - -int redisCheckSocketError(redisContext *c) { - int err = 0, errno_saved = errno; - socklen_t errlen = sizeof(err); - - if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "getsockopt(SO_ERROR)"); - return REDIS_ERR; - } - - if (err == 0) { - err = errno_saved; - } - - if (err) { - errno = err; - __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisContextSetTimeout(redisContext *c, const struct timeval tv) { - const void *to_ptr = &tv; - size_t to_sz = sizeof(tv); - - if (setsockopt(c->fd, SOL_SOCKET, SO_RCVTIMEO, to_ptr, to_sz) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "setsockopt(SO_RCVTIMEO)"); - return REDIS_ERR; - } - if (setsockopt(c->fd, SOL_SOCKET, SO_SNDTIMEO, to_ptr, to_sz) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "setsockopt(SO_SNDTIMEO)"); - return REDIS_ERR; - } - return REDIS_OK; -} - -int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) { - /* Same timeval struct, short circuit */ - if (c->connect_timeout == timeout) - return REDIS_OK; - - /* Allocate context timeval if we need to */ - if (c->connect_timeout == NULL) { - c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout)); - if (c->connect_timeout == NULL) - return REDIS_ERR; - } - - memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout)); - return REDIS_OK; -} - -int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) { - /* Same timeval struct, short circuit */ - if (c->command_timeout == timeout) - return REDIS_OK; - - /* Allocate context timeval if we need to */ - if (c->command_timeout == NULL) { - c->command_timeout = hi_malloc(sizeof(*c->command_timeout)); - if (c->command_timeout == NULL) - return REDIS_ERR; - } - - memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout)); - return REDIS_OK; -} - -static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr) { - redisFD s; - int rv, n; - char _port[6]; /* strlen("65535"); */ - struct addrinfo hints, *servinfo, *bservinfo, *p, *b; - int blocking = (c->flags & REDIS_BLOCK); - int reuseaddr = (c->flags & REDIS_REUSEADDR); - int reuses = 0; - 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) { - hi_free(c->tcp.host); - - c->tcp.host = hi_strdup(addr); - if (c->tcp.host == NULL) - goto oom; - } - - if (timeout) { - if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) - goto oom; - } else { - hi_free(c->connect_timeout); - c->connect_timeout = NULL; - } - - if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { - __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); - goto error; - } - - if (source_addr == NULL) { - hi_free(c->tcp.source_addr); - c->tcp.source_addr = NULL; - } else if (c->tcp.source_addr != source_addr) { - hi_free(c->tcp.source_addr); - c->tcp.source_addr = hi_strdup(source_addr); - } - - snprintf(_port, 6, "%d", port); - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - /* Try with IPv6 if no IPv4 address was found. We do it in this order since - * in a Redis client you can't afford to test if you have IPv6 connectivity - * as this would add latency to every connect. Otherwise a more sensible - * route could be: Use IPv6 if both addresses are available and there is IPv6 - * connectivity. */ - if ((rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo)) != 0) { - hints.ai_family = AF_INET6; - if ((rv = getaddrinfo(addr, _port, &hints, &servinfo)) != 0) { - __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv)); - return REDIS_ERR; - } - } - for (p = servinfo; p != NULL; p = p->ai_next) { - addrretry: - if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == REDIS_INVALID_FD) - continue; - - c->fd = s; - if (redisSetBlocking(c, 0) != REDIS_OK) - goto error; - if (c->tcp.source_addr) { - int bound = 0; - /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ - if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { - char buf[128]; - snprintf(buf, sizeof(buf), "Can't get addr: %s", gai_strerror(rv)); - __redisSetError(c, REDIS_ERR_OTHER, buf); - goto error; - } - - if (reuseaddr) { - n = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &n, - sizeof(n)) < 0) { - freeaddrinfo(bservinfo); - goto error; - } - } - - for (b = bservinfo; b != NULL; b = b->ai_next) { - if (bind(s, b->ai_addr, b->ai_addrlen) != -1) { - bound = 1; - break; - } - } - freeaddrinfo(bservinfo); - if (!bound) { - char buf[128]; - snprintf(buf, sizeof(buf), "Can't bind socket: %s", strerror(errno)); - __redisSetError(c, REDIS_ERR_OTHER, buf); - goto error; - } - } - - /* For repeat connection */ - hi_free(c->saddr); - c->saddr = hi_malloc(p->ai_addrlen); - if (c->saddr == NULL) - goto oom; - - memcpy(c->saddr, p->ai_addr, p->ai_addrlen); - c->addrlen = p->ai_addrlen; - - if (connect(s, p->ai_addr, p->ai_addrlen) == -1) { - if (errno == EHOSTUNREACH) { - redisNetClose(c); - continue; - } else if (errno == EINPROGRESS) { - if (blocking) { - goto wait_for_ready; - } - /* This is ok. - * Note that even when it's in blocking mode, we unset blocking - * for `connect()` - */ - } else if (errno == EADDRNOTAVAIL && reuseaddr) { - if (++reuses >= REDIS_CONNECT_RETRIES) { - goto error; - } else { - redisNetClose(c); - goto addrretry; - } - } else { - wait_for_ready: - if (redisContextWaitReady(c, timeout_msec) != REDIS_OK) - goto error; - if (redisSetTcpNoDelay(c) != REDIS_OK) - goto error; - } - } - if (blocking && redisSetBlocking(c, 1) != REDIS_OK) - goto error; - - c->flags |= REDIS_CONNECTED; - rv = REDIS_OK; - goto end; - } - if (p == NULL) { - char buf[128]; - snprintf(buf, sizeof(buf), "Can't create socket: %s", strerror(errno)); - __redisSetError(c, REDIS_ERR_OTHER, buf); - goto error; - } - - oom: - __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); - error: - rv = REDIS_ERR; - end: - if (servinfo) { - freeaddrinfo(servinfo); - } - - return rv; // Need to return REDIS_OK if alright -} - -int redisContextConnectTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout) { - return _redisContextConnectTcp(c, addr, port, timeout, NULL); -} - -int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr) { - return _redisContextConnectTcp(c, addr, port, timeout, source_addr); -} - -int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { - return REDIS_ERR; -} diff --git a/src/net.h b/src/net.h deleted file mode 100644 index 9f43283..0000000 --- a/src/net.h +++ /dev/null @@ -1,56 +0,0 @@ -/* Extracted from anet.c to work properly with Hiredis error reporting. - * - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2014, Pieter Noordhuis - * Copyright (c) 2015, Matt Stancliff , - * Jan-Erik Rediger - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __NET_H -#define __NET_H - -#include "hiredis.h" - -void redisNetClose(redisContext *c); -ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap); -ssize_t redisNetWrite(redisContext *c); - -int redisCheckSocketError(redisContext *c); -int redisContextSetTimeout(redisContext *c, const struct timeval tv); -int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); -int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, - const struct timeval *timeout, - const char *source_addr); -int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); -int redisKeepAlive(redisContext *c, int interval); -int redisCheckConnectDone(redisContext *c, int *completed); - -int redisSetTcpNoDelay(redisContext *c); - -#endif diff --git a/src/rdbLoad.c b/src/rdbLoad.c deleted file mode 100644 index 097a32c..0000000 --- a/src/rdbLoad.c +++ /dev/null @@ -1,374 +0,0 @@ -#include "rdbLoad.h" -#include "sds.h" -#include "sdscompat.h" - -#include -#include -#include -#include -#include - -int rmLoadRioWithLoading(migrateObj *mobj) { - char buf[1024]; - int error; - if (syncReadLine(mobj->source_cc->fd, buf, 9, mobj->timeout) == -1) { - serverLog(LL_WARNING, "[rm]read version failed:%s,port:%d ", mobj->host, - mobj->port); - return C_ERR; - } - buf[9] = '\0'; - serverLog(LL_NOTICE, "[rm] version=%s", buf); - if (memcmp(buf, "REDIS", 5) != 0) { - serverLog(LL_WARNING, "[rm] Wrong signature trying to load DB from file"); - errno = EINVAL; - return C_ERR; - } - int type, rdbver; - uint64_t dbid = 0; - rdbver = atoi(buf + 5); - if (rdbver < 1 || rdbver > RDB_VERSION) { - serverLog(LL_WARNING, "Can't handle RDB format version %d", rdbver); - errno = EINVAL; - return C_ERR; - } - long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime(); - - // syncReadLine(mobj->source_cc->fd, buf, 4, mobj->timeout); - // buf[4] = '\0'; - // serverLog(LL_NOTICE, "buf:%s", buf); - // type = atoi(buf); - // serverLog(LL_NOTICE, "type:%d", type); - char type_buf[1]; - if (read(mobj->source_cc->fd, &type_buf, 1) == 0) - { - serverLog(LL_WARNING, "read mark failed"); - return C_ERR; - } - - while (1) { - sds key; - robj *val; - if (read(mobj->source_cc->fd, &type_buf, 1) == 0) { - serverLog(LL_WARNING, "read type failed"); - return C_ERR; - } - int type = type_buf[0] & 0xff; - serverLog(LL_NOTICE, "type: %d", type); - - if (type == RDB_OPCODE_EXPIRETIME) { - expiretime = rmLoadTime(mobj); - if (expiretime == -1) { - return C_ERR; - } - expiretime *= 1000; - continue; - } else if (type == RDB_OPCODE_EXPIRETIME_MS) { - expiretime = rmLoadMillisecondTime(mobj, rdbver); - continue; - } else if (type == RDB_OPCODE_FREQ) { - uint8_t byte; - if (read(mobj->source_cc->fd, &byte, 1) == 0) - return C_ERR; - lfu_freq = byte; - continue; - } else if (type == RDB_OPCODE_IDLE) { - uint64_t qword; - if ((qword = rmLoadLen(mobj, NULL)) == RDB_LENERR) - return C_ERR; - lru_idle = qword; - continue; - } else if (type == RDB_OPCODE_EOF) { - serverLog(LL_NOTICE, "rdb file parse end.."); - break; - } else if (type == RDB_OPCODE_SELECTDB) { - if ((dbid = rmLoadLen(mobj, NULL)) == RDB_LENERR) return C_ERR; - continue; - } else if (type == RDB_OPCODE_RESIZEDB) { - uint64_t db_size, expires_size; - if ((db_size = rmLoadLen(mobj, NULL)) == RDB_LENERR) return C_ERR; - if ((expires_size = rmLoadLen(mobj, NULL)) == RDB_LENERR) return C_ERR; - continue; - } else if (type == RDB_OPCODE_AUX) { - sds *auxkey, *auxval; - if ((auxkey = rmLoadStringObject(mobj)) == NULL) { - serverLog(LL_WARNING, "auxkey is null"); - return NULL; - } - if ((auxval = rmLoadStringObject(mobj)) == NULL) { - serverLog(LL_WARNING, "auxval is null"); - return NULL; - } - if (auxkey[0] == '%') { - serverLog(LL_NOTICE, "RDB '%s': %s", auxkey, auxval); - } else if (!strcasecmp(auxkey, "repl-id")) { - memcmp(mobj->psync_replid, auxval, CONFIG_RUN_ID_SIZE + 1); - serverLog(LL_NOTICE, "repl-id: %s", auxval); - } else if (!strcasecmp(auxkey, "repl-offset")) { - serverLog(LL_NOTICE, "repl-offset: %s", auxval); - } else if (!strcasecmp(auxkey, "lua") || !strcasecmp(auxkey, "aof-base") - || !strcasecmp(auxkey, "redis-bits") || !strcasecmp(auxkey, "aof-preamble") - || !strcasecmp(auxkey, "repl-stream-db") || !strcasecmp(auxkey, "redis-ver") - || !strcasecmp(auxkey, "ctime")) { - serverLog(LL_NOTICE, "RDB '%s': %s", auxkey, auxval); - } else if (!strcasecmp(auxkey, "used-mem")) { - long long usedmem = strtoll(auxval, NULL, 10); - serverLog(LL_NOTICE, "RDB memory usage when created %.2f Mb", (double)usedmem / (1024 * 1024)); - } else { - serverLog(LL_DEBUG, "Unrecognized RDB AUX field: '%s'", auxkey); - } - continue; - - } else if (type == RDB_OPCODE_FUNCTION || type == RDB_OPCODE_FUNCTION2) { - continue; - } - if ((key = rmGenericLoadStringObject(mobj, RDB_LOAD_SDS, NULL)) == NULL) { - return NULL; - } - val = rmLoadObject(type, mobj, key, &error); - - if (val == NULL) { - } - } -} - -robj *rmLoadObject(int rdbtype, migrateObj *mobj, sds key, int *error) { - int dbid = 0; - robj *o = NULL, *ele, *dec; - uint64_t len; - unsigned int i; - - return o; -} - -sds rmLoadStringObject(migrateObj *mobj) { - return rmGenericLoadStringObject(mobj, RDB_LOAD_NONE, NULL); -} - -sds rmGenericLoadStringObject(migrateObj *mobj, int flags, size_t *lenptr) { - int encode = flags & RDB_LOAD_ENC; - int plain = flags & RDB_LOAD_PLAIN; - int sds = flags & RDB_LOAD_SDS; - int isencoded; - unsigned long long len; - len = rmLoadLen(mobj, &isencoded); - serverLog(LL_NOTICE, "len=%d, isencoded=%d", len, isencoded); - if (len == RDB_LENERR) return NULL; - if (isencoded) { - switch (len) { - case RDB_ENC_INT8: - case RDB_ENC_INT16: - case RDB_ENC_INT32: - return rmLoadIntegerObject(mobj, len, flags, lenptr); - case RDB_ENC_LZF: - return rmLoadLzfStringObject(mobj, flags, lenptr); - default: - serverLog(LL_WARNING, "Unknown RDB string encoding type %llu", len); - return NULL; - } - } - if (plain || sds) { - void *buf = sds_malloc(len); - if (!buf) { - serverLog(LL_WARNING, "rdbGenericLoadStringObject failed allocating %llu bytes", len); - return NULL; - } - if (lenptr) *lenptr = len; - if (len && read(mobj->source_cc->fd, buf, len) == 0) { - if (plain) - zfree(buf); - else - sdsfree(buf); - return NULL; - } - return hi_sdsnewlen(buf, len); - } else { - char buf[len]; - if (!buf) { - serverLog(LL_WARNING, "rdbGenericLoadStringObject failed allocating %llu bytes", len); - return NULL; - } - if (len && read(mobj->source_cc->fd, buf, len) == 0) { - return NULL; - } - buf[len] = '\0'; - return hi_sdsnewlen(buf, len); - } -} - -void *rmLoadIntegerObject(migrateObj *mobj, int enctype, int flags, size_t *lenptr) { - int plain = flags & RDB_LOAD_PLAIN; - int sds = flags & RDB_LOAD_SDS; - int encode = flags & RDB_LOAD_ENC; - unsigned char enc[4]; - long long val; - if (enctype == RDB_ENC_INT8) { - if (read(mobj->source_cc->fd, enc, 1) == 0) return NULL; - val = (signed char)enc[0]; - } else if (enctype == RDB_ENC_INT16) { - uint16_t v; - if (read(mobj->source_cc->fd, enc, 2) == 0) return NULL; - v = ((uint32_t)enc[0]) | ((uint32_t)enc[1] << 8); - val = (int16_t)v; - } else if (enctype == RDB_ENC_INT32) { - uint32_t v; - if (read(mobj->source_cc->fd, enc, 4) == 0) return NULL; - v = ((uint32_t)enc[0]) | ((uint32_t)enc[1] << 8) | ((uint32_t)enc[2] << 16) | ((uint32_t)enc[3] << 24); - val = (int32_t)v; - } else { - // rdbReportCorruptRDB("Unknown RDB integer encoding type %d", enctype); - return NULL; /* Never reached. */ - } - serverLog(LL_NOTICE, "plain=%d, encode=%d", plain, encode); - if (plain || sds) { - char buf[LONG_STR_SIZE], *p; - int len = ll2string(buf, sizeof(buf), val); - if (lenptr) *lenptr = len; - p = plain ? zmalloc(len) : sdsnewlen(SDS_NOINIT, len); - memcpy(p, buf, len); - return p; - } else if (encode) { - return createStringObjectFromLongLongForValue(val); - } else { - return sdsfromlonglong(val); - } -} - -void *rmLoadLzfStringObject(migrateObj *mobj, int flags, size_t *lenptr) { - int plain = flags & RDB_LOAD_PLAIN; - int sds = flags & RDB_LOAD_SDS; - uint64_t len, clen; - unsigned char *c = NULL; - char *val = NULL; - if ((clen = rmLoadLen(mobj, NULL)) == RDB_LENERR) return NULL; - if ((len = rmLoadLen(mobj, NULL)) == RDB_LENERR) return NULL; - if ((c = hi_malloc(clen)) == NULL) { - serverLog(LL_WARNING, "rdbLoadLzfStringObject failed allocating %llu bytes", (unsigned long long)clen); - goto err; - } - if (plain) { - val = hi_malloc(len); - } else { - val = hi_sdsnewlen(SDS_NOINIT, len); - } - if (!val) { - serverLog(LL_WARNING, "rdbLoadLzfStringObject failed allocating %llu bytes", (unsigned long long)len); - goto err; - } - if (lenptr) *lenptr = len; - if (read(mobj->source_cc->fd, c, len) == 0) return NULL; - if (lzf_decompress(c, clen, val, len) != len) { - serverLog(LL_WARNING, "Invalid LZF compressed string"); - goto err; - } - zfree(c); - - if (plain || sds) { - return val; - } else { - return createObject(OBJ_STRING, val); - } -err: - zfree(c); - if (plain) - zfree(val); - else - sdsfree(val); - return NULL; -} - -uint64_t rmLoadLen(migrateObj *mobj, int *isencoded) { - uint64_t len; - - if (rmLoadLenByRef(mobj, isencoded, &len) == -1) - return RDB_LENERR; - return len; -} - -int rmLoadType(migrateObj *mobj) { - unsigned char type; - if (rmRead(mobj, &type, 1) == 0) { - return -1; - } - - return type; -} - -int rmRead(migrateObj *mobj, void *buf, size_t len) { - if (read(mobj->source_cc->fd, &buf, len) == 0) { - return 0; - } - buf = (char *)buf + len; - return 1; -} - -int rmLoadLenByRef(migrateObj *mobj, int *isencoded, uint64_t *lenptr) { - unsigned char buf[2]; - int type; - if (isencoded) - *isencoded = 0; - if (read(mobj->source_cc->fd, buf, 1) == 0) { - return -1; - } - type = (buf[0] & 0xC0) >> 6; - if (type == RDB_ENCVAL) { - if (isencoded) - *isencoded = 1; - *lenptr = buf[0] & 0x3F; - } else if (type == RDB_6BITLEN) { - /* Read a 6 bit len. */ - *lenptr = buf[0] & 0x3F; - } else if (type == RDB_14BITLEN) { - if (read(mobj->source_cc->fd, buf + 1, 1) == 0) - return -1; - *lenptr = ((buf[0] & 0x3F) << 8) | buf[1]; - } else if (buf[0] == RDB_32BITLEN) { - uint32_t len; - if (read(mobj->source_cc->fd, &len, 4) == 0) - return -1; - *lenptr = ntohl(len); - } else if (buf[0] == RDB_64BITLEN) { - uint64_t len; - if (read(mobj->source_cc->fd, &len, 8) == 0) - return -1; - *lenptr = ntohu64(len); - } else { - return -1; - } - return 0; -} - -time_t rmLoadTime(migrateObj *mobj) { - int32_t t32; - if (read(mobj->source_cc->fd, &t32, 4) == 0) { - return -1; - } - t32 = (char *)t32 + 4; - return (time_t)t32; -} - -long long rmLoadMillisecondTime(migrateObj *mobj, int rdbver) { - int64_t t64; - if (read(mobj->source_cc->fd, &t64, 8) == 0) { - return LLONG_MAX; - } - if (rdbver >= 9) - memrev64(&t64); - return (long long)t64; -} - -void rm_memrev64(void *p) { - unsigned char *x = p, t; - - t = x[0]; - x[0] = x[7]; - x[7] = t; - t = x[1]; - x[1] = x[6]; - x[6] = t; - t = x[2]; - x[2] = x[5]; - x[5] = t; - t = x[3]; - x[3] = x[4]; - x[4] = t; -} diff --git a/src/rdbLoad.h b/src/rdbLoad.h deleted file mode 100644 index 589b935..0000000 --- a/src/rdbLoad.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef RDB_LOAD_REDIS_MIGRATE_H -#define RDB_LOAD_REDIS_MIGRATE_H -#include "redis-migrate.h" - -#define RDB_6BITLEN 0 -#define RDB_14BITLEN 1 -#define RDB_32BITLEN 0x80 -#define RDB_64BITLEN 0x81 -#define RDB_ENCVAL 3 -#define RDB_LENERR UINT64_MAX - -#define RDB_VERSION 10 -#define RDB_OPCODE_FUNCTION2 245 /* function library data */ -#define RDB_OPCODE_FUNCTION 246 /* old function library data for 7.0 rc1 and rc2 */ -#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */ -#define RDB_OPCODE_IDLE 248 /* LRU idle time. */ -#define RDB_OPCODE_FREQ 249 /* LFU frequency. */ -#define RDB_OPCODE_AUX 250 /* RDB aux field. */ -#define RDB_OPCODE_RESIZEDB 251 /* Hash table resize hint. */ -#define RDB_OPCODE_EXPIRETIME_MS 252 /* Expire time in milliseconds. */ -#define RDB_OPCODE_EXPIRETIME 253 /* Old expire time in seconds. */ -#define RDB_OPCODE_SELECTDB 254 /* DB number of the following keys. */ -#define RDB_OPCODE_EOF 255 /* End of the RDB file. */ - -#define RDB_LOAD_NONE 0 -#define RDB_LOAD_ENC (1 << 0) -#define RDB_LOAD_PLAIN (1 << 1) -#define RDB_LOAD_SDS (1 << 2) - -#define RDB_ENC_INT8 0 /* 8 bit signed integer */ -#define RDB_ENC_INT16 1 /* 16 bit signed integer */ -#define RDB_ENC_INT32 2 /* 32 bit signed integer */ -#define RDB_ENC_LZF 3 /* string compressed with FASTLZ */ - -#define LONG_STR_SIZE 21 -extern const char *SDS_NOINIT; -#define OBJ_STRING 0 /* String object. */ -#define OBJ_LIST 1 /* List object. */ -#define OBJ_SET 2 /* Set object. */ -#define OBJ_ZSET 3 /* Sorted set object. */ -#define OBJ_HASH 4 /* Hash object. */ - -#define htonu64(v) intrev64(v) -#define ntohu64(v) intrev64(v) - -#define LLONG_MAX __LONG_LONG_MAX__ - -int rmRead(migrateObj *mobj, void *buf, size_t len); - -int rmLoadRioWithLoading(migrateObj *mobj); - -int rmLoadType(migrateObj *mobj); - -time_t rmLoadTime(migrateObj *mobj); - -long long rmLoadMillisecondTime(migrateObj *mobj, int rdbver); - -int rmLoadLenByRef(migrateObj *mobi, int *isencoded, uint64_t *lenptr); - -void rm_memrev64(void *p); - -uint64_t rmLoadLen(migrateObj *mobj, int *isencoded); - -sds rmLoadStringObject(migrateObj *mobj); - -sds rmGenericLoadStringObject(migrateObj *mobj, int flags, size_t *lenptr); - -void *rmLoadIntegerObject(migrateObj *mobj, int enctype, int flags, size_t *lenptr); - -void *rmLoadLzfStringObject(migrateObj *mobj, int flags, size_t *lenptr); - -robj *rmLoadObject(int rdbtype, migrateObj *mobj, sds key, int *error); - -#endif diff --git a/src/read.c b/src/read.c deleted file mode 100644 index 810c2d3..0000000 --- a/src/read.c +++ /dev/null @@ -1,784 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include -#include -#ifndef _MSC_VER -#include -#include -#endif -#include -#include -#include -#include -#include - -#include "alloc.h" -#include "read.h" -#include "sds.h" - -/* Initial size of our nested reply stack and how much we grow it when needd */ -#define REDIS_READER_STACK_SIZE 9 - -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. */ - hi_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) { - char *ret; - - /* We cannot match with fewer than 2 bytes */ - if (len < 2) - return NULL; - - /* Search up to len - 1 characters */ - len--; - - /* Look for the \r */ - while ((ret = memchr(s, '\r', len)) != NULL) { - if (ret[1] == '\n') { - /* Found. */ - break; - } - /* Continue searching. */ - ret++; - len -= ret - s; - s = ret; - } - - return ret; -} - -/* Convert a string into a long long. Returns REDIS_OK if the string could be - * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value - * will be set to the parsed value when appropriate. - * - * Note that this function demands that the string strictly represents - * a long long: no spaces or other characters before or after the string - * representing the number are accepted, nor zeroes at the start if not - * for the string "0" representing the zero number. - * - * Because of its strictness, it is safe to use this function to check if - * you can convert a string into a long long, and obtain back the string - * from the number without any loss in the string representation. */ -static int string2ll(const char *s, size_t slen, long long *value) { - const char *p = s; - size_t plen = 0; - int negative = 0; - unsigned long long v; - - if (plen == slen) - return REDIS_ERR; - - /* Special case: first and only digit is 0. */ - if (slen == 1 && p[0] == '0') { - if (value != NULL) *value = 0; - return REDIS_OK; - } - - if (p[0] == '-') { - negative = 1; - p++; plen++; - - /* Abort on only a negative sign. */ - if (plen == slen) - return REDIS_ERR; - } - - /* First digit should be 1-9, otherwise the string should just be 0. */ - if (p[0] >= '1' && p[0] <= '9') { - v = p[0]-'0'; - p++; plen++; - } else if (p[0] == '0' && slen == 1) { - *value = 0; - return REDIS_OK; - } else { - return REDIS_ERR; - } - - while (plen < slen && p[0] >= '0' && p[0] <= '9') { - if (v > (ULLONG_MAX / 10)) /* Overflow. */ - return REDIS_ERR; - v *= 10; - - if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ - return REDIS_ERR; - v += p[0]-'0'; - - p++; plen++; - } - - /* Return if not all bytes were used. */ - if (plen < slen) - return REDIS_ERR; - - if (negative) { - if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ - return REDIS_ERR; - if (value != NULL) *value = -v; - } else { - if (v > LLONG_MAX) /* Overflow. */ - return REDIS_ERR; - if (value != NULL) *value = v; - } - return REDIS_OK; -} - -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->task[r->ridx]; - prv = r->task[r->ridx-1]; - assert(prv->type == REDIS_REPLY_ARRAY || - prv->type == REDIS_REPLY_MAP || - prv->type == REDIS_REPLY_SET || - prv->type == REDIS_REPLY_PUSH); - 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->task[r->ridx]; - void *obj; - char *p; - int len; - - if ((p = readLine(r,&len)) != NULL) { - if (cur->type == REDIS_REPLY_INTEGER) { - long long v; - - if (string2ll(p, len, &v) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad integer value"); - return REDIS_ERR; - } - - if (r->fn && r->fn->createInteger) { - obj = r->fn->createInteger(cur,v); - } else { - obj = (void*)REDIS_REPLY_INTEGER; - } - } else if (cur->type == REDIS_REPLY_DOUBLE) { - char buf[326], *eptr; - double d; - - if ((size_t)len >= sizeof(buf)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Double value is too large"); - return REDIS_ERR; - } - - memcpy(buf,p,len); - buf[len] = '\0'; - - if (len == 3 && strcasecmp(buf,"inf") == 0) { - d = INFINITY; /* Positive infinite. */ - } else if (len == 4 && strcasecmp(buf,"-inf") == 0) { - d = -INFINITY; /* Negative infinite. */ - } else { - d = strtod((char*)buf,&eptr); - /* RESP3 only allows "inf", "-inf", and finite values, while - * strtod() allows other variations on infinity, NaN, - * etc. We explicity handle our two allowed infinite cases - * above, so strtod() should only result in finite values. */ - if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad double value"); - return REDIS_ERR; - } - } - - if (r->fn && r->fn->createDouble) { - obj = r->fn->createDouble(cur,d,buf,len); - } else { - obj = (void*)REDIS_REPLY_DOUBLE; - } - } else if (cur->type == REDIS_REPLY_NIL) { - if (len != 0) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad nil value"); - return REDIS_ERR; - } - - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - } else if (cur->type == REDIS_REPLY_BOOL) { - int bval; - - if (len != 1 || !strchr("tTfF", p[0])) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad bool value"); - return REDIS_ERR; - } - - bval = p[0] == 't' || p[0] == 'T'; - if (r->fn && r->fn->createBool) - obj = r->fn->createBool(cur,bval); - else - obj = (void*)REDIS_REPLY_BOOL; - } else if (cur->type == REDIS_REPLY_BIGNUM) { - /* Ensure all characters are decimal digits (with possible leading - * minus sign). */ - for (int i = 0; i < len; i++) { - /* XXX Consider: Allow leading '+'? Error on leading '0's? */ - if (i == 0 && p[0] == '-') continue; - if (p[i] < '0' || p[i] > '9') { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad bignum value"); - return REDIS_ERR; - } - } - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,p,len); - else - obj = (void*)REDIS_REPLY_BIGNUM; - } else { - /* Type will be error or status. */ - for (int i = 0; i < len; i++) { - if (p[i] == '\r' || p[i] == '\n') { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad simple string value"); - return REDIS_ERR; - } - } - 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->task[r->ridx]; - void *obj = NULL; - char *p, *s; - long 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 */ - - if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad bulk string length"); - return REDIS_ERR; - } - - if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bulk string length out of range"); - return REDIS_ERR; - } - - if (len == -1) { - /* 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 ((cur->type == REDIS_REPLY_VERB && len < 4) || - (cur->type == REDIS_REPLY_VERB && s[5] != ':')) - { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Verbatim string 4 bytes of content type are " - "missing or incorrectly encoded."); - return REDIS_ERR; - } - if (r->fn && r->fn->createString) - obj = r->fn->createString(cur,s+2,len); - else - obj = (void*)(long)cur->type; - 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 redisReaderGrow(redisReader *r) { - redisReadTask **aux; - int newlen; - - /* Grow our stack size */ - newlen = r->tasks + REDIS_READER_STACK_SIZE; - aux = hi_realloc(r->task, sizeof(*r->task) * newlen); - if (aux == NULL) - goto oom; - - r->task = aux; - - /* Allocate new tasks */ - for (; r->tasks < newlen; r->tasks++) { - r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); - if (r->task[r->tasks] == NULL) - goto oom; - } - - return REDIS_OK; -oom: - __redisReaderSetErrorOOM(r); - return REDIS_ERR; -} - -/* Process the array, map and set types. */ -static int processAggregateItem(redisReader *r) { - redisReadTask *cur = r->task[r->ridx]; - void *obj; - char *p; - long long elements; - int root = 0, len; - - if (r->ridx == r->tasks - 1) { - if (redisReaderGrow(r) == REDIS_ERR) - return REDIS_ERR; - } - - if ((p = readLine(r,&len)) != NULL) { - if (string2ll(p, len, &elements) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad multi-bulk length"); - return REDIS_ERR; - } - - root = (r->ridx == 0); - - if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) || - (r->maxelements > 0 && elements > r->maxelements)) - { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Multi-bulk length out of range"); - return REDIS_ERR; - } - - 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 (cur->type == REDIS_REPLY_MAP) elements *= 2; - - if (r->fn && r->fn->createArray) - obj = r->fn->createArray(cur,elements); - else - obj = (void*)(long)cur->type; - - 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->task[r->ridx]->type = -1; - r->task[r->ridx]->elements = -1; - r->task[r->ridx]->idx = 0; - r->task[r->ridx]->obj = NULL; - r->task[r->ridx]->parent = cur; - r->task[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->task[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_DOUBLE; - break; - case '_': - cur->type = REDIS_REPLY_NIL; - break; - case '$': - cur->type = REDIS_REPLY_STRING; - break; - case '*': - cur->type = REDIS_REPLY_ARRAY; - break; - case '%': - cur->type = REDIS_REPLY_MAP; - break; - case '~': - cur->type = REDIS_REPLY_SET; - break; - case '#': - cur->type = REDIS_REPLY_BOOL; - break; - case '=': - cur->type = REDIS_REPLY_VERB; - break; - case '>': - cur->type = REDIS_REPLY_PUSH; - break; - case '(': - cur->type = REDIS_REPLY_BIGNUM; - 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: - case REDIS_REPLY_DOUBLE: - case REDIS_REPLY_NIL: - case REDIS_REPLY_BOOL: - case REDIS_REPLY_BIGNUM: - return processLineItem(r); - case REDIS_REPLY_STRING: - case REDIS_REPLY_VERB: - return processBulkItem(r); - case REDIS_REPLY_ARRAY: - case REDIS_REPLY_MAP: - case REDIS_REPLY_SET: - case REDIS_REPLY_PUSH: - return processAggregateItem(r); - default: - assert(NULL); - return REDIS_ERR; /* Avoid warning. */ - } -} - -redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { - redisReader *r; - - r = hi_calloc(1,sizeof(redisReader)); - if (r == NULL) - return NULL; - - r->buf = hi_sdsempty(); - if (r->buf == NULL) - goto oom; - - r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task)); - if (r->task == NULL) - goto oom; - - for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) { - r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); - if (r->task[r->tasks] == NULL) - goto oom; - } - - r->fn = fn; - r->maxbuf = REDIS_READER_MAX_BUF; - r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS; - r->ridx = -1; - - return r; -oom: - redisReaderFree(r); - return NULL; -} - -void redisReaderFree(redisReader *r) { - if (r == NULL) - return; - - if (r->reply != NULL && r->fn && r->fn->freeObject) - r->fn->freeObject(r->reply); - - if (r->task) { - /* We know r->task[i] is allocated if i < r->tasks */ - for (int i = 0; i < r->tasks; i++) { - hi_free(r->task[i]); - } - - hi_free(r->task); - } - - hi_sdsfree(r->buf); - hi_free(r); -} - -int redisReaderFeed(redisReader *r, const char *buf, size_t len) { - hisds 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 && hi_sdsavail(r->buf) > r->maxbuf) { - hi_sdsfree(r->buf); - r->buf = hi_sdsempty(); - if (r->buf == 0) goto oom; - - r->pos = 0; - } - - newbuf = hi_sdscatlen(r->buf,buf,len); - if (newbuf == NULL) goto oom; - - r->buf = newbuf; - r->len = hi_sdslen(r->buf); - } - - return REDIS_OK; -oom: - __redisReaderSetErrorOOM(r); - return REDIS_ERR; -} - -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->task[0]->type = -1; - r->task[0]->elements = -1; - r->task[0]->idx = -1; - r->task[0]->obj = NULL; - r->task[0]->parent = NULL; - r->task[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) { - if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; - r->pos = 0; - r->len = hi_sdslen(r->buf); - } - - /* Emit a reply when there is one. */ - if (r->ridx == -1) { - if (reply != NULL) { - *reply = r->reply; - } else if (r->reply != NULL && r->fn && r->fn->freeObject) { - r->fn->freeObject(r->reply); - } - r->reply = NULL; - } - return REDIS_OK; -} diff --git a/src/read.h b/src/read.h deleted file mode 100644 index 2d74d77..0000000 --- a/src/read.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * - * 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 /* 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_TIMEOUT 6 /* Timed out */ -#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_REPLY_DOUBLE 7 -#define REDIS_REPLY_BOOL 8 -#define REDIS_REPLY_MAP 9 -#define REDIS_REPLY_SET 10 -#define REDIS_REPLY_ATTR 11 -#define REDIS_REPLY_PUSH 12 -#define REDIS_REPLY_BIGNUM 13 -#define REDIS_REPLY_VERB 14 - -/* Default max unused reader buffer. */ -#define REDIS_READER_MAX_BUF (1024*16) - -/* Default multi-bulk element limit */ -#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1) - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct redisReadTask { - int type; - long long 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*, size_t); - void *(*createInteger)(const redisReadTask*, long long); - void *(*createDouble)(const redisReadTask*, double, char*, size_t); - void *(*createNil)(const redisReadTask*); - void *(*createBool)(const redisReadTask*, int); - 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 */ - long long maxelements; /* Max multi-bulk elements */ - - redisReadTask **task; - int tasks; - - 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 diff --git a/src/redis-migrate.c b/src/redis-migrate.c deleted file mode 100644 index bbfce48..0000000 --- a/src/redis-migrate.c +++ /dev/null @@ -1,396 +0,0 @@ - -#include -#include -#include -#include "redis-migrate.h" -#include "hiredis.h" -#include "rdbLoad.h" - -static migrateObj *mobj; - -migrateObj *createMigrateObject(robj *host, int port, int begin_slot, int end_slot) { - migrateObj *m; - m = hi_malloc(sizeof(*m)); - m->host = host->ptr; - m->port = port; - m->begin_slot = begin_slot; - m->end_slot = end_slot; - m->repl_stat = REPL_STATE_NONE; - m->isCache = 0; - m->timeout = 10 * 1000; - m->repl_transfer_size = -1; - return m; -} - -void freeMigrateObj(migrateObj *m) { - redisFree(m->source_cc); - hi_free(m); - mobj = NULL; -} - -long long ustime(void) { - struct timeval tv; - long long ust; - - gettimeofday(&tv, NULL); - ust = ((long long)tv.tv_sec) * 1000000; - ust += tv.tv_usec; - return ust; -} - -/* Return the UNIX time in milliseconds */ -mstime_t mstime(void) { - return ustime() / 1000; -} - -int sendSyncCommand() { - if (!mobj->isCache) { - mobj->isCache = 1; - mobj->psync_replid = "?"; - memcpy(mobj->psync_offset, "-1", 3); - } - if (redisAppendCommand(mobj->source_cc, "PSYNC %s %s", mobj->psync_replid, mobj->psync_offset) != REDIS_OK) { - serverLog(LL_WARNING, "append PSYNC %s %s failed ip:%s,port:%d, ", - mobj->psync_replid, mobj->psync_offset, mobj->host, mobj->port); - return 0; - } - if (redisFlush(mobj->source_cc) != REDIS_OK) { - serverLog(LL_WARNING, "send PSYNC %s %s failed ip:%s,port:%d, ", - mobj->psync_replid, mobj->psync_offset, mobj->host, mobj->port); - return 0; - } - serverLog(LL_NOTICE, "PSYNC %s %s ", mobj->psync_replid, mobj->psync_offset); - return 1; -} - -sds redisReceive() { - char buf[256]; - if (syncReadLine(mobj->source_cc->fd, buf, sizeof(buf), mobj->timeout) == -1) { - serverLog(LL_WARNING, "redisReceive failed ip:%s,port:%d ", mobj->host, mobj->port); - return NULL; - } - return hi_sdsnew(buf); -} - -int receiveDataFromRedis() { - sds reply = redisReceive(); - if (reply == NULL) { - serverLog(LL_WARNING, "Master did not reply to PSYNC"); - return PSYNC_TRY_LATER; - } - if (sdslen(reply) == 0) { - sdsfree(reply); - return PSYNC_WAIT_REPLY; - } - serverLog(LL_NOTICE, "reply=%s", reply); - if (!strncmp(reply, "+FULLRESYNC", 11)) { - char *replid = NULL, *offset = NULL; - - /* FULL RESYNC, parse the reply in order to extract the replid - * and the replication offset. */ - replid = strchr(reply, ' '); - if (replid) { - replid++; - offset = strchr(replid, ' '); - if (offset) - offset++; - } - if (!replid || !offset || (offset - replid - 1) != CONFIG_RUN_ID_SIZE) { - serverLog(LL_WARNING, "Master replied with wrong +FULLRESYNC syntax."); - /* This is an unexpected condition, actually the +FULLRESYNC - * reply means that the master supports PSYNC, but the reply - * format seems wrong. To stay safe we blank the master - * replid to make sure next PSYNCs will fail. */ - memset(mobj->master_replid, 0, CONFIG_RUN_ID_SIZE + 1); - } else { - memcpy(mobj->master_replid, replid, offset - replid - 1); - mobj->master_replid[CONFIG_RUN_ID_SIZE] = '\0'; - mobj->master_initial_offset = strtoll(offset, NULL, 10); - serverLog(LL_NOTICE, "Full sync from master: %s:%lld", - mobj->master_replid, mobj->master_initial_offset); - } - sdsfree(reply); - return PSYNC_FULLRESYNC; - } - if (!strncmp(reply, "+CONTINUE", 9)) { - /* Partial resync was accepted. */ - serverLog(LL_NOTICE, "Successful partial resynchronization with master."); - - /* Check the new replication ID advertised by the master. If it - * changed, we need to set the new ID as primary ID, and set - * secondary ID as the old master ID up to the current offset, so - * that our sub-slaves will be able to PSYNC with us after a - * disconnection. */ - char *start = reply + 10; - char *end = reply + 9; - while (end[0] != '\r' && end[0] != '\n' && end[0] != '\0') - end++; - if (end - start == CONFIG_RUN_ID_SIZE) { - char new[CONFIG_RUN_ID_SIZE + 1]; - memcpy(new, start, CONFIG_RUN_ID_SIZE); - new[CONFIG_RUN_ID_SIZE] = '\0'; - - return PSYNC_CONTINUE; - } - } -} - -void readFullData() { - static char eofmark[CONFIG_RUN_ID_SIZE]; - static char lastbytes[CONFIG_RUN_ID_SIZE]; - static int usemark = 0; - char buf[PROTO_IOBUF_LEN]; - if (mobj->repl_transfer_size == -1) { - int nread = syncReadLine(mobj->source_cc->fd, buf, PROTO_IOBUF_LEN, mobj->timeout); - if (nread == -1) { - serverLog(LL_WARNING, "read full data failed"); - goto error; - } - if (buf[0] == '-') { - serverLog(LL_WARNING, "MASTER aborted replication with an error: %s", buf + 1); - goto error; - } else if (buf[0] == '\0') { - // mobj->repl_transfer_lastio = server.unixtime; - return; - } else if (buf[0] != '$') { - serverLog(LL_WARNING, "Bad protocol from MASTER, the first byte is not '$' (we received '%s'), are you sure the host and port are right?", buf); - goto error; - } - - if (strncmp(buf + 1, "EOF:", 4) == 0 && strlen(buf + 5) >= CONFIG_RUN_ID_SIZE) { - usemark = 1; - usemark = 1; - memcpy(eofmark, buf + 5, CONFIG_RUN_ID_SIZE); - memset(lastbytes, 0, CONFIG_RUN_ID_SIZE); - /* Set any repl_transfer_size to avoid entering this code path - * at the next call. */ - mobj->repl_transfer_size = 0; - serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF to parser"); - } else { - usemark = 0; - mobj->repl_transfer_size = strtol(buf + 1, NULL, 10); - serverLog(LL_NOTICE, - "MASTER <-> REPLICA sync: receiving %lld bytes from master to parser", - (long long)mobj->repl_transfer_size); - } - } - int flag = rmLoadRioWithLoading(mobj); - - return; -error: - cancelMigrate(); - return; -} - -void cancelMigrate() { -} - -void syncDataWithRedis(int fd, void *user_data, int mask) { - REDISMODULE_NOT_USED(fd); - REDISMODULE_NOT_USED(mask); - REDISMODULE_NOT_USED(user_data); - sds err = NULL; - if (mobj->repl_stat == REPL_STATE_CONNECTING) { - if (redisSendCommand(mobj->source_cc, "PING") != REDIS_OK) { - serverLog(LL_WARNING, "send PING failed ip:%s,port:%d", - mobj->host, mobj->port); - goto error; - } - mobj->repl_stat = REPL_STATE_RECEIVE_PING_REPLY; - return; - } - if (mobj->repl_stat == REPL_STATE_RECEIVE_PING_REPLY) { - err = redisReceive(); - if (err == NULL) - goto no_response_error; - if (err[0] != '+' && strncmp(err, "-NOAUTH", 7) != 0 && strncmp(err, "-NOPERM", 7) != 0 && strncmp(err, "-ERR operation not permitted", 28) != 0) { - serverLog(LL_WARNING, "Error reply to PING from master: '%s'", err); - sdsfree(err); - goto error; - } else { - serverLog(LL_NOTICE, "Master replied to PING, replication can continue..."); - } - sdsfree(err); - err = NULL; - mobj->repl_stat = REPL_STATE_SEND_HANDSHAKE; - return; - } - if (mobj->repl_stat == REPL_STATE_SEND_HANDSHAKE) { - // todo 增加认证 - mobj->repl_stat = REPL_STATE_RECEIVE_AUTH_REPLY; - } - if (mobj->repl_stat == REPL_STATE_RECEIVE_AUTH_REPLY) { - // todo 接受认证信息 - sds portstr = sdsfromlonglong(mobj->port); - if (redisSendCommand(mobj->source_cc, "REPLCONF listening-port %s", portstr) != REDIS_OK) { - serverLog(LL_WARNING, "send PING failed ip:%s,port:%d", - mobj->host, mobj->port); - goto error; - } - mobj->repl_stat = REPL_STATE_RECEIVE_PORT_REPLY; - sdsfree(portstr); - return; - } - if (mobj->repl_stat == REPL_STATE_RECEIVE_PORT_REPLY) { - err = redisReceive(); - if (err == NULL) - goto no_response_error; - if (err[0] == '-') { - serverLog(LL_NOTICE, "(Non critical) Master does not understand REPLCONF listening-port: %s", err); - goto error; - } - serverLog(LL_NOTICE, "REPLCONF listening-port success"); - if (redisSendCommand(mobj->source_cc, "REPLCONF ip-address %s", mobj->host) != REDIS_OK) { - serverLog(LL_WARNING, "REPLCONF ip-address %s failed", mobj->host); - goto error; - } - sdsfree(err); - err = NULL; - mobj->repl_stat = REPL_STATE_RECEIVE_IP_REPLY; - return; - } - if (mobj->repl_stat == REPL_STATE_RECEIVE_IP_REPLY) { - err = redisReceive(); - if (err == NULL) - goto no_response_error; - if (err[0] == '-') { - serverLog(LL_NOTICE, "(Non critical) Master does not understand REPLCONF ip-address: %s", err); - goto error; - } - serverLog(LL_NOTICE, "REPLCONF REPLCONF ip-address success"); - if (redisSendCommand(mobj->source_cc, "REPLCONF %s %s %s %s", "capa", "eof", "capa", "psync2") != REDIS_OK) { - serverLog(LL_WARNING, "send REPLCONF capa eof capa psync2 failed"); - goto error; - } - sdsfree(err); - err = NULL; - mobj->repl_stat = REPL_STATE_RECEIVE_CAPA_REPLY; - return; - } - if (mobj->repl_stat == REPL_STATE_RECEIVE_CAPA_REPLY) { - err = redisReceive(); - if (err == NULL) - goto no_response_error; - if (err[0] == '-') { - serverLog(LL_NOTICE, "(Non critical) Master does not understand REPLCONF capa: %s", err); - goto error; - } - serverLog(LL_NOTICE, "REPLCONF capa eof capa psync2 success"); - sdsfree(err); - err = NULL; - mobj->repl_stat = REPL_STATE_SEND_PSYNC; - return; - } - if (mobj->repl_stat == REPL_STATE_SEND_PSYNC) { - if (!sendSyncCommand()) { - serverLog(LL_WARNING, "send PSYNC %s %s failed ip:%s,port:%d, ", - mobj->psync_replid, mobj->psync_offset, mobj->host, mobj->port); - goto error; - } - mobj->repl_stat = REPL_STATE_RECEIVE_PSYNC_REPLY; - return; - } - - if (mobj->repl_stat == REPL_STATE_RECEIVE_PSYNC_REPLY) { - int psync_result = receiveDataFromRedis(); - if (psync_result == PSYNC_WAIT_REPLY) - return; - if (psync_result == PSYNC_TRY_LATER) - goto error; - if (psync_result == PSYNC_CONTINUE) { - mobj->repl_stat = REPL_STATE_CONTINUE_SYNC; - } else { - mobj->repl_stat = REPL_STATE_FULL_SYNC; - } - } - - // 接受全部数据 - if (mobj->repl_stat == REPL_STATE_FULL_SYNC) { - serverLog(LL_NOTICE, "begin receive full data"); - mobj->repl_stat = REPL_STATE_READING_FULL_DATA; - readFullData(); - } - if (mobj->repl_stat == REPL_STATE_CONTINUE_SYNC) { - serverLog(LL_NOTICE, "begin receive continue data"); - } - - return; -error: - if (err != NULL) { - sdsfree(err); - } - freeMigrateObj(mobj); -no_response_error: /* Handle receiveSynchronousResponse() error when master has no reply */ - serverLog(LL_WARNING, "Master did not respond to command during SYNC handshake"); -} - -/** - * migrate data to current instance. - * migrate host port begin-slot end-slot - * @param ctxz - * @param argv - * @param argc - * @return - */ -int rm_migrateCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - if (argc != 5) { - return RedisModule_WrongArity(ctx); - } - if (RedisModule_IsKeysPositionRequest(ctx)) { - RedisModule_Log(ctx, VERBOSE, "get keys from module"); - return RedisModule_ReplyWithSimpleString(ctx, "OK"); - } - robj *host = (robj *)argv[1]; - robj *port = (robj *)argv[2]; - robj *begin_slot = (robj *)argv[3]; - robj *end_slot = (robj *)argv[4]; - RedisModule_Log(ctx, NOTICE, "host:%s, port:%s, begin:%s, end:%s", (char *)host->ptr, - (char *)port->ptr, (char *)begin_slot->ptr, (char *)end_slot->ptr); - if (mobj != NULL) { - return RedisModule_ReplyWithError(ctx, "migrating, please waiting"); - } - mobj = createMigrateObject(host, atoi(port->ptr), atoi(begin_slot->ptr), atoi(end_slot->ptr)); - struct timeval timeout = {1, 500000}; // 1.5s - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, (const char *)mobj->host, mobj->port); - options.connect_timeout = &timeout; - mobj->source_cc = redisConnectWithOptions(&options); - if (mobj->source_cc == NULL || mobj->source_cc->err) { - RedisModule_Log(ctx, WARNING, "Could not connect to Redis at ip:%s,port:%d, error:%s", - mobj->host, mobj->port, mobj->source_cc->errstr); - freeMigrateObj(mobj); - return RedisModule_ReplyWithError(ctx, "Can't connect source redis"); - } - mobj->repl_stat = REPL_STATE_CONNECTING; - RedisModule_EventLoopAdd(mobj->source_cc->fd, AE_WRITABLE, syncDataWithRedis, ctx); - return RedisModule_ReplyWithSimpleString(ctx, "OK"); -} - -int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - REDISMODULE_NOT_USED(argv); - REDISMODULE_NOT_USED(argc); - - int flag = RedisModule_Init(ctx, MODULE_NAME, REDIS_MIGRATE_VERSION, REDISMODULE_APIVER_1); - if (flag == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - RedisModule_Log(ctx, NOTICE, "begin init commands of %s", MODULE_NAME); - flag = RedisModule_CreateCommand(ctx, "rm.migrate", rm_migrateCommand, "write deny-oom admin getkeys-api", 0, 0, 0); - if (flag == REDISMODULE_ERR) { - RedisModule_Log(ctx, WARNING, "init rm.migrate failed"); - return REDISMODULE_ERR; - } - RedisModuleCallReply *reply = RedisModule_Call(ctx, "config", "cc", "get", "logfile"); - long long items = RedisModule_CallReplyLength(reply); - if (items != 2) { - RedisModule_Log(ctx, WARNING, "logfile is empty"); - return REDISMODULE_ERR; - } - RedisModuleCallReply *item1 = RedisModule_CallReplyArrayElement(reply, 1); - robj *logfile = (robj *)RedisModule_CreateStringFromCallReply(item1); - RedisModule_Log(ctx, NOTICE, "logfile is %s", (char *)logfile->ptr); - createLogObj((char *)logfile->ptr); - RedisModule_Log(ctx, NOTICE, "init %s success", MODULE_NAME); - return REDISMODULE_OK; -} diff --git a/src/redis-migrate.cpp b/src/redis-migrate.cpp new file mode 100644 index 0000000..7a8209d --- /dev/null +++ b/src/redis-migrate.cpp @@ -0,0 +1,161 @@ + +#include +#include +#include +#include +#include +#include +#include "redis-migrate.h" +#include "hiredis/hiredis.h" +#include "redismodule.h" +#include "log.h" + +static logObj mLog = {}; + +static RedisModuleCommandFilter *filter; + +static bool isMigrating = false; + +migrateObj createMigrateObject(RedisModuleString *host, int port, int slot, RedisModuleString *key, migrateObj m) { + size_t hostLen, keyLen; + const char *hostStr = RedisModule_StringPtrLen(host, &hostLen); + const char *keyStr = RedisModule_StringPtrLen(key, &keyLen); + m.port = port; + m.slot = slot; + m.timeout = 10000; + m.isCache = false; + m.host = hostStr; + m.hostLen = hostLen; + m.key = keyStr; + m.keyLen = keyLen; + return m; +} + +migrateObj findMigrating(std::string key) { + std::map::iterator p; + p = migrating.find(key); + if (p != migrating.end()) { + return p->second; + } + return {}; +} + +static void initLogObj(RedisModuleCtx *ctx) { + if (mLog.logfile != nullptr) { + return; + } + RedisModuleCallReply *reply = RedisModule_Call(ctx, "config", "cc", "get", "logfile"); + long long items = RedisModule_CallReplyLength(reply); + if (items != 2) { + RedisModule_Log(ctx, WARNING, "logfile is empty"); + return; + } + RedisModuleCallReply *item1 = RedisModule_CallReplyArrayElement(reply, 1); + RedisModuleString *logfileStr = RedisModule_CreateStringFromCallReply(item1); + size_t logfileLen; + const char *logfile = RedisModule_StringPtrLen(logfileStr, &logfileLen); + RedisModule_Log(ctx, NOTICE, "init logfile success logfile is %s", logfile); + mLog.logfile = logfile; + mLog.loglevel = LL_NOTICE; +} + +int rm_migrateCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 5) { + return RedisModule_ReplyWithError(ctx, "args number failed"); + } + if (RedisModule_IsKeysPositionRequest(ctx)) { + RedisModule_Log(ctx, VERBOSE, "get keys from module"); + return RedisModule_ReplyWithSimpleString(ctx, "OK"); + } + + RedisModuleString *host = argv[1]; + double portDouble, slotDouble; + if (RedisModule_StringToDouble(argv[2], &portDouble) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx, "ERR invalid port"); + } + if (RedisModule_StringToDouble(argv[3], &slotDouble) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx, "ERR invalid port"); + } + + RedisModuleString *key = argv[4]; + if (migrating.size() >= MAX_MIGRATEING_NUM) { + return RedisModule_ReplyWithError(ctx, "migrating key is bigger 50"); + } + initLogObj(ctx); + size_t keyLen; + const char *keyChar = RedisModule_StringPtrLen(key, &keyLen); + std::string keyStr(keyChar, keyLen); + migrateObj m = findMigrating(keyStr); + if (m.hostLen > 0) { + return RedisModule_ReplyWithError(ctx, "migrating, try again later"); + } + + m = createMigrateObject(host, (int)portDouble, (int)slotDouble, key, m); + migrating[keyStr] = m; + isMigrating = true; + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +void rm_migrateFilter(RedisModuleCommandFilterCtx *filter) { + if (!isMigrating) { + return; + } + + int pos = 0; + bool isMigratingKey = false; + + while (pos < RedisModule_CommandFilterArgsCount(filter)) { + const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos); + size_t arg_len; + const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len); + if (isMigratingKey) { + RedisModule_CommandFilterArgDelete(filter, pos); + continue; + } + + if (pos == 1) { + std::string keyStr(arg_str, arg_len); + migrateObj m = findMigrating(keyStr); + if (m.hostLen > 0) { + isMigratingKey = true; + migrateLog(mLog, LL_NOTICE, "key:%s is migrating, do not allow change", arg_str); + RedisModule_CommandFilterArgDelete(filter, pos); + continue; + } + } + + pos++; + } +} + +#ifdef __cplusplus +extern "C" { +#endif + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + int flag = RedisModule_Init(ctx, MODULE_NAME, REDIS_MIGRATE_VERSION, REDISMODULE_APIVER_1); + if (flag == REDISMODULE_ERR) { + return REDISMODULE_ERR; + } + + RedisModule_Log(ctx, NOTICE, "begin init commands of %s", "rm.migrate"); + flag = RedisModule_CreateCommand(ctx, "rm.migrate", rm_migrateCommand, "write deny-oom admin getkeys-api", 0, 0, 0); + if (flag == REDISMODULE_ERR) { + RedisModule_Log(ctx, WARNING, "init rm.migrate failed"); + return REDISMODULE_ERR; + } + filter = RedisModule_RegisterCommandFilter(ctx, rm_migrateFilter, 0); + if (filter == NULL) { + RedisModule_Log(ctx, WARNING, "init filter failed"); + return REDISMODULE_ERR; + } + + return REDISMODULE_OK; +} +#ifdef __cplusplus +} +#endif diff --git a/src/redis-migrate.h b/src/redis-migrate.h index 0bed185..552e848 100644 --- a/src/redis-migrate.h +++ b/src/redis-migrate.h @@ -2,10 +2,10 @@ #ifndef REDIS_MIGRATE_REDIS_MIGRATE_H #define REDIS_MIGRATE_REDIS_MIGRATE_H +#include +#include #include "redismodule.h" -#include "ae.h" -#include "sds.h" -#include "sdscompat.h" +#include "hiredis/sds.h" #include "log.h" #define MODULE_NAME "redis-migrate" @@ -23,87 +23,38 @@ #define C_OK 1 #define PROTO_IOBUF_LEN (1024 * 16) +#define MAX_MIGRATEING_NUM 50 + /* Anti-warning macro... */ #define UNUSED(V) ((void)V) -typedef struct redisObject -{ - unsigned type : 4; - unsigned encoding : 4; - unsigned lru : LRU_BITS; - int refcount; - void *ptr; -} robj; - -typedef struct migrateObject -{ +typedef struct migrateObject { char *address; - int repl_stat; - redisContext *source_cc; - char *host; - int port; - int begin_slot; - int end_slot; - char *psync_replid; - char master_replid[CONFIG_RUN_ID_SIZE + 1]; + int migrating; + const char *host; + size_t hostLen = 0; + size_t port = 0; + size_t slot = 0; + const char *key; + size_t keyLen = 0; int timeout; int isCache; - char psync_offset[32]; - int repl_transfer_size; - long long master_initial_offset; - time_t repl_transfer_lastio; } migrateObj; -typedef enum -{ - REPL_STATE_NONE = 0, /* No active replication */ - REPL_STATE_CONNECT, /* Must connect to master */ - REPL_STATE_CONNECTING, /* Connecting to master */ - /* --- Handshake states, must be ordered --- */ - REPL_STATE_RECEIVE_PING_REPLY, /* Wait for PING reply */ - REPL_STATE_SEND_HANDSHAKE, /* Send handshake sequence to master */ - REPL_STATE_RECEIVE_AUTH_REPLY, /* Wait for AUTH reply */ - REPL_STATE_RECEIVE_PORT_REPLY, /* Wait for REPLCONF reply */ - REPL_STATE_RECEIVE_IP_REPLY, /* Wait for REPLCONF reply */ - REPL_STATE_RECEIVE_CAPA_REPLY, /* Wait for REPLCONF reply */ - REPL_STATE_SEND_PSYNC, /* Send PSYNC */ - REPL_STATE_RECEIVE_PSYNC_REPLY, /* Wait for PSYNC reply */ - REPL_STATE_FULL_SYNC, - REPL_STATE_READING_FULL_DATA, - REPL_STATE_CONTINUE_SYNC, - /* --- End of handshake states --- */ - REPL_STATE_TRANSFER, /* Receiving .rdb from master */ - REPL_STATE_CONNECTED, /* Connected to master */ -} repl_state; +static std::map migrating; -long long ustime(void); -mstime_t mstime(void); - -migrateObj *createMigrateObject(robj *host, int port, int begin_slot, int end_slot); - -void freeMigrateObj(migrateObj *m); - -int sendSyncCommand(); - -int receiveDataFromRedis(); - -ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout); - -ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout); - -ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout); - -sds redisReceive(); - -void readFullData(); - -void cancelMigrate(); - -void syncDataWithRedis(int fd, void *user_data, int mask); +migrateObj createMigrateObject(RedisModuleString *host, int port, int slot, RedisModuleString *key, migrateObj m); int rm_migrateCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); -int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +void rm_migrateFilter(RedisModuleCommandFilterCtx *filter); +#ifdef __cplusplus +extern "C" { +#endif +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +#ifdef __cplusplus +} +#endif #endif // REDIS_MIGRATE_REDIS_MIGRATE_H diff --git a/src/redismodule.h b/src/redismodule.h index 98edf24..36e8bf5 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -162,11 +162,13 @@ typedef struct RedisModuleStreamID { #define REDISMODULE_CTX_FLAGS_RESP3 (1<<22) /* Redis is currently async loading database for diskless replication. */ #define REDISMODULE_CTX_FLAGS_ASYNC_LOADING (1<<23) +/* Redis is starting. */ +#define REDISMODULE_CTX_FLAGS_SERVER_STARTUP (1<<24) /* Next context flag, must be updated when adding new flags above! This flag should not be used directly by the module. * Use RedisModule_GetContextFlagsAll instead. */ -#define _REDISMODULE_CTX_FLAGS_NEXT (1<<24) +#define _REDISMODULE_CTX_FLAGS_NEXT (1<<25) /* Keyspace changes notification classes. Every class is associated with a * character for configuration purposes. @@ -185,11 +187,12 @@ This flag should not be used directly by the module. #define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */ #define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */ #define REDISMODULE_NOTIFY_MODULE (1<<13) /* d, module key space notification */ +#define REDISMODULE_NOTIFY_NEW (1<<14) /* n, new key notification */ /* Next notification flag, must be updated when adding new flags above! This flag should not be used directly by the module. * Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */ -#define _REDISMODULE_NOTIFY_NEXT (1<<14) +#define _REDISMODULE_NOTIFY_NEXT (1<<15) #define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */ @@ -220,7 +223,7 @@ This flag should not be used directly by the module. /* Logging level strings */ #define REDISMODULE_LOGLEVEL_DEBUG "debug" -#define REDISMODULE_LOGLEVEL_VERBOSE "VERBOSE" +#define REDISMODULE_LOGLEVEL_VERBOSE "verbose" #define REDISMODULE_LOGLEVEL_NOTICE "notice" #define REDISMODULE_LOGLEVEL_WARNING "warning" @@ -248,7 +251,7 @@ typedef uint64_t RedisModuleTimerID; /* When set, Redis will not call RedisModule_SignalModifiedKey(), implicitly in * RedisModule_CloseKey, and the module needs to do that when manually when keys - * are modified from the user's sperspective, to invalidate WATCH. */ + * are modified from the user's perspective, to invalidate WATCH. */ #define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1) /* Declare that the module can handle diskless async replication with RedisModule_SetModuleOptions. */ @@ -325,6 +328,7 @@ typedef struct RedisModuleCommandArg { int flags; /* The REDISMODULE_CMD_ARG_* macros. */ const char *deprecated_since; struct RedisModuleCommandArg *subargs; + const char *display_text; } RedisModuleCommandArg; typedef struct { @@ -346,7 +350,7 @@ typedef struct { const char *keyword; /* An index in argv from which to start searching. * Can be negative, which means start search from the end, in reverse - * (Example: -2 means to start in reverse from the panultimate arg) */ + * (Example: -2 means to start in reverse from the penultimate arg) */ int startfrom; } keyword; } bs; @@ -655,6 +659,8 @@ typedef struct RedisModuleClientInfo { #define RedisModuleClientInfo RedisModuleClientInfoV1 +#define REDISMODULE_CLIENTINFO_INITIALIZER_V1 { .version = 1 } + #define REDISMODULE_REPLICATIONINFO_VERSION 1 typedef struct RedisModuleReplicationInfo { uint64_t version; /* Not used since this structure is never passed @@ -744,11 +750,40 @@ typedef enum { REDISMODULE_ACL_LOG_CHANNEL /* Channel authorization failure */ } RedisModuleACLLogEntryReason; +/* Incomplete structures needed by both the core and modules. */ +typedef struct RedisModuleString RedisModuleString; +typedef struct RedisModuleIO RedisModuleIO; +typedef struct RedisModuleDigest RedisModuleDigest; +typedef struct RedisModuleInfoCtx RedisModuleInfoCtx; +typedef struct RedisModuleDefragCtx RedisModuleDefragCtx; + +/* Function pointers needed by both the core and modules, these needs to be + * exposed since you can't cast a function pointer to (void *). */ +typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report); +typedef void (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx); +typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata); + /* ------------------------- End of common defines ------------------------ */ -#ifndef REDISMODULE_CORE +#if defined REDISMODULE_CORE +/* Things only defined for the modules core (server), not exported to modules + * that include this file. */ + +#define RedisModuleString robj + +#endif /* defined REDISMODULE_CORE */ + +#if !defined REDISMODULE_CORE && !defined REDISMODULE_CORE_MODULE +/* Things defined for modules, but not for core-modules. */ typedef long long mstime_t; +typedef long long ustime_t; + +#endif /* !defined REDISMODULE_CORE && !defined REDISMODULE_CORE_MODULE */ + +/* ----------- The rest of the defines are only for modules ----------------- */ +#if !defined REDISMODULE_CORE || defined REDISMODULE_CORE_MODULE +/* Things defined for modules and core-modules. */ /* Macro definitions specific to individual compilers */ #ifndef REDISMODULE_ATTR_UNUSED @@ -779,21 +814,16 @@ typedef long long mstime_t; typedef struct RedisModuleCtx RedisModuleCtx; typedef struct RedisModuleCommand RedisModuleCommand; typedef struct RedisModuleKey RedisModuleKey; -typedef struct RedisModuleString RedisModuleString; typedef struct RedisModuleCallReply RedisModuleCallReply; -typedef struct RedisModuleIO RedisModuleIO; typedef struct RedisModuleType RedisModuleType; -typedef struct RedisModuleDigest RedisModuleDigest; typedef struct RedisModuleBlockedClient RedisModuleBlockedClient; typedef struct RedisModuleClusterInfo RedisModuleClusterInfo; typedef struct RedisModuleDict RedisModuleDict; typedef struct RedisModuleDictIter RedisModuleDictIter; typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx; typedef struct RedisModuleCommandFilter RedisModuleCommandFilter; -typedef struct RedisModuleInfoCtx RedisModuleInfoCtx; typedef struct RedisModuleServerInfoData RedisModuleServerInfoData; typedef struct RedisModuleScanCursor RedisModuleScanCursor; -typedef struct RedisModuleDefragCtx RedisModuleDefragCtx; typedef struct RedisModuleUser RedisModuleUser; typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx; @@ -820,11 +850,8 @@ typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const cha typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter); typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data); -typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report); typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata); typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata); -typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata); -typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx); typedef RedisModuleString * (*RedisModuleConfigGetStringFunc)(const char *name, void *privdata); typedef long long (*RedisModuleConfigGetNumericFunc)(const char *name, void *privdata); typedef int (*RedisModuleConfigGetBoolFunc)(const char *name, void *privdata); @@ -914,6 +941,7 @@ REDISMODULE_API size_t (*RedisModule_CallReplyLength)(RedisModuleCallReply *repl REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromULongLong)(RedisModuleCtx *ctx, unsigned long long ull) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str) REDISMODULE_ATTR; @@ -947,6 +975,7 @@ REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d REDISMODULE_API int (*RedisModule_ReplyWithBigNumber)(RedisModuleCtx *ctx, const char *bignum, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringToULongLong)(const RedisModuleString *str, unsigned long long *ull) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringToDouble)(const RedisModuleString *str, double *d) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_StringToStreamID)(const RedisModuleString *str, RedisModuleStreamID *id) REDISMODULE_ATTR; @@ -999,6 +1028,8 @@ REDISMODULE_API void (*RedisModule_ChannelAtPosWithFlags)(RedisModuleCtx *ctx, i REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_GetClientNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetClientNameById)(uint64_t id, RedisModuleString *name) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_PublishMessageShard)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR; @@ -1038,7 +1069,7 @@ REDISMODULE_API int (*RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, Redis REDISMODULE_API void (*RedisModule_TrimStringAllocation)(RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_HoldString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringCompare)(const RedisModuleString *a, const RedisModuleString *b) REDISMODULE_ATTR; REDISMODULE_API RedisModuleCtx * (*RedisModule_GetContextFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key) REDISMODULE_ATTR; @@ -1048,8 +1079,10 @@ REDISMODULE_API int (*RedisModule_GetDbIdFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_API int (*RedisModule_GetToDbIdFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API const RedisModuleString * (*RedisModule_GetToKeyNameFromOptCtx)(RedisModuleKeyOptCtx *ctx) REDISMODULE_ATTR; -REDISMODULE_API long long (*RedisModule_Milliseconds)(void) REDISMODULE_ATTR; +REDISMODULE_API mstime_t (*RedisModule_Milliseconds)(void) REDISMODULE_ATTR; REDISMODULE_API uint64_t (*RedisModule_MonotonicMicroseconds)(void) REDISMODULE_ATTR; +REDISMODULE_API ustime_t (*RedisModule_Microseconds)(void) REDISMODULE_ATTR; +REDISMODULE_API ustime_t (*RedisModule_CachedMicroseconds)(void) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, const char *ele, size_t len) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_DigestEndSequence)(RedisModuleDigest *md) REDISMODULE_ATTR; @@ -1160,6 +1193,7 @@ REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR; REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR; REDISMODULE_API size_t (*RedisModule_MallocSize)(void* ptr) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_MallocUsableSize)(void *ptr) REDISMODULE_ATTR; REDISMODULE_API size_t (*RedisModule_MallocSizeString)(RedisModuleString* str) REDISMODULE_ATTR; REDISMODULE_API size_t (*RedisModule_MallocSizeDict)(RedisModuleDict* dict) REDISMODULE_ATTR; REDISMODULE_API RedisModuleUser * (*RedisModule_CreateModuleUser)(const char *name) REDISMODULE_ATTR; @@ -1190,7 +1224,7 @@ REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDefragCtx) REDISMODULE_API int (*RedisModule_EventLoopAdd)(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopDel)(int fd, int mask) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopAddOneShot)(RedisModuleEventLoopOneShotFunc func, void *user_data) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_RegisterBoolConfig)(RedisModuleCtx *ctx, char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterBoolConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_RegisterNumericConfig)(RedisModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, RedisModuleConfigGetNumericFunc getfn, RedisModuleConfigSetNumericFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_RegisterStringConfig)(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_RegisterEnumConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR; @@ -1256,6 +1290,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ListInsert); REDISMODULE_GET_API(ListDelete); REDISMODULE_GET_API(StringToLongLong); + REDISMODULE_GET_API(StringToULongLong); REDISMODULE_GET_API(StringToDouble); REDISMODULE_GET_API(StringToLongDouble); REDISMODULE_GET_API(StringToStreamID); @@ -1278,6 +1313,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(CreateStringFromCallReply); REDISMODULE_GET_API(CreateString); REDISMODULE_GET_API(CreateStringFromLongLong); + REDISMODULE_GET_API(CreateStringFromULongLong); REDISMODULE_GET_API(CreateStringFromDouble); REDISMODULE_GET_API(CreateStringFromLongDouble); REDISMODULE_GET_API(CreateStringFromString); @@ -1380,6 +1416,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(GetToDbIdFromOptCtx); REDISMODULE_GET_API(Milliseconds); REDISMODULE_GET_API(MonotonicMicroseconds); + REDISMODULE_GET_API(Microseconds); + REDISMODULE_GET_API(CachedMicroseconds); REDISMODULE_GET_API(DigestAddStringBuffer); REDISMODULE_GET_API(DigestAddLongLong); REDISMODULE_GET_API(DigestEndSequence); @@ -1424,6 +1462,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ServerInfoGetFieldUnsigned); REDISMODULE_GET_API(ServerInfoGetFieldDouble); REDISMODULE_GET_API(GetClientInfoById); + REDISMODULE_GET_API(GetClientNameById); + REDISMODULE_GET_API(SetClientNameById); REDISMODULE_GET_API(PublishMessage); REDISMODULE_GET_API(PublishMessageShard); REDISMODULE_GET_API(SubscribeToServerEvent); @@ -1493,6 +1533,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(KillForkChild); REDISMODULE_GET_API(GetUsedMemoryRatio); REDISMODULE_GET_API(MallocSize); + REDISMODULE_GET_API(MallocUsableSize); REDISMODULE_GET_API(MallocSizeString); REDISMODULE_GET_API(MallocSizeDict); REDISMODULE_GET_API(CreateModuleUser); @@ -1538,11 +1579,5 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int #define RMAPI_FUNC_SUPPORTED(func) (func != NULL) -#else - -/* Things only defined for the modules core, not exported to modules - * including this file. */ -#define RedisModuleString robj - #endif /* REDISMODULE_CORE */ #endif /* REDISMODULE_H */ diff --git a/src/sds.c b/src/sds.c deleted file mode 100644 index 114fa49..0000000 --- a/src/sds.c +++ /dev/null @@ -1,1289 +0,0 @@ -/* SDSLib 2.0 -- A C dynamic strings library - * - * Copyright (c) 2006-2015, Salvatore Sanfilippo - * 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. - */ - -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include "sds.h" -#include "sdsalloc.h" - -static inline int hi_sdsHdrSize(char type) { - switch(type&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return sizeof(struct hisdshdr5); - case HI_SDS_TYPE_8: - return sizeof(struct hisdshdr8); - case HI_SDS_TYPE_16: - return sizeof(struct hisdshdr16); - case HI_SDS_TYPE_32: - return sizeof(struct hisdshdr32); - case HI_SDS_TYPE_64: - return sizeof(struct hisdshdr64); - } - return 0; -} - -static inline char hi_sdsReqType(size_t string_size) { - if (string_size < 32) - return HI_SDS_TYPE_5; - if (string_size < 0xff) - return HI_SDS_TYPE_8; - if (string_size < 0xffff) - return HI_SDS_TYPE_16; - if (string_size < 0xffffffff) - return HI_SDS_TYPE_32; - return HI_SDS_TYPE_64; -} - -/* Create a new hisds string with the content specified by the 'init' pointer - * and 'initlen'. - * If NULL is used for 'init' the string is initialized with zero bytes. - * - * The string is always null-terminated (all the hisds strings are, always) so - * even if you create an hisds string with: - * - * mystring = hi_sdsnewlen("abc",3); - * - * 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 - * \0 characters in the middle, as the length is stored in the hisds header. */ -hisds hi_sdsnewlen(const void *init, size_t initlen) { - void *sh; - hisds s; - char type = hi_sdsReqType(initlen); - /* Empty strings are usually created in order to append. Use type 8 - * since type 5 is not good at this. */ - if (type == HI_SDS_TYPE_5 && initlen == 0) type = HI_SDS_TYPE_8; - int hdrlen = hi_sdsHdrSize(type); - unsigned char *fp; /* flags pointer. */ - - sh = hi_s_malloc(hdrlen+initlen+1); - 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 HI_SDS_TYPE_5: { - *fp = type | (initlen << HI_SDS_TYPE_BITS); - break; - } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - } - if (initlen && init) - memcpy(s, init, initlen); - s[initlen] = '\0'; - return s; -} - -/* Create an empty (zero length) hisds string. Even in this case the string - * always has an implicit null term. */ -hisds hi_sdsempty(void) { - return hi_sdsnewlen("",0); -} - -/* Create a new hisds string starting from a null terminated C string. */ -hisds hi_sdsnew(const char *init) { - size_t initlen = (init == NULL) ? 0 : strlen(init); - return hi_sdsnewlen(init, initlen); -} - -/* Duplicate an hisds string. */ -hisds hi_sdsdup(const hisds s) { - return hi_sdsnewlen(s, hi_sdslen(s)); -} - -/* Free an hisds string. No operation is performed if 's' is NULL. */ -void hi_sdsfree(hisds s) { - if (s == NULL) return; - hi_s_free((char*)s-hi_sdsHdrSize(s[-1])); -} - -/* Set the hisds string length to the length as obtained with strlen(), so - * considering as content only up to the first null term character. - * - * This function is useful when the hisds string is hacked manually in some - * way, like in the following example: - * - * s = hi_sdsnew("foobar"); - * s[2] = '\0'; - * hi_sdsupdatelen(s); - * printf("%d\n", hi_sdslen(s)); - * - * The output will be "2", but if we comment out the call to hi_sdsupdatelen() - * the output will be "6" as the string was modified but the logical length - * remains 6 bytes. */ -void hi_sdsupdatelen(hisds s) { - int reallen = strlen(s); - hi_sdssetlen(s, reallen); -} - -/* Modify an hisds string in-place to make it empty (zero length). - * 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 - * number of bytes previously available. */ -void hi_sdsclear(hisds s) { - hi_sdssetlen(s, 0); - s[0] = '\0'; -} - -/* Enlarge the free space at the end of the hisds string so that the caller - * is sure that after calling this function can overwrite up to addlen - * bytes after the end of the string, plus one more byte for nul term. - * - * Note: this does not change the *length* of the hisds string as returned - * by hi_sdslen(), but only the free buffer space we have. */ -hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { - void *sh, *newsh; - size_t avail = hi_sdsavail(s); - size_t len, newlen; - char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; - int hdrlen; - - /* Return ASAP if there is enough space left. */ - if (avail >= addlen) return s; - - len = hi_sdslen(s); - sh = (char*)s-hi_sdsHdrSize(oldtype); - newlen = (len+addlen); - if (newlen < HI_SDS_MAX_PREALLOC) - newlen *= 2; - else - newlen += HI_SDS_MAX_PREALLOC; - - type = hi_sdsReqType(newlen); - - /* Don't use type 5: the user is appending to the string and type 5 is - * not able to remember empty space, so hi_sdsMakeRoomFor() must be called - * at every appending operation. */ - if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8; - - hdrlen = hi_sdsHdrSize(type); - if (oldtype==type) { - newsh = hi_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 = hi_s_malloc(hdrlen+newlen+1); - if (newsh == NULL) return NULL; - memcpy((char*)newsh+hdrlen, s, len+1); - hi_s_free(sh); - s = (char*)newsh+hdrlen; - s[-1] = type; - hi_sdssetlen(s, len); - } - hi_sdssetalloc(s, newlen); - return s; -} - -/* Reallocate the hisds string so that it has no free space at the end. The - * contained string remains not altered, but next concatenation operations - * will require a reallocation. - * - * After the call, the passed hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdsRemoveFreeSpace(hisds s) { - void *sh, *newsh; - char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; - int hdrlen; - size_t len = hi_sdslen(s); - sh = (char*)s-hi_sdsHdrSize(oldtype); - - type = hi_sdsReqType(len); - hdrlen = hi_sdsHdrSize(type); - if (oldtype==type) { - newsh = hi_s_realloc(sh, hdrlen+len+1); - if (newsh == NULL) return NULL; - s = (char*)newsh+hdrlen; - } else { - newsh = hi_s_malloc(hdrlen+len+1); - if (newsh == NULL) return NULL; - memcpy((char*)newsh+hdrlen, s, len+1); - hi_s_free(sh); - s = (char*)newsh+hdrlen; - s[-1] = type; - hi_sdssetlen(s, len); - } - hi_sdssetalloc(s, len); - return s; -} - -/* Return the total size of the allocation of the specifed hisds string, - * including: - * 1) The hisds header before the pointer. - * 2) The string. - * 3) The free buffer at the end if any. - * 4) The implicit null term. - */ -size_t hi_sdsAllocSize(hisds s) { - size_t alloc = hi_sdsalloc(s); - return hi_sdsHdrSize(s[-1])+alloc+1; -} - -/* Return the pointer of the actual SDS allocation (normally SDS strings - * are referenced by the start of the string buffer). */ -void *hi_sdsAllocPtr(hisds s) { - return (void*) (s-hi_sdsHdrSize(s[-1])); -} - -/* Increment the hisds length and decrements the left free space at the - * end of the string according to 'incr'. Also set the null term - * in the new end of the string. - * - * This function is used in order to fix the string length after the - * user calls hi_sdsMakeRoomFor(), writes something after the end of - * the current string, and finally needs to set the new length. - * - * Note: it is possible to use a negative increment in order to - * right-trim the string. - * - * Usage example: - * - * Using hi_sdsIncrLen() and hi_sdsMakeRoomFor() it is possible to mount the - * following schema, to cat bytes coming from the kernel to the end of an - * hisds string without copying into an intermediate buffer: - * - * oldlen = hi_hi_sdslen(s); - * s = hi_sdsMakeRoomFor(s, BUFFER_SIZE); - * nread = read(fd, s+oldlen, BUFFER_SIZE); - * ... check for nread <= 0 and handle it ... - * hi_sdsIncrLen(s, nread); - */ -void hi_sdsIncrLen(hisds s, int incr) { - unsigned char flags = s[-1]; - size_t len; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: { - unsigned char *fp = ((unsigned char*)s)-1; - unsigned char oldlen = HI_SDS_TYPE_5_LEN(flags); - assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); - *fp = HI_SDS_TYPE_5 | ((oldlen+incr) << HI_SDS_TYPE_BITS); - len = oldlen+incr; - break; - } - case HI_SDS_TYPE_8: { - HI_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 HI_SDS_TYPE_16: { - HI_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 HI_SDS_TYPE_32: { - HI_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 HI_SDS_TYPE_64: { - HI_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 hisds to have the specified length. Bytes that were not part of - * the original length of the hisds will be set to zero. - * - * if the specified length is smaller than the current length, no operation - * is performed. */ -hisds hi_sdsgrowzero(hisds s, size_t len) { - size_t curlen = hi_sdslen(s); - - if (len <= curlen) return s; - s = hi_sdsMakeRoomFor(s,len-curlen); - if (s == NULL) return NULL; - - /* Make sure added region doesn't contain garbage */ - memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ - hi_sdssetlen(s, len); - return s; -} - -/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the - * end of the specified hisds string 's'. - * - * After the call, the passed hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscatlen(hisds s, const void *t, size_t len) { - size_t curlen = hi_sdslen(s); - - s = hi_sdsMakeRoomFor(s,len); - if (s == NULL) return NULL; - memcpy(s+curlen, t, len); - hi_sdssetlen(s, curlen+len); - s[curlen+len] = '\0'; - return s; -} - -/* Append the specified null termianted C string to the hisds string 's'. - * - * After the call, the passed hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscat(hisds s, const char *t) { - return hi_sdscatlen(s, t, strlen(t)); -} - -/* Append the specified hisds 't' to the existing hisds 's'. - * - * After the call, the modified hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscatsds(hisds s, const hisds t) { - return hi_sdscatlen(s, t, hi_sdslen(t)); -} - -/* Destructively modify the hisds string 's' to hold the specified binary - * safe string pointed by 't' of length 'len' bytes. */ -hisds hi_sdscpylen(hisds s, const char *t, size_t len) { - if (hi_sdsalloc(s) < len) { - s = hi_sdsMakeRoomFor(s,len-hi_sdslen(s)); - if (s == NULL) return NULL; - } - memcpy(s, t, len); - s[len] = '\0'; - hi_sdssetlen(s, len); - return s; -} - -/* Like hi_sdscpylen() but 't' must be a null-terminated string so that the length - * of the string is obtained with strlen(). */ -hisds hi_sdscpy(hisds s, const char *t) { - return hi_sdscpylen(s, t, strlen(t)); -} - -/* Helper for hi_sdscatlonglong() doing the actual number -> string - * conversion. 's' must point to a string with room for at least - * HI_SDS_LLSTR_SIZE bytes. - * - * The function returns the length of the null-terminated string - * representation stored at 's'. */ -#define HI_SDS_LLSTR_SIZE 21 -int hi_sdsll2str(char *s, long long value) { - char *p, aux; - unsigned long long v; - size_t l; - - /* Generate the string representation, this method produces - * an reversed string. */ - v = (value < 0) ? -value : value; - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - if (value < 0) *p++ = '-'; - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; - } - return l; -} - -/* Identical hi_sdsll2str(), but for unsigned long long type. */ -int hi_sdsull2str(char *s, unsigned long long v) { - char *p, aux; - size_t l; - - /* Generate the string representation, this method produces - * an reversed string. */ - p = s; - do { - *p++ = '0'+(v%10); - v /= 10; - } while(v); - - /* Compute length and add null term. */ - l = p-s; - *p = '\0'; - - /* Reverse the string. */ - p--; - while(s < p) { - aux = *s; - *s = *p; - *p = aux; - s++; - p--; - } - return l; -} - -/* Create an hisds string from a long long value. It is much faster than: - * - * hi_sdscatprintf(hi_sdsempty(),"%lld\n", value); - */ -hisds hi_sdsfromlonglong(long long value) { - char buf[HI_SDS_LLSTR_SIZE]; - int len = hi_sdsll2str(buf,value); - - return hi_sdsnewlen(buf,len); -} - -/* Like hi_sdscatprintf() but gets va_list instead of being variadic. */ -hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { - va_list cpy; - char staticbuf[1024], *buf = staticbuf, *t; - size_t buflen = strlen(fmt)*2; - - /* We try to start using a static buffer for speed. - * If not possible we revert to heap allocation. */ - if (buflen > sizeof(staticbuf)) { - buf = hi_s_malloc(buflen); - if (buf == NULL) return NULL; - } else { - buflen = sizeof(staticbuf); - } - - /* Try with buffers two times bigger every time we fail to - * fit the string in the current buffer size. */ - while(1) { - buf[buflen-2] = '\0'; - va_copy(cpy,ap); - vsnprintf(buf, buflen, fmt, cpy); - va_end(cpy); - if (buf[buflen-2] != '\0') { - if (buf != staticbuf) hi_s_free(buf); - buflen *= 2; - buf = hi_s_malloc(buflen); - if (buf == NULL) return NULL; - continue; - } - break; - } - - /* Finally concat the obtained string to the SDS string and return it. */ - t = hi_sdscat(s, buf); - if (buf != staticbuf) hi_s_free(buf); - return t; -} - -/* Append to the hisds string 's' a string obtained using printf-alike format - * specifier. - * - * After the call, the modified hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. - * - * Example: - * - * s = hi_sdsnew("Sum is: "); - * s = hi_sdscatprintf(s,"%d+%d = %d",a,b,a+b). - * - * Often you need to create a string from scratch with the printf-alike - * format. When this is the need, just use hi_sdsempty() as the target string: - * - * s = hi_sdscatprintf(hi_sdsempty(), "... your format ...", args); - */ -hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { - va_list ap; - char *t; - va_start(ap, fmt); - t = hi_sdscatvprintf(s,fmt,ap); - va_end(ap); - return t; -} - -/* This function is similar to hi_sdscatprintf, but much faster as it does - * not rely on sprintf() family functions implemented by the libc that - * are often very slow. Moreover directly handling the hisds string as - * new data is concatenated provides a performance improvement. - * - * However this function only handles an incompatible subset of printf-alike - * format specifiers: - * - * %s - C String - * %S - SDS string - * %i - signed int - * %I - 64 bit signed integer (long long, int64_t) - * %u - unsigned int - * %U - 64 bit unsigned integer (unsigned long long, uint64_t) - * %% - Verbatim "%" character. - */ -hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { - const char *f = fmt; - int i; - va_list ap; - - va_start(ap,fmt); - i = hi_sdslen(s); /* Position of the next byte to write to dest str. */ - while(*f) { - char next, *str; - size_t l; - long long num; - unsigned long long unum; - - /* Make sure there is always space for at least 1 char. */ - if (hi_sdsavail(s)==0) { - s = hi_sdsMakeRoomFor(s,1); - if (s == NULL) goto fmt_error; - } - - switch(*f) { - case '%': - next = *(f+1); - f++; - switch(next) { - case 's': - case 'S': - str = va_arg(ap,char*); - l = (next == 's') ? strlen(str) : hi_sdslen(str); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); - if (s == NULL) goto fmt_error; - } - memcpy(s+i,str,l); - hi_sdsinclen(s,l); - i += l; - break; - case 'i': - case 'I': - if (next == 'i') - num = va_arg(ap,int); - else - num = va_arg(ap,long long); - { - char buf[HI_SDS_LLSTR_SIZE]; - l = hi_sdsll2str(buf,num); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); - if (s == NULL) goto fmt_error; - } - memcpy(s+i,buf,l); - hi_sdsinclen(s,l); - i += l; - } - break; - case 'u': - case 'U': - if (next == 'u') - unum = va_arg(ap,unsigned int); - else - unum = va_arg(ap,unsigned long long); - { - char buf[HI_SDS_LLSTR_SIZE]; - l = hi_sdsull2str(buf,unum); - if (hi_sdsavail(s) < l) { - s = hi_sdsMakeRoomFor(s,l); - if (s == NULL) goto fmt_error; - } - memcpy(s+i,buf,l); - hi_sdsinclen(s,l); - i += l; - } - break; - default: /* Handle %% and generally %. */ - s[i++] = next; - hi_sdsinclen(s,1); - break; - } - break; - default: - s[i++] = *f; - hi_sdsinclen(s,1); - break; - } - f++; - } - va_end(ap); - - /* Add null-term */ - s[i] = '\0'; - return s; - -fmt_error: - va_end(ap); - return NULL; -} - -/* Remove the part of the string from left and from right composed just of - * contiguous characters found in 'cset', that is a null terminted C string. - * - * After the call, the modified hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. - * - * Example: - * - * s = hi_sdsnew("AA...AA.a.aa.aHelloWorld :::"); - * s = hi_sdstrim(s,"Aa. :"); - * printf("%s\n", s); - * - * Output will be just "Hello World". - */ -hisds hi_sdstrim(hisds s, const char *cset) { - char *start, *end, *sp, *ep; - size_t len; - - sp = start = s; - ep = end = s+hi_sdslen(s)-1; - while(sp <= end && strchr(cset, *sp)) sp++; - while(ep > sp && strchr(cset, *ep)) ep--; - len = (sp > ep) ? 0 : ((ep-sp)+1); - if (s != sp) memmove(s, sp, len); - s[len] = '\0'; - hi_sdssetlen(s,len); - return s; -} - -/* Turn the string into a smaller (or equal) string containing only the - * substring specified by the 'start' and 'end' indexes. - * - * start and end can be negative, where -1 means the last character of the - * string, -2 the penultimate character, and so forth. - * - * The interval is inclusive, so the start and end characters will be part - * of the resulting string. - * - * The string is modified in-place. - * - * Return value: - * -1 (error) if hi_sdslen(s) is larger than maximum positive ssize_t value. - * 0 on success. - * - * Example: - * - * s = hi_sdsnew("Hello World"); - * hi_sdsrange(s,1,-1); => "ello World" - */ -int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { - size_t newlen, len = hi_sdslen(s); - if (len > SSIZE_MAX) return -1; - - if (len == 0) return 0; - if (start < 0) { - start = len+start; - if (start < 0) start = 0; - } - if (end < 0) { - end = len+end; - if (end < 0) end = 0; - } - newlen = (start > end) ? 0 : (end-start)+1; - if (newlen != 0) { - if (start >= (ssize_t)len) { - newlen = 0; - } else if (end >= (ssize_t)len) { - end = len-1; - newlen = (start > end) ? 0 : (end-start)+1; - } - } else { - start = 0; - } - if (start && newlen) memmove(s, s+start, newlen); - s[newlen] = 0; - hi_sdssetlen(s,newlen); - return 0; -} - -/* Apply tolower() to every character of the hisds string 's'. */ -void hi_sdstolower(hisds s) { - int len = hi_sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = tolower(s[j]); -} - -/* Apply toupper() to every character of the hisds string 's'. */ -void hi_sdstoupper(hisds s) { - int len = hi_sdslen(s), j; - - for (j = 0; j < len; j++) s[j] = toupper(s[j]); -} - -/* Compare two hisds strings s1 and s2 with memcmp(). - * - * Return value: - * - * positive if s1 > s2. - * negative if s1 < s2. - * 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 - * additional characters, the longer string is considered to be greater than - * the smaller one. */ -int hi_sdscmp(const hisds s1, const hisds s2) { - size_t l1, l2, minlen; - int cmp; - - l1 = hi_sdslen(s1); - l2 = hi_sdslen(s2); - minlen = (l1 < l2) ? l1 : l2; - cmp = memcmp(s1,s2,minlen); - if (cmp == 0) return l1-l2; - return cmp; -} - -/* Split 's' with separator in 'sep'. An array - * of hisds strings is returned. *count will be set - * by reference to the number of tokens returned. - * - * On out of memory, zero length string, zero length - * separator, NULL is returned. - * - * Note that 'sep' is able to split a string using - * a multi-character separator. For example - * hi_sdssplit("foo_-_bar","_-_"); will return two - * elements "foo" and "bar". - * - * This version of the function is binary-safe but - * requires length arguments. hi_sdssplit() is just the - * same function but for zero-terminated strings. - */ -hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { - int elements = 0, slots = 5, start = 0, j; - hisds *tokens; - - if (seplen < 1 || len < 0) return NULL; - - tokens = hi_s_malloc(sizeof(hisds)*slots); - if (tokens == NULL) return NULL; - - if (len == 0) { - *count = 0; - return tokens; - } - for (j = 0; j < (len-(seplen-1)); j++) { - /* make sure there is room for the next element and the final one */ - if (slots < elements+2) { - hisds *newtokens; - - slots *= 2; - newtokens = hi_s_realloc(tokens,sizeof(hisds)*slots); - if (newtokens == NULL) goto cleanup; - tokens = newtokens; - } - /* search the separator */ - if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { - tokens[elements] = hi_sdsnewlen(s+start,j-start); - if (tokens[elements] == NULL) goto cleanup; - elements++; - start = j+seplen; - j = j+seplen-1; /* skip the separator */ - } - } - /* Add the final element. We are sure there is room in the tokens array. */ - tokens[elements] = hi_sdsnewlen(s+start,len-start); - if (tokens[elements] == NULL) goto cleanup; - elements++; - *count = elements; - return tokens; - -cleanup: - { - int i; - for (i = 0; i < elements; i++) hi_sdsfree(tokens[i]); - hi_s_free(tokens); - *count = 0; - return NULL; - } -} - -/* Free the result returned by hi_sdssplitlen(), or do nothing if 'tokens' is NULL. */ -void hi_sdsfreesplitres(hisds *tokens, int count) { - if (!tokens) return; - while(count--) - hi_sdsfree(tokens[count]); - hi_s_free(tokens); -} - -/* Append to the hisds string "s" an escaped string representation where - * all the non-printable characters (tested with isprint()) are turned into - * escapes in the form "\n\r\a...." or "\x". - * - * After the call, the modified hisds string is no longer valid and all the - * references must be substituted with the new pointer returned by the call. */ -hisds hi_sdscatrepr(hisds s, const char *p, size_t len) { - s = hi_sdscatlen(s,"\"",1); - while(len--) { - switch(*p) { - case '\\': - case '"': - s = hi_sdscatprintf(s,"\\%c",*p); - break; - case '\n': s = hi_sdscatlen(s,"\\n",2); break; - case '\r': s = hi_sdscatlen(s,"\\r",2); break; - case '\t': s = hi_sdscatlen(s,"\\t",2); break; - case '\a': s = hi_sdscatlen(s,"\\a",2); break; - case '\b': s = hi_sdscatlen(s,"\\b",2); break; - default: - if (isprint(*p)) - s = hi_sdscatprintf(s,"%c",*p); - else - s = hi_sdscatprintf(s,"\\x%02x",(unsigned char)*p); - break; - } - p++; - } - return hi_sdscatlen(s,"\"",1); -} - -/* Helper function for hi_sdssplitargs() that converts a hex digit into an - * integer from 0 to 15 */ -static int hi_hex_digit_to_int(char c) { - switch(c) { - case '0': return 0; - case '1': return 1; - case '2': return 2; - case '3': return 3; - case '4': return 4; - case '5': return 5; - case '6': return 6; - case '7': return 7; - case '8': return 8; - case '9': return 9; - case 'a': case 'A': return 10; - case 'b': case 'B': return 11; - case 'c': case 'C': return 12; - case 'd': case 'D': return 13; - case 'e': case 'E': return 14; - case 'f': case 'F': return 15; - default: return 0; - } -} - -/* Split a line into arguments, where every argument can be in the - * following programming-language REPL-alike form: - * - * foo bar "newline are supported\n" and "\xff\x00otherstuff" - * - * The number of arguments is stored into *argc, and an array - * of hisds is returned. - * - * The caller should free the resulting array of hisds strings with - * hi_sdsfreesplitres(). - * - * Note that hi_sdscatrepr() is able to convert back a string into - * a quoted string in the same format hi_sdssplitargs() is able to parse. - * - * The function returns the allocated tokens on success, even when the - * input string is empty, or NULL if the input contains unbalanced - * quotes or closed quotes followed by non space characters - * as in: "foo"bar or "foo' - */ -hisds *hi_sdssplitargs(const char *line, int *argc) { - const char *p = line; - char *current = NULL; - char **vector = NULL; - - *argc = 0; - while(1) { - /* skip blanks */ - while(*p && isspace(*p)) p++; - if (*p) { - /* get a token */ - int inq=0; /* set to 1 if we are in "quotes" */ - int insq=0; /* set to 1 if we are in 'single quotes' */ - int done=0; - - if (current == NULL) current = hi_sdsempty(); - while(!done) { - if (inq) { - if (*p == '\\' && *(p+1) == 'x' && - isxdigit(*(p+2)) && - isxdigit(*(p+3))) - { - unsigned char byte; - - byte = (hi_hex_digit_to_int(*(p+2))*16)+ - hi_hex_digit_to_int(*(p+3)); - current = hi_sdscatlen(current,(char*)&byte,1); - p += 3; - } else if (*p == '\\' && *(p+1)) { - char c; - - p++; - switch(*p) { - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - case 'b': c = '\b'; break; - case 'a': c = '\a'; break; - default: c = *p; break; - } - current = hi_sdscatlen(current,&c,1); - } else if (*p == '"') { - /* closing quote must be followed by a space or - * nothing at all. */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = hi_sdscatlen(current,p,1); - } - } else if (insq) { - if (*p == '\\' && *(p+1) == '\'') { - p++; - current = hi_sdscatlen(current,"'",1); - } else if (*p == '\'') { - /* closing quote must be followed by a space or - * nothing at all. */ - if (*(p+1) && !isspace(*(p+1))) goto err; - done=1; - } else if (!*p) { - /* unterminated quotes */ - goto err; - } else { - current = hi_sdscatlen(current,p,1); - } - } else { - switch(*p) { - case ' ': - case '\n': - case '\r': - case '\t': - case '\0': - done=1; - break; - case '"': - inq=1; - break; - case '\'': - insq=1; - break; - default: - current = hi_sdscatlen(current,p,1); - break; - } - } - if (*p) p++; - } - /* add the token to the vector */ - { - char **new_vector = hi_s_realloc(vector,((*argc)+1)*sizeof(char*)); - if (new_vector == NULL) { - hi_s_free(vector); - return NULL; - } - - vector = new_vector; - vector[*argc] = current; - (*argc)++; - current = NULL; - } - } else { - /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = hi_s_malloc(sizeof(void*)); - return vector; - } - } - -err: - while((*argc)--) - hi_sdsfree(vector[*argc]); - hi_s_free(vector); - if (current) hi_sdsfree(current); - *argc = 0; - return NULL; -} - -/* Modify the string substituting all the occurrences of the set of - * characters specified in the 'from' string to the corresponding character - * in the 'to' array. - * - * For instance: hi_sdsmapchars(mystring, "ho", "01", 2) - * will have the effect of turning the string "hello" into "0ell1". - * - * The function returns the hisds string pointer, that is always the same - * as the input pointer since no resize is needed. */ -hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen) { - size_t j, i, l = hi_sdslen(s); - - for (j = 0; j < l; j++) { - for (i = 0; i < setlen; i++) { - if (s[j] == from[i]) { - s[j] = to[i]; - break; - } - } - } - return s; -} - -/* Join an array of C strings using the specified separator (also a C string). - * Returns the result as an hisds string. */ -hisds hi_sdsjoin(char **argv, int argc, char *sep) { - hisds join = hi_sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = hi_sdscat(join, argv[j]); - if (j != argc-1) join = hi_sdscat(join,sep); - } - return join; -} - -/* Like hi_sdsjoin, but joins an array of SDS strings. */ -hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen) { - hisds join = hi_sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = hi_sdscatsds(join, argv[j]); - if (j != argc-1) join = hi_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 *hi_sds_malloc(size_t size) { return hi_s_malloc(size); } -void *hi_sds_realloc(void *ptr, size_t size) { return hi_s_realloc(ptr,size); } -void hi_sds_free(void *ptr) { hi_s_free(ptr); } - -#if defined(HI_SDS_TEST_MAIN) -#include -#include "testhelp.h" -#include "limits.h" - -#define UNUSED(x) (void)(x) -int hi_sdsTest(void) { - { - hisds x = hi_sdsnew("foo"), y; - - test_cond("Create a string and obtain the length", - hi_sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) - - hi_sdsfree(x); - x = hi_sdsnewlen("foo",2); - test_cond("Create a string with specified length", - hi_sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) - - x = hi_sdscat(x,"bar"); - test_cond("Strings concatenation", - hi_sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); - - x = hi_sdscpy(x,"a"); - test_cond("hi_sdscpy() against an originally longer string", - hi_sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) - - x = hi_sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); - test_cond("hi_sdscpy() against an originally shorter string", - hi_sdslen(x) == 33 && - memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) - - hi_sdsfree(x); - x = hi_sdscatprintf(hi_sdsempty(),"%d",123); - test_cond("hi_sdscatprintf() seems working in the base case", - hi_sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) - - hi_sdsfree(x); - x = hi_sdsnew("--"); - x = hi_sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); - test_cond("hi_sdscatfmt() seems working in the base case", - hi_sdslen(x) == 60 && - memcmp(x,"--Hello Hi! World -9223372036854775808," - "9223372036854775807--",60) == 0) - printf("[%s]\n",x); - - hi_sdsfree(x); - x = hi_sdsnew("--"); - x = hi_sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); - test_cond("hi_sdscatfmt() seems working with unsigned numbers", - hi_sdslen(x) == 35 && - memcmp(x,"--4294967295,18446744073709551615--",35) == 0) - - hi_sdsfree(x); - x = hi_sdsnew(" x "); - hi_sdstrim(x," x"); - test_cond("hi_sdstrim() works when all chars match", - hi_sdslen(x) == 0) - - hi_sdsfree(x); - x = hi_sdsnew(" x "); - hi_sdstrim(x," "); - test_cond("hi_sdstrim() works when a single char remains", - hi_sdslen(x) == 1 && x[0] == 'x') - - hi_sdsfree(x); - x = hi_sdsnew("xxciaoyyy"); - hi_sdstrim(x,"xy"); - test_cond("hi_sdstrim() correctly trims characters", - hi_sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) - - y = hi_sdsdup(x); - hi_sdsrange(y,1,1); - test_cond("hi_sdsrange(...,1,1)", - hi_sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,1,-1); - test_cond("hi_sdsrange(...,1,-1)", - hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,-2,-1); - test_cond("hi_sdsrange(...,-2,-1)", - hi_sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,2,1); - test_cond("hi_sdsrange(...,2,1)", - hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,1,100); - test_cond("hi_sdsrange(...,1,100)", - hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) - - hi_sdsfree(y); - y = hi_sdsdup(x); - hi_sdsrange(y,100,100); - test_cond("hi_sdsrange(...,100,100)", - hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("foo"); - y = hi_sdsnew("foa"); - test_cond("hi_sdscmp(foo,foa)", hi_sdscmp(x,y) > 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("bar"); - y = hi_sdsnew("bar"); - test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) == 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnew("aar"); - y = hi_sdsnew("bar"); - test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) < 0) - - hi_sdsfree(y); - hi_sdsfree(x); - x = hi_sdsnewlen("\a\n\0foo\r",7); - y = hi_sdscatrepr(hi_sdsempty(),x,hi_sdslen(x)); - test_cond("hi_sdscatrepr(...data...)", - memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) - - { - unsigned int oldfree; - char *p; - int step = 10, j, i; - - hi_sdsfree(x); - hi_sdsfree(y); - x = hi_sdsnew("0"); - test_cond("hi_sdsnew() free/len buffers", hi_sdslen(x) == 1 && hi_sdsavail(x) == 0); - - /* Run the test a few times in order to hit the first two - * SDS header types. */ - for (i = 0; i < 10; i++) { - int oldlen = hi_sdslen(x); - x = hi_sdsMakeRoomFor(x,step); - int type = x[-1]&HI_SDS_TYPE_MASK; - - test_cond("sdsMakeRoomFor() len", hi_sdslen(x) == oldlen); - if (type != HI_SDS_TYPE_5) { - test_cond("hi_sdsMakeRoomFor() free", hi_sdsavail(x) >= step); - oldfree = hi_sdsavail(x); - } - p = x+oldlen; - for (j = 0; j < step; j++) { - p[j] = 'A'+j; - } - hi_sdsIncrLen(x,step); - } - test_cond("hi_sdsMakeRoomFor() content", - memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); - test_cond("sdsMakeRoomFor() final length",hi_sdslen(x)==101); - - hi_sdsfree(x); - } - } - test_report(); - return 0; -} -#endif - -#ifdef HI_SDS_TEST_MAIN -int main(void) { - return hi_sdsTest(); -} -#endif diff --git a/src/sds.h b/src/sds.h deleted file mode 100644 index 573d6dd..0000000 --- a/src/sds.h +++ /dev/null @@ -1,278 +0,0 @@ -/* SDSLib 2.0 -- A C dynamic strings library - * - * Copyright (c) 2006-2015, Salvatore Sanfilippo - * 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. - */ - -#ifndef HIREDIS_SDS_H -#define HIREDIS_SDS_H - -#define HI_SDS_MAX_PREALLOC (1024*1024) -#ifdef _MSC_VER -#define __attribute__(x) -typedef long long ssize_t; -#define SSIZE_MAX (LLONG_MAX >> 1) -#endif - -#include -#include -#include - -typedef char *hisds; - -/* Note: sdshdr5 is never used, we just access the flags byte directly. - * However is here to document the layout of type 5 SDS strings. */ -struct __attribute__ ((__packed__)) hisdshdr5 { - unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ - char buf[]; -}; -struct __attribute__ ((__packed__)) hisdshdr8 { - 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__)) hisdshdr16 { - 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__)) hisdshdr32 { - 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__)) hisdshdr64 { - 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[]; -}; - -#define HI_SDS_TYPE_5 0 -#define HI_SDS_TYPE_8 1 -#define HI_SDS_TYPE_16 2 -#define HI_SDS_TYPE_32 3 -#define HI_SDS_TYPE_64 4 -#define HI_SDS_TYPE_MASK 7 -#define HI_SDS_TYPE_BITS 3 -#define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))); -#define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)))) -#define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS) - -static inline size_t hi_sdslen(const hisds s) { - unsigned char flags = s[-1]; - switch(flags & HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return HI_SDS_TYPE_5_LEN(flags); - case HI_SDS_TYPE_8: - return HI_SDS_HDR(8,s)->len; - case HI_SDS_TYPE_16: - return HI_SDS_HDR(16,s)->len; - case HI_SDS_TYPE_32: - return HI_SDS_HDR(32,s)->len; - case HI_SDS_TYPE_64: - return HI_SDS_HDR(64,s)->len; - } - return 0; -} - -static inline size_t hi_sdsavail(const hisds s) { - unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: { - return 0; - } - case HI_SDS_TYPE_8: { - HI_SDS_HDR_VAR(8,s); - return sh->alloc - sh->len; - } - case HI_SDS_TYPE_16: { - HI_SDS_HDR_VAR(16,s); - return sh->alloc - sh->len; - } - case HI_SDS_TYPE_32: { - HI_SDS_HDR_VAR(32,s); - return sh->alloc - sh->len; - } - case HI_SDS_TYPE_64: { - HI_SDS_HDR_VAR(64,s); - return sh->alloc - sh->len; - } - } - return 0; -} - -static inline void hi_sdssetlen(hisds s, size_t newlen) { - unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - { - unsigned char *fp = ((unsigned char*)s)-1; - *fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS)); - } - break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->len = (uint8_t)newlen; - break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->len = (uint16_t)newlen; - break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->len = (uint32_t)newlen; - break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->len = (uint64_t)newlen; - break; - } -} - -static inline void hi_sdsinclen(hisds s, size_t inc) { - unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - { - unsigned char *fp = ((unsigned char*)s)-1; - unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc; - *fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS); - } - break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->len += (uint8_t)inc; - break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->len += (uint16_t)inc; - break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->len += (uint32_t)inc; - break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->len += (uint64_t)inc; - break; - } -} - -/* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */ -static inline size_t hi_sdsalloc(const hisds s) { - unsigned char flags = s[-1]; - switch(flags & HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - return HI_SDS_TYPE_5_LEN(flags); - case HI_SDS_TYPE_8: - return HI_SDS_HDR(8,s)->alloc; - case HI_SDS_TYPE_16: - return HI_SDS_HDR(16,s)->alloc; - case HI_SDS_TYPE_32: - return HI_SDS_HDR(32,s)->alloc; - case HI_SDS_TYPE_64: - return HI_SDS_HDR(64,s)->alloc; - } - return 0; -} - -static inline void hi_sdssetalloc(hisds s, size_t newlen) { - unsigned char flags = s[-1]; - switch(flags&HI_SDS_TYPE_MASK) { - case HI_SDS_TYPE_5: - /* Nothing to do, this type has no total allocation info. */ - break; - case HI_SDS_TYPE_8: - HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen; - break; - case HI_SDS_TYPE_16: - HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen; - break; - case HI_SDS_TYPE_32: - HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen; - break; - case HI_SDS_TYPE_64: - HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen; - break; - } -} - -hisds hi_sdsnewlen(const void *init, size_t initlen); -hisds hi_sdsnew(const char *init); -hisds hi_sdsempty(void); -hisds hi_sdsdup(const hisds s); -void hi_sdsfree(hisds s); -hisds hi_sdsgrowzero(hisds s, size_t len); -hisds hi_sdscatlen(hisds s, const void *t, size_t len); -hisds hi_sdscat(hisds s, const char *t); -hisds hi_sdscatsds(hisds s, const hisds t); -hisds hi_sdscpylen(hisds s, const char *t, size_t len); -hisds hi_sdscpy(hisds s, const char *t); - -hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap); -#ifdef __GNUC__ -hisds hi_sdscatprintf(hisds s, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else -hisds hi_sdscatprintf(hisds s, const char *fmt, ...); -#endif - -hisds hi_sdscatfmt(hisds s, char const *fmt, ...); -hisds hi_sdstrim(hisds s, const char *cset); -int hi_sdsrange(hisds s, ssize_t start, ssize_t end); -void hi_sdsupdatelen(hisds s); -void hi_sdsclear(hisds s); -int hi_sdscmp(const hisds s1, const hisds s2); -hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); -void hi_sdsfreesplitres(hisds *tokens, int count); -void hi_sdstolower(hisds s); -void hi_sdstoupper(hisds s); -hisds hi_sdsfromlonglong(long long value); -hisds hi_sdscatrepr(hisds s, const char *p, size_t len); -hisds *hi_sdssplitargs(const char *line, int *argc); -hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen); -hisds hi_sdsjoin(char **argv, int argc, char *sep); -hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen); - -/* Low level functions exposed to the user API */ -hisds hi_sdsMakeRoomFor(hisds s, size_t addlen); -void hi_sdsIncrLen(hisds s, int incr); -hisds hi_sdsRemoveFreeSpace(hisds s); -size_t hi_sdsAllocSize(hisds s); -void *hi_sdsAllocPtr(hisds 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 *hi_sds_malloc(size_t size); -void *hi_sds_realloc(void *ptr, size_t size); -void hi_sds_free(void *ptr); - -#ifdef REDIS_TEST -int hi_sdsTest(int argc, char *argv[]); -#endif - -#endif /* HIREDIS_SDS_H */ diff --git a/src/sdsalloc.h b/src/sdsalloc.h deleted file mode 100644 index c9dcc3d..0000000 --- a/src/sdsalloc.h +++ /dev/null @@ -1,44 +0,0 @@ -/* SDSLib 2.0 -- A C dynamic strings library - * - * Copyright (c) 2006-2015, Salvatore Sanfilippo - * 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). */ - -#include "alloc.h" - -#define hi_s_malloc hi_malloc -#define hi_s_realloc hi_realloc -#define hi_s_free hi_free diff --git a/src/sdscompat.h b/src/sdscompat.h deleted file mode 100644 index e5a2574..0000000 --- a/src/sdscompat.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2020, Michael Grunder - * - * 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 compatibility header. - * - * This simple file maps sds types and calls to their unique hiredis symbol names. - * It's useful when we build Hiredis as a dependency of Redis and want to call - * Hiredis' sds symbols rather than the ones built into Redis, as the libraries - * have slightly diverged and could cause hard to track down ABI incompatibility - * bugs. - * - */ - -#ifndef HIREDIS_SDS_COMPAT -#define HIREDIS_SDS_COMPAT - -#define sds hisds - -#define sdslen hi_sdslen -#define sdsavail hi_sdsavail -#define sdssetlen hi_sdssetlen -#define sdsinclen hi_sdsinclen -#define sdsalloc hi_sdsalloc -#define sdssetalloc hi_sdssetalloc - -#define sdsAllocPtr hi_sdsAllocPtr -#define sdsAllocSize hi_sdsAllocSize -#define sdscat hi_sdscat -#define sdscatfmt hi_sdscatfmt -#define sdscatlen hi_sdscatlen -#define sdscatprintf hi_sdscatprintf -#define sdscatrepr hi_sdscatrepr -#define sdscatsds hi_sdscatsds -#define sdscatvprintf hi_sdscatvprintf -#define sdsclear hi_sdsclear -#define sdscmp hi_sdscmp -#define sdscpy hi_sdscpy -#define sdscpylen hi_sdscpylen -#define sdsdup hi_sdsdup -#define sdsempty hi_sdsempty -#define sds_free hi_sds_free -#define sdsfree hi_sdsfree -#define sdsfreesplitres hi_sdsfreesplitres -#define sdsfromlonglong hi_sdsfromlonglong -#define sdsgrowzero hi_sdsgrowzero -#define sdsIncrLen hi_sdsIncrLen -#define sdsjoin hi_sdsjoin -#define sdsjoinsds hi_sdsjoinsds -#define sdsll2str hi_sdsll2str -#define sdsMakeRoomFor hi_sdsMakeRoomFor -#define sds_malloc hi_sds_malloc -#define sdsmapchars hi_sdsmapchars -#define sdsnew hi_sdsnew -#define sdsnewlen hi_sdsnewlen -#define sdsrange hi_sdsrange -#define sds_realloc hi_sds_realloc -#define sdsRemoveFreeSpace hi_sdsRemoveFreeSpace -#define sdssplitargs hi_sdssplitargs -#define sdssplitlen hi_sdssplitlen -#define sdstolower hi_sdstolower -#define sdstoupper hi_sdstoupper -#define sdstrim hi_sdstrim -#define sdsull2str hi_sdsull2str -#define sdsupdatelen hi_sdsupdatelen - -#endif /* HIREDIS_SDS_COMPAT */ diff --git a/src/ssl.c b/src/ssl.c deleted file mode 100644 index d42d1e1..0000000 --- a/src/ssl.c +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * Copyright (c) 2019, Redis Labs - * - * 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 "hiredis.h" -#include "async.h" - -#include -#include -#include -#ifdef _WIN32 -#include -#include -#else -#include -#endif - -#include -#include - -#include "async_private.h" -#include "hiredis_ssl.h" - -void __redisSetError(redisContext *c, int type, const char *str); - -struct redisSSLContext { - /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */ - SSL_CTX *ssl_ctx; - - /* Requested SNI, or NULL */ - char *server_name; -}; - -/* The SSL connection context is attached to SSL/TLS connections as a privdata. */ -typedef struct redisSSL { - /** - * OpenSSL SSL object. - */ - SSL *ssl; - - /** - * SSL_write() requires to be called again with the same arguments it was - * previously called with in the event of an SSL_read/SSL_write situation - */ - size_t lastLen; - - /** Whether the SSL layer requires read (possibly before a write) */ - int wantRead; - - /** - * Whether a write was requested prior to a read. If set, the write() - * should resume whenever a read takes place, if possible - */ - int pendingWrite; -} redisSSL; - -/* Forward declaration */ -redisContextFuncs redisContextSSLFuncs; - -/** - * OpenSSL global initialization and locking handling callbacks. - * Note that this is only required for OpenSSL < 1.1.0. - */ - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define HIREDIS_USE_CRYPTO_LOCKS -#endif - -#ifdef HIREDIS_USE_CRYPTO_LOCKS -#ifdef _WIN32 -typedef CRITICAL_SECTION sslLockType; -static void sslLockInit(sslLockType* l) { - InitializeCriticalSection(l); -} -static void sslLockAcquire(sslLockType* l) { - EnterCriticalSection(l); -} -static void sslLockRelease(sslLockType* l) { - LeaveCriticalSection(l); -} -#else -typedef pthread_mutex_t sslLockType; -static void sslLockInit(sslLockType *l) { - pthread_mutex_init(l, NULL); -} -static void sslLockAcquire(sslLockType *l) { - pthread_mutex_lock(l); -} -static void sslLockRelease(sslLockType *l) { - pthread_mutex_unlock(l); -} -#endif - -static sslLockType* ossl_locks; - -static void opensslDoLock(int mode, int lkid, const char *f, int line) { - sslLockType *l = ossl_locks + lkid; - - if (mode & CRYPTO_LOCK) { - sslLockAcquire(l); - } else { - sslLockRelease(l); - } - - (void)f; - (void)line; -} - -static int initOpensslLocks(void) { - unsigned ii, nlocks; - if (CRYPTO_get_locking_callback() != NULL) { - /* Someone already set the callback before us. Don't destroy it! */ - return REDIS_OK; - } - nlocks = CRYPTO_num_locks(); - ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks); - if (ossl_locks == NULL) - return REDIS_ERR; - - for (ii = 0; ii < nlocks; ii++) { - sslLockInit(ossl_locks + ii); - } - CRYPTO_set_locking_callback(opensslDoLock); - return REDIS_OK; -} -#endif /* HIREDIS_USE_CRYPTO_LOCKS */ - -int redisInitOpenSSL(void) -{ - SSL_library_init(); -#ifdef HIREDIS_USE_CRYPTO_LOCKS - initOpensslLocks(); -#endif - - return REDIS_OK; -} - -/** - * redisSSLContext helper context destruction. - */ - -const char *redisSSLContextGetError(redisSSLContextError error) -{ - switch (error) { - case REDIS_SSL_CTX_NONE: - return "No Error"; - case REDIS_SSL_CTX_CREATE_FAILED: - return "Failed to create OpenSSL SSL_CTX"; - case REDIS_SSL_CTX_CERT_KEY_REQUIRED: - return "Client cert and key must both be specified or skipped"; - case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED: - return "Failed to load CA Certificate or CA Path"; - case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED: - return "Failed to load client certificate"; - case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED: - return "Failed to load private key"; - case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED: - return "Failed to open system certifcate store"; - case REDIS_SSL_CTX_OS_CERT_ADD_FAILED: - return "Failed to add CA certificates obtained from system to the SSL context"; - default: - return "Unknown error code"; - } -} - -void redisFreeSSLContext(redisSSLContext *ctx) -{ - if (!ctx) - return; - - if (ctx->server_name) { - hi_free(ctx->server_name); - ctx->server_name = NULL; - } - - if (ctx->ssl_ctx) { - SSL_CTX_free(ctx->ssl_ctx); - ctx->ssl_ctx = NULL; - } - - hi_free(ctx); -} - - -/** - * redisSSLContext helper context initialization. - */ - -redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath, - const char *cert_filename, const char *private_key_filename, - const char *server_name, redisSSLContextError *error) -{ -#ifdef _WIN32 - HCERTSTORE win_store = NULL; - PCCERT_CONTEXT win_ctx = NULL; -#endif - - redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext)); - if (ctx == NULL) - goto error; - - ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - if (!ctx->ssl_ctx) { - if (error) *error = REDIS_SSL_CTX_CREATE_FAILED; - goto error; - } - - SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL); - - if ((cert_filename != NULL && private_key_filename == NULL) || - (private_key_filename != NULL && cert_filename == NULL)) { - if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED; - goto error; - } - - if (capath || cacert_filename) { -#ifdef _WIN32 - if (0 == strcmp(cacert_filename, "wincert")) { - win_store = CertOpenSystemStore(NULL, "Root"); - if (!win_store) { - if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED; - goto error; - } - X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx); - while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) { - X509* x509 = NULL; - x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded); - if (x509) { - if ((1 != X509_STORE_add_cert(store, x509)) || - (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509))) - { - if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED; - goto error; - } - X509_free(x509); - } - } - CertFreeCertificateContext(win_ctx); - CertCloseStore(win_store, 0); - } else -#endif - if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) { - if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; - goto error; - } - } - - if (cert_filename) { - if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) { - if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED; - goto error; - } - if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) { - if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED; - goto error; - } - } - - if (server_name) - ctx->server_name = hi_strdup(server_name); - - return ctx; - -error: -#ifdef _WIN32 - CertFreeCertificateContext(win_ctx); - CertCloseStore(win_store, 0); -#endif - redisFreeSSLContext(ctx); - return NULL; -} - -/** - * SSL Connection initialization. - */ - - -static int redisSSLConnect(redisContext *c, SSL *ssl) { - if (c->privctx) { - __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); - return REDIS_ERR; - } - - redisSSL *rssl = hi_calloc(1, sizeof(redisSSL)); - if (rssl == NULL) { - __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); - return REDIS_ERR; - } - - c->funcs = &redisContextSSLFuncs; - rssl->ssl = ssl; - - SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_set_fd(rssl->ssl, c->fd); - SSL_set_connect_state(rssl->ssl); - - ERR_clear_error(); - int rv = SSL_connect(rssl->ssl); - if (rv == 1) { - c->privctx = rssl; - return REDIS_OK; - } - - rv = SSL_get_error(rssl->ssl, rv); - if (((c->flags & REDIS_BLOCK) == 0) && - (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { - c->privctx = rssl; - return REDIS_OK; - } - - if (c->err == 0) { - char err[512]; - if (rv == SSL_ERROR_SYSCALL) - snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno)); - else { - unsigned long e = ERR_peek_last_error(); - snprintf(err,sizeof(err)-1,"SSL_connect failed: %s", - ERR_reason_error_string(e)); - } - __redisSetError(c, REDIS_ERR_IO, err); - } - - hi_free(rssl); - return REDIS_ERR; -} - -/** - * A wrapper around redisSSLConnect() for users who manage their own context and - * create their own SSL object. - */ - -int redisInitiateSSL(redisContext *c, SSL *ssl) { - return redisSSLConnect(c, ssl); -} - -/** - * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't - * manage their own SSL objects. - */ - -int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx) -{ - if (!c || !redis_ssl_ctx) - return REDIS_ERR; - - /* We want to verify that redisSSLConnect() won't fail on this, as it will - * not own the SSL object in that case and we'll end up leaking. - */ - if (c->privctx) - return REDIS_ERR; - - SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx); - if (!ssl) { - __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); - goto error; - } - - if (redis_ssl_ctx->server_name) { - if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) { - __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI"); - goto error; - } - } - - if (redisSSLConnect(c, ssl) != REDIS_OK) { - goto error; - } - - return REDIS_OK; - -error: - if (ssl) - SSL_free(ssl); - return REDIS_ERR; -} - -static int maybeCheckWant(redisSSL *rssl, int rv) { - /** - * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set - * and true is returned. False is returned otherwise - */ - if (rv == SSL_ERROR_WANT_READ) { - rssl->wantRead = 1; - return 1; - } else if (rv == SSL_ERROR_WANT_WRITE) { - rssl->pendingWrite = 1; - return 1; - } else { - return 0; - } -} - -/** - * Implementation of redisContextFuncs for SSL connections. - */ - -static void redisSSLFree(void *privctx){ - redisSSL *rsc = privctx; - - if (!rsc) return; - if (rsc->ssl) { - SSL_free(rsc->ssl); - rsc->ssl = NULL; - } - hi_free(rsc); -} - -static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) { - redisSSL *rssl = c->privctx; - - int nread = SSL_read(rssl->ssl, buf, bufcap); - if (nread > 0) { - return nread; - } else if (nread == 0) { - __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); - return -1; - } else { - int err = SSL_get_error(rssl->ssl, nread); - if (c->flags & REDIS_BLOCK) { - /** - * In blocking mode, we should never end up in a situation where - * we get an error without it being an actual error, except - * in the case of EINTR, which can be spuriously received from - * debuggers or whatever. - */ - if (errno == EINTR) { - return 0; - } else { - const char *msg = NULL; - if (errno == EAGAIN) { - msg = "Resource temporarily unavailable"; - } - __redisSetError(c, REDIS_ERR_IO, msg); - return -1; - } - } - - /** - * We can very well get an EWOULDBLOCK/EAGAIN, however - */ - if (maybeCheckWant(rssl, err)) { - return 0; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } -} - -static ssize_t redisSSLWrite(redisContext *c) { - redisSSL *rssl = c->privctx; - - size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf); - int rv = SSL_write(rssl->ssl, c->obuf, len); - - if (rv > 0) { - rssl->lastLen = 0; - } else if (rv < 0) { - rssl->lastLen = len; - - int err = SSL_get_error(rssl->ssl, rv); - if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) { - return 0; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } - return rv; -} - -static void redisSSLAsyncRead(redisAsyncContext *ac) { - int rv; - redisSSL *rssl = ac->c.privctx; - redisContext *c = &ac->c; - - rssl->wantRead = 0; - - if (rssl->pendingWrite) { - int done; - - /* This is probably just a write event */ - rssl->pendingWrite = 0; - rv = redisBufferWrite(c, &done); - if (rv == REDIS_ERR) { - __redisAsyncDisconnect(ac); - return; - } else if (!done) { - _EL_ADD_WRITE(ac); - } - } - - rv = redisBufferRead(c); - if (rv == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -static void redisSSLAsyncWrite(redisAsyncContext *ac) { - int rv, done = 0; - redisSSL *rssl = ac->c.privctx; - redisContext *c = &ac->c; - - rssl->pendingWrite = 0; - rv = redisBufferWrite(c, &done); - if (rv == REDIS_ERR) { - __redisAsyncDisconnect(ac); - return; - } - - if (!done) { - if (rssl->wantRead) { - /* Need to read-before-write */ - rssl->pendingWrite = 1; - _EL_DEL_WRITE(ac); - } else { - /* No extra reads needed, just need to write more */ - _EL_ADD_WRITE(ac); - } - } else { - /* Already done! */ - _EL_DEL_WRITE(ac); - } - - /* Always reschedule a read */ - _EL_ADD_READ(ac); -} - -redisContextFuncs redisContextSSLFuncs = { - .free_privctx = redisSSLFree, - .async_read = redisSSLAsyncRead, - .async_write = redisSSLAsyncWrite, - .read = redisSSLRead, - .write = redisSSLWrite -}; - diff --git a/src/syncio.c b/src/syncio.c deleted file mode 100644 index 129432d..0000000 --- a/src/syncio.c +++ /dev/null @@ -1,117 +0,0 @@ - -#include "redis-migrate.h" -#include "ae.h" -#include -#include - -/* ----------------- Blocking sockets I/O with timeouts --------------------- */ - -#define SYNCIO__RESOLUTION 10 - -ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout) -{ - ssize_t nwritten, ret = size; - long long start = mstime(); - long long remaining = timeout; - - while (1) - { - long long wait = (remaining > SYNCIO__RESOLUTION) ? remaining : SYNCIO__RESOLUTION; - long long elapsed; - - nwritten = write(fd, ptr, size); - if (nwritten == -1) - { - if (errno != EAGAIN) - return -1; - } - else - { - ptr += nwritten; - size -= nwritten; - } - if (size == 0) - return ret; - - /* Wait */ - aeWait(fd, AE_WRITABLE, wait); - elapsed = mstime() - start; - if (elapsed >= timeout) - { - errno = ETIMEDOUT; - return -1; - } - remaining = timeout - elapsed; - } -} - -ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout) -{ - ssize_t nread, totread = 0; - long long start = mstime(); - long long remaining = timeout; - - if (size == 0) - return 0; - while (1) - { - long long wait = (remaining > SYNCIO__RESOLUTION) ? remaining : SYNCIO__RESOLUTION; - long long elapsed; - - nread = read(fd, ptr, size); - if (nread == 0) - return -1; - if (nread == -1) - { - if (errno != EAGAIN) - return -1; - } - else - { - ptr += nread; - size -= nread; - totread += nread; - } - if (size == 0) - return totread; - - /* Wait */ - aeWait(fd, AE_READABLE, wait); - elapsed = mstime() - start; - if (elapsed >= timeout) - { - errno = ETIMEDOUT; - return -1; - } - remaining = timeout - elapsed; - } -} - -ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout) -{ - ssize_t nread = 0; - - size--; - while (size) - { - char c; - - if (syncRead(fd, &c, 1, timeout) == -1) - return -1; - if (c == '\n') - { - *ptr = '\0'; - if (nread && *(ptr - 1) == '\r') - *(ptr - 1) = '\0'; - return nread; - } - else - { - *ptr++ = c; - *ptr = '\0'; - nread++; - } - size--; - } - return nread; -}