mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
Merge branch 'unstable' into fixChildInfoPipeFdLeak
This commit is contained in:
commit
adfaf548e3
@ -216,7 +216,7 @@ Inside the root are the following important directories:
|
||||
|
||||
* `src`: contains the Redis implementation, written in C.
|
||||
* `tests`: contains the unit tests, implemented in Tcl.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
|
||||
|
||||
There are a few more directories but they are not very important for our goals
|
||||
here. We'll focus mostly on `src`, where the Redis implementation is contained,
|
||||
|
6
deps/README.md
vendored
6
deps/README.md
vendored
@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that
|
||||
should be provided by the operating system.
|
||||
|
||||
* **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time.
|
||||
* **geohash-int** is inside the dependencies directory but is actually part of the Redis project, since it is our private fork (heavily modified) of a library initially developed for Ardb, which is in turn a fork of Redis.
|
||||
* **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed.
|
||||
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
|
||||
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
|
||||
@ -42,11 +41,6 @@ the following additional steps:
|
||||
changed, otherwise you could just copy the old implementation if you are
|
||||
upgrading just to a similar version of Jemalloc.
|
||||
|
||||
Geohash
|
||||
---
|
||||
|
||||
This is never upgraded since it's part of the Redis project. If there are changes to merge from Ardb there is the need to manually check differences, but at this point the source code is pretty different.
|
||||
|
||||
Hiredis
|
||||
---
|
||||
|
||||
|
6
deps/hiredis/.travis.yml
vendored
6
deps/hiredis/.travis.yml
vendored
@ -8,6 +8,12 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
branches:
|
||||
only:
|
||||
- staging
|
||||
- trying
|
||||
- master
|
||||
|
||||
before_script:
|
||||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
||||
|
||||
|
53
deps/hiredis/CHANGELOG.md
vendored
53
deps/hiredis/CHANGELOG.md
vendored
@ -1,7 +1,51 @@
|
||||
### 1.0.0 (unreleased)
|
||||
|
||||
**Fixes**:
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
||||
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well. If it was used to
|
||||
compare to other values, casting might be necessary or can be removed, if
|
||||
casting was applied before.
|
||||
|
||||
### 0.14.0 (2018-09-25)
|
||||
|
||||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
||||
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
|
||||
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
|
||||
* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
|
||||
* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
|
||||
* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
|
||||
* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
|
||||
* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
|
||||
* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
|
||||
* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
|
||||
* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
|
||||
* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
|
||||
* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
|
||||
* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
|
||||
* Fix libevent leak (zfz [515228])
|
||||
* Clean up GCC warning (Ichito Nagata [2ec774])
|
||||
* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
|
||||
* Solaris compilation fix (Donald Whyte [41b07d])
|
||||
* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
|
||||
* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
|
||||
* libuv use after free fix (Paul Scott [cbb956])
|
||||
* Properly close socket fd on reconnect attempt (WSL [64d1ec])
|
||||
* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
|
||||
* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
|
||||
* Update libevent (Chris Xin [386802])
|
||||
* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
|
||||
* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
|
||||
* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
|
||||
* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
|
||||
* Compatibility fix for strerror_r (Tom Lee [bb1747])
|
||||
* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
|
||||
* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
|
||||
* Catch a buffer overflow when formatting the error message
|
||||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
|
||||
* Fix warnings, when compiled with -Wshadow
|
||||
@ -9,11 +53,6 @@
|
||||
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
|
||||
* Remove backwards compatibility macro's
|
||||
|
||||
This removes the following old function aliases, use the new name now:
|
||||
@ -94,7 +133,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
|
||||
|
||||
* Add IPv6 support
|
||||
|
||||
* Remove possiblity of multiple close on same fd
|
||||
* Remove possibility of multiple close on same fd
|
||||
|
||||
* Add ability to bind source address on connect
|
||||
|
||||
|
24
deps/hiredis/Makefile
vendored
24
deps/hiredis/Makefile
vendored
@ -36,13 +36,13 @@ endef
|
||||
export REDIS_TEST_CONFIG
|
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $(CXX) >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
DEBUG_FLAGS?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH)
|
||||
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
@ -58,12 +58,11 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
ifeq ($(uname_S),SunOS)
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
INSTALL= cp -r
|
||||
endif
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||
@ -94,7 +93,7 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
|
||||
|
||||
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
|
||||
@ -161,11 +160,7 @@ clean:
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
INSTALL?= cp -r
|
||||
endif
|
||||
|
||||
INSTALL?= cp -a
|
||||
INSTALL?= cp -pPR
|
||||
|
||||
$(PKGCONFNAME): hiredis.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@ -181,8 +176,9 @@ $(PKGCONFNAME): hiredis.h
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||
|
4
deps/hiredis/adapters/libevent.h
vendored
4
deps/hiredis/adapters/libevent.h
vendored
@ -73,8 +73,8 @@ static void redisLibeventDelWrite(void *privdata) {
|
||||
|
||||
static void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(e->rev);
|
||||
event_del(e->wev);
|
||||
event_free(e->rev);
|
||||
event_free(e->wev);
|
||||
free(e);
|
||||
}
|
||||
|
||||
|
9
deps/hiredis/adapters/libuv.h
vendored
9
deps/hiredis/adapters/libuv.h
vendored
@ -15,15 +15,12 @@ typedef struct redisLibuvEvents {
|
||||
|
||||
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||
int ev = (status ? p->events : events);
|
||||
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p->context != NULL && (events & UV_READABLE)) {
|
||||
if (p->context != NULL && (ev & UV_READABLE)) {
|
||||
redisAsyncHandleRead(p->context);
|
||||
}
|
||||
if (p->context != NULL && (events & UV_WRITABLE)) {
|
||||
if (p->context != NULL && (ev & UV_WRITABLE)) {
|
||||
redisAsyncHandleWrite(p->context);
|
||||
}
|
||||
}
|
||||
|
17
deps/hiredis/appveyor.yml
vendored
17
deps/hiredis/appveyor.yml
vendored
@ -1,24 +1,13 @@
|
||||
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
|
||||
environment:
|
||||
matrix:
|
||||
- CYG_ROOT: C:\cygwin64
|
||||
CYG_SETUP: setup-x86_64.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin64\var\cache\setup
|
||||
CYG_BASH: C:\cygwin64\bin\bash
|
||||
- CYG_BASH: C:\cygwin64\bin\bash
|
||||
CC: gcc
|
||||
- CYG_ROOT: C:\cygwin
|
||||
CYG_SETUP: setup-x86.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin\var\cache\setup
|
||||
CYG_BASH: C:\cygwin\bin\bash
|
||||
- CYG_BASH: C:\cygwin\bin\bash
|
||||
CC: gcc
|
||||
TARGET: 32bit
|
||||
TARGET_VARS: 32bit-vars
|
||||
|
||||
# Cache Cygwin files to speed up build
|
||||
cache:
|
||||
- '%CYG_CACHE%'
|
||||
clone_depth: 1
|
||||
|
||||
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
|
||||
@ -27,8 +16,6 @@ init:
|
||||
|
||||
# Install needed build dependencies
|
||||
install:
|
||||
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
|
||||
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
|
||||
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
|
||||
|
||||
build_script:
|
||||
|
67
deps/hiredis/async.c
vendored
67
deps/hiredis/async.c
vendored
@ -336,7 +336,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
|
||||
if (ac->err == 0) {
|
||||
/* For clean disconnects, there should be no pending callbacks. */
|
||||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
|
||||
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. */
|
||||
@ -364,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
|
||||
redisContext *c = &(ac->c);
|
||||
dict *callbacks;
|
||||
redisCallback *cb;
|
||||
dictEntry *de;
|
||||
int pvariant;
|
||||
char *stype;
|
||||
@ -387,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
||||
de = dictFind(callbacks,sname);
|
||||
if (de != NULL) {
|
||||
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
|
||||
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) {
|
||||
dictDelete(callbacks,sname);
|
||||
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);
|
||||
if (reply->element[2]->integer == 0)
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
@ -410,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
|
||||
void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb = {NULL, NULL, NULL};
|
||||
redisCallback cb = {NULL, NULL, 0, NULL};
|
||||
void *reply = NULL;
|
||||
int status;
|
||||
|
||||
@ -492,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
* 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 (redisCheckSocketError(c) == REDIS_ERR) {
|
||||
/* Try again later when connect(2) is still in progress. */
|
||||
if (errno == EINPROGRESS)
|
||||
return REDIS_OK;
|
||||
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
|
||||
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
||||
/* Error! */
|
||||
redisCheckSocketError(c);
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||
__redisAsyncDisconnect(ac);
|
||||
return REDIS_ERR;
|
||||
} else if (completed == 1) {
|
||||
/* connected! */
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Mark context as connected. */
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
@ -583,6 +597,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
|
||||
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;
|
||||
@ -596,6 +613,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
/* Setup callback */
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
cb.pending_subs = 1;
|
||||
|
||||
/* Find out which command will be appended. */
|
||||
p = nextArgument(cmd,&cstr,&clen);
|
||||
@ -612,9 +630,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||
sname = sdsnewlen(astr,alen);
|
||||
if (pvariant)
|
||||
ret = dictReplace(ac->sub.patterns,sname,&cb);
|
||||
cbdict = ac->sub.patterns;
|
||||
else
|
||||
ret = dictReplace(ac->sub.channels,sname,&cb);
|
||||
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) sdsfree(sname);
|
||||
}
|
||||
@ -676,6 +703,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||
if (len < 0)
|
||||
return REDIS_ERR;
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
sdsfree(cmd);
|
||||
return status;
|
||||
|
5
deps/hiredis/async.h
vendored
5
deps/hiredis/async.h
vendored
@ -45,6 +45,7 @@ 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;
|
||||
|
||||
@ -92,6 +93,10 @@ typedef struct redisAsyncContext {
|
||||
/* Regular command callbacks */
|
||||
redisCallbackList replies;
|
||||
|
||||
/* Address used for connect() */
|
||||
struct sockaddr *saddr;
|
||||
size_t addrlen;
|
||||
|
||||
/* Subscription callbacks */
|
||||
struct {
|
||||
redisCallbackList invalid;
|
||||
|
19
deps/hiredis/fmacros.h
vendored
19
deps/hiredis/fmacros.h
vendored
@ -1,25 +1,12 @@
|
||||
#ifndef __HIREDIS_FMACRO_H
|
||||
#define __HIREDIS_FMACRO_H
|
||||
|
||||
#if defined(__linux__)
|
||||
#define _BSD_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#else
|
||||
#if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define _OSX
|
||||
/* Enable TCP_KEEPALIVE */
|
||||
#define _DARWIN_C_SOURCE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
125
deps/hiredis/hiredis.c
vendored
125
deps/hiredis/hiredis.c
vendored
@ -47,7 +47,9 @@ static redisReply *createReplyObject(int type);
|
||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
||||
static void *createArrayObject(const redisReadTask *task, int 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. */
|
||||
@ -55,7 +57,9 @@ static redisReplyObjectFunctions defaultFunctions = {
|
||||
createStringObject,
|
||||
createArrayObject,
|
||||
createIntegerObject,
|
||||
createDoubleObject,
|
||||
createNilObject,
|
||||
createBoolObject,
|
||||
freeReplyObject
|
||||
};
|
||||
|
||||
@ -82,18 +86,19 @@ void freeReplyObject(void *reply) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
break; /* Nothing to free */
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
if (r->element != NULL) {
|
||||
for (j = 0; j < r->elements; j++)
|
||||
if (r->element[j] != NULL)
|
||||
freeReplyObject(r->element[j]);
|
||||
freeReplyObject(r->element[j]);
|
||||
free(r->element);
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
if (r->str != NULL)
|
||||
free(r->str);
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
free(r->str);
|
||||
break;
|
||||
}
|
||||
free(r);
|
||||
@ -125,7 +130,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -134,7 +141,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_ARRAY);
|
||||
r = createReplyObject(task->type);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
@ -150,7 +157,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -167,7 +176,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
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 = 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';
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -182,7 +225,28 @@ static void *createNilObject(const redisReadTask *task) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
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->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -432,11 +496,7 @@ cleanup:
|
||||
}
|
||||
|
||||
sdsfree(curarg);
|
||||
|
||||
/* No need to check cmd since it is the last statement that can fail,
|
||||
* but do it anyway to be as defensive as possible. */
|
||||
if (cmd != NULL)
|
||||
free(cmd);
|
||||
free(cmd);
|
||||
|
||||
return error_type;
|
||||
}
|
||||
@ -581,7 +641,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
|
||||
} else {
|
||||
/* Only REDIS_ERR_IO may lack a description! */
|
||||
assert(type == REDIS_ERR_IO);
|
||||
__redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,14 +656,8 @@ static redisContext *redisContextInit(void) {
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->err = 0;
|
||||
c->errstr[0] = '\0';
|
||||
c->obuf = sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
c->tcp.host = NULL;
|
||||
c->tcp.source_addr = NULL;
|
||||
c->unix_sock.path = NULL;
|
||||
c->timeout = NULL;
|
||||
|
||||
if (c->obuf == NULL || c->reader == NULL) {
|
||||
redisFree(c);
|
||||
@ -618,18 +672,14 @@ void redisFree(redisContext *c) {
|
||||
return;
|
||||
if (c->fd > 0)
|
||||
close(c->fd);
|
||||
if (c->obuf != NULL)
|
||||
sdsfree(c->obuf);
|
||||
if (c->reader != NULL)
|
||||
redisReaderFree(c->reader);
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
if (c->tcp.source_addr)
|
||||
free(c->tcp.source_addr);
|
||||
if (c->unix_sock.path)
|
||||
free(c->unix_sock.path);
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
free(c->tcp.host);
|
||||
free(c->tcp.source_addr);
|
||||
free(c->unix_sock.path);
|
||||
free(c->timeout);
|
||||
free(c->saddr);
|
||||
free(c);
|
||||
}
|
||||
|
||||
@ -710,6 +760,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
@ -718,6 +770,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
@ -789,7 +843,7 @@ int redisEnableKeepAlive(redisContext *c) {
|
||||
/* 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 redisContextReadReply to
|
||||
* 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];
|
||||
@ -1007,9 +1061,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
|
||||
|
||||
void *redisCommand(redisContext *c, const char *format, ...) {
|
||||
va_list ap;
|
||||
void *reply = NULL;
|
||||
va_start(ap,format);
|
||||
reply = redisvCommand(c,format,ap);
|
||||
void *reply = redisvCommand(c,format,ap);
|
||||
va_end(ap);
|
||||
return reply;
|
||||
}
|
||||
|
37
deps/hiredis/hiredis.h
vendored
37
deps/hiredis/hiredis.h
vendored
@ -40,9 +40,9 @@
|
||||
#include "sds.h" /* for sds */
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 13
|
||||
#define HIREDIS_PATCH 3
|
||||
#define HIREDIS_SONAME 0.13
|
||||
#define HIREDIS_MINOR 14
|
||||
#define HIREDIS_PATCH 0
|
||||
#define HIREDIS_SONAME 0.14
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
@ -80,30 +80,6 @@
|
||||
* SO_REUSEADDR is being used. */
|
||||
#define REDIS_CONNECT_RETRIES 10
|
||||
|
||||
/* strerror_r has two completely different prototypes and behaviors
|
||||
* depending on system issues, so we need to operate on the error buffer
|
||||
* differently depending on which strerror_r we're using. */
|
||||
#ifndef _GNU_SOURCE
|
||||
/* "regular" POSIX strerror_r that does the right thing. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
strerror_r((errno), (buf), (len)); \
|
||||
} while (0)
|
||||
#else
|
||||
/* "bad" GNU strerror_r we need to clean up after. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
char *err_str = strerror_r((errno), (buf), (len)); \
|
||||
/* If return value _isn't_ the start of the buffer we passed in, \
|
||||
* then GNU strerror_r returned an internal static buffer and we \
|
||||
* need to copy the result into our private buffer. */ \
|
||||
if (err_str != (buf)) { \
|
||||
strncpy((buf), err_str, ((len) - 1)); \
|
||||
buf[(len)-1] = '\0'; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -112,8 +88,10 @@ extern "C" {
|
||||
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 both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
@ -158,6 +136,9 @@ typedef struct redisContext {
|
||||
char *path;
|
||||
} unix_sock;
|
||||
|
||||
/* For non-blocking connect */
|
||||
struct sockadr *saddr;
|
||||
size_t addrlen;
|
||||
} redisContext;
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
|
75
deps/hiredis/net.c
vendored
75
deps/hiredis/net.c
vendored
@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) {
|
||||
}
|
||||
|
||||
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);
|
||||
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
|
||||
strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
|
||||
__redisSetError(c,type,buf);
|
||||
}
|
||||
|
||||
@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
|
||||
val = interval;
|
||||
|
||||
#ifdef _OSX
|
||||
#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__)
|
||||
val = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (redisCheckSocketError(c) != REDIS_OK)
|
||||
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
|
||||
redisCheckSocketError(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
@ -232,8 +234,28 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
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;
|
||||
int err = 0, errno_saved = errno;
|
||||
socklen_t errlen = sizeof(err);
|
||||
|
||||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
||||
@ -241,6 +263,10 @@ int redisCheckSocketError(redisContext *c) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
err = errno_saved;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
errno = err;
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
@ -285,8 +311,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
||||
**/
|
||||
if (c->tcp.host != addr) {
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
free(c->tcp.host);
|
||||
|
||||
c->tcp.host = strdup(addr);
|
||||
}
|
||||
@ -299,8 +324,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
@ -356,6 +380,7 @@ addrretry:
|
||||
n = 1;
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
|
||||
sizeof(n)) < 0) {
|
||||
freeaddrinfo(bservinfo);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@ -374,12 +399,27 @@ addrretry:
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* For repeat connection */
|
||||
if (c->saddr) {
|
||||
free(c->saddr);
|
||||
}
|
||||
c->saddr = malloc(p->ai_addrlen);
|
||||
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) {
|
||||
redisContextCloseFd(c);
|
||||
continue;
|
||||
} else if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} 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;
|
||||
@ -388,6 +428,7 @@ addrretry:
|
||||
goto addrretry;
|
||||
}
|
||||
} else {
|
||||
wait_for_ready:
|
||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||
goto error;
|
||||
}
|
||||
@ -411,7 +452,10 @@ addrretry:
|
||||
error:
|
||||
rv = REDIS_ERR;
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
if(servinfo) {
|
||||
freeaddrinfo(servinfo);
|
||||
}
|
||||
|
||||
return rv; // Need to return REDIS_OK if alright
|
||||
}
|
||||
|
||||
@ -431,7 +475,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
struct sockaddr_un sa;
|
||||
long timeout_msec = -1;
|
||||
|
||||
if (redisCreateSocket(c,AF_LOCAL) < 0)
|
||||
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||
return REDIS_ERR;
|
||||
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
@ -448,15 +492,14 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
sa.sun_family = AF_UNIX;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
|
5
deps/hiredis/net.h
vendored
5
deps/hiredis/net.h
vendored
@ -37,10 +37,6 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
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);
|
||||
@ -49,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
233
deps/hiredis/read.c
vendored
233
deps/hiredis/read.c
vendored
@ -29,7 +29,6 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@ -39,6 +38,8 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "read.h"
|
||||
#include "sds.h"
|
||||
@ -52,11 +53,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
}
|
||||
|
||||
/* Clear input buffer on errors. */
|
||||
if (r->buf != NULL) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
}
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
|
||||
/* Reset task stack. */
|
||||
r->ridx = -1;
|
||||
@ -143,33 +142,79 @@ static char *seekNewline(char *s, size_t len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read a long long value starting at *s, under the assumption that it will be
|
||||
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
|
||||
static long long readLongLong(char *s) {
|
||||
long long v = 0;
|
||||
int dec, mult = 1;
|
||||
char c;
|
||||
/* 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 (*s == '-') {
|
||||
mult = -1;
|
||||
s++;
|
||||
} else if (*s == '+') {
|
||||
mult = 1;
|
||||
s++;
|
||||
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;
|
||||
}
|
||||
|
||||
while ((c = *(s++)) != '\r') {
|
||||
dec = c - '0';
|
||||
if (dec >= 0 && dec < 10) {
|
||||
v *= 10;
|
||||
v += dec;
|
||||
} else {
|
||||
/* Should not happen... */
|
||||
return -1;
|
||||
}
|
||||
if (p[0] == '-') {
|
||||
negative = 1;
|
||||
p++; plen++;
|
||||
|
||||
/* Abort on only a negative sign. */
|
||||
if (plen == slen)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
return mult*v;
|
||||
/* 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) {
|
||||
@ -198,7 +243,9 @@ static void moveToNextTask(redisReader *r) {
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY ||
|
||||
prv->type == REDIS_REPLY_MAP ||
|
||||
prv->type == REDIS_REPLY_SET);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
@ -220,10 +267,58 @@ static int processLineItem(redisReader *r) {
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
if (r->fn && r->fn->createInteger)
|
||||
obj = r->fn->createInteger(cur,readLongLong(p));
|
||||
else
|
||||
if (r->fn && r->fn->createInteger) {
|
||||
long long v;
|
||||
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad integer value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
obj = r->fn->createInteger(cur,v);
|
||||
} else {
|
||||
obj = (void*)REDIS_REPLY_INTEGER;
|
||||
}
|
||||
} else if (cur->type == REDIS_REPLY_DOUBLE) {
|
||||
if (r->fn && r->fn->createDouble) {
|
||||
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 (strcasecmp(buf,",inf") == 0) {
|
||||
d = 1.0/0.0; /* Positive infinite. */
|
||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||
d = -1.0/0.0; /* Nevative infinite. */
|
||||
} else {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad double value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
obj = r->fn->createDouble(cur,d,buf,len);
|
||||
} else {
|
||||
obj = (void*)REDIS_REPLY_DOUBLE;
|
||||
}
|
||||
} else if (cur->type == REDIS_REPLY_NIL) {
|
||||
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 = 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 {
|
||||
/* Type will be error or status. */
|
||||
if (r->fn && r->fn->createString)
|
||||
@ -250,7 +345,7 @@ static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long len;
|
||||
long long len;
|
||||
unsigned long bytelen;
|
||||
int success = 0;
|
||||
|
||||
@ -259,9 +354,20 @@ static int processBulkItem(redisReader *r) {
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = readLongLong(p);
|
||||
|
||||
if (len < 0) {
|
||||
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);
|
||||
@ -299,12 +405,13 @@ static int processBulkItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processMultiBulkItem(redisReader *r) {
|
||||
/* Process the array, map and set types. */
|
||||
static int processAggregateItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
long elements;
|
||||
int root = 0;
|
||||
long long elements;
|
||||
int root = 0, len;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == 8) {
|
||||
@ -313,10 +420,21 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = readLongLong(p);
|
||||
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 || elements > INT_MAX) {
|
||||
__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);
|
||||
@ -330,10 +448,12 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
|
||||
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*)REDIS_REPLY_ARRAY;
|
||||
obj = (void*)(long)cur->type;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
@ -381,12 +501,27 @@ static int processItem(redisReader *r) {
|
||||
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;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@ -402,11 +537,16 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_INTEGER:
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
case REDIS_REPLY_NIL:
|
||||
case REDIS_REPLY_BOOL:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
return processAggregateItem(r);
|
||||
default:
|
||||
assert(NULL);
|
||||
return REDIS_ERR; /* Avoid warning. */
|
||||
@ -416,12 +556,10 @@ static int processItem(redisReader *r) {
|
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
redisReader *r;
|
||||
|
||||
r = calloc(sizeof(redisReader),1);
|
||||
r = calloc(1,sizeof(redisReader));
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->err = 0;
|
||||
r->errstr[0] = '\0';
|
||||
r->fn = fn;
|
||||
r->buf = sdsempty();
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
@ -435,10 +573,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
}
|
||||
|
||||
void redisReaderFree(redisReader *r) {
|
||||
if (r == NULL)
|
||||
return;
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||
r->fn->freeObject(r->reply);
|
||||
if (r->buf != NULL)
|
||||
sdsfree(r->buf);
|
||||
sdsfree(r->buf);
|
||||
free(r);
|
||||
}
|
||||
|
||||
|
10
deps/hiredis/read.h
vendored
10
deps/hiredis/read.h
vendored
@ -53,6 +53,14 @@
|
||||
#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_VERB 9
|
||||
#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_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||
|
||||
@ -73,7 +81,9 @@ typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
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;
|
||||
|
||||
|
29
deps/hiredis/sds.c
vendored
29
deps/hiredis/sds.c
vendored
@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
hdrlen = sdsHdrSize(type);
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc(sh, hdrlen+newlen+1);
|
||||
if (newsh == NULL) return NULL;
|
||||
if (newsh == NULL) {
|
||||
s_free(sh);
|
||||
return NULL;
|
||||
}
|
||||
s = (char*)newsh+hdrlen;
|
||||
} else {
|
||||
/* Since the header size changes, need to move the string forward,
|
||||
@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
/* Make sure there is always space for at least 1 char. */
|
||||
if (sdsavail(s)==0) {
|
||||
s = sdsMakeRoomFor(s,1);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
|
||||
switch(*f) {
|
||||
@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,str,l);
|
||||
sdsinclen(s,l);
|
||||
@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = sdsll2str(buf,num);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sdsinclen(s,l);
|
||||
@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = sdsull2str(buf,unum);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sdsinclen(s,l);
|
||||
@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
/* 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
|
||||
@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
{
|
||||
char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
if (new_vector == NULL) {
|
||||
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 = s_malloc(sizeof(void*));
|
||||
|
154
deps/hiredis/test.c
vendored
154
deps/hiredis/test.c
vendored
@ -3,7 +3,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netdb.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
@ -91,7 +93,7 @@ static int disconnect(redisContext *c, int keep_fd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static redisContext *connect(struct config config) {
|
||||
static redisContext *do_connect(struct config config) {
|
||||
redisContext *c = NULL;
|
||||
|
||||
if (config.type == CONN_TCP) {
|
||||
@ -248,7 +250,7 @@ static void test_append_formatted_commands(struct config config) {
|
||||
char *cmd;
|
||||
int len;
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
|
||||
test("Append format command: ");
|
||||
|
||||
@ -302,6 +304,82 @@ static void test_reply_reader(void) {
|
||||
strncasecmp(reader->errstr,"No support for",14) == 0);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":9223372036854775807\r\n",22);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->integer == LLONG_MAX);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when > LLONG_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":9223372036854775808\r\n",22);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MIN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->integer == LLONG_MIN);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when < LLONG_MIN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when array < -1: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when bulk < -1: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when array > INT_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when bulk > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
#endif
|
||||
|
||||
test("Works with NULL functions for reply: ");
|
||||
reader = redisReaderCreate();
|
||||
reader->fn = NULL;
|
||||
@ -358,18 +436,32 @@ static void test_free_null(void) {
|
||||
|
||||
static void test_blocking_connection_errors(void) {
|
||||
redisContext *c;
|
||||
struct addrinfo hints = {.ai_family = AF_INET};
|
||||
struct addrinfo *ai_tmp = NULL;
|
||||
const char *bad_domain = "idontexist.com";
|
||||
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
c = redisConnect((char*)"idontexist.test", 6379);
|
||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr,"Name or service not known") == 0 ||
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
|
||||
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp);
|
||||
if (rv != 0) {
|
||||
// Address does *not* exist
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
// First see if this domain name *actually* resolves to NXDOMAIN
|
||||
c = redisConnect("dontexist.com", 6379);
|
||||
test_cond(
|
||||
c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||
strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
} else {
|
||||
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
|
||||
freeaddrinfo(ai_tmp);
|
||||
}
|
||||
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 1);
|
||||
@ -387,7 +479,7 @@ static void test_blocking_connection(struct config config) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
|
||||
test("Is able to deliver commands: ");
|
||||
reply = redisCommand(c,"PING");
|
||||
@ -468,7 +560,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
||||
struct timeval tv;
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
test("Successfully completes a command when the timeout is not exceeded: ");
|
||||
reply = redisCommand(c,"SET foo fast");
|
||||
freeReplyObject(reply);
|
||||
@ -480,7 +572,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
freeReplyObject(reply);
|
||||
disconnect(c, 0);
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
test("Does not return a reply when the command times out: ");
|
||||
s = write(c->fd, cmd, strlen(cmd));
|
||||
tv.tv_sec = 0;
|
||||
@ -514,7 +606,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
int major, minor;
|
||||
|
||||
/* Connect to target given by config. */
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
{
|
||||
/* Find out Redis version to determine the path for the next test */
|
||||
const char *field = "redis_version:";
|
||||
@ -549,7 +641,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
redisFree(c);
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
test("Returns I/O error on socket timeout: ");
|
||||
struct timeval tv = { 0, 1000 };
|
||||
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
||||
@ -583,7 +675,7 @@ static void test_invalid_timeout_errors(struct config config) {
|
||||
}
|
||||
|
||||
static void test_throughput(struct config config) {
|
||||
redisContext *c = connect(config);
|
||||
redisContext *c = do_connect(config);
|
||||
redisReply **replies;
|
||||
int i, num;
|
||||
long long t1, t2;
|
||||
@ -616,6 +708,17 @@ static void test_throughput(struct config config) {
|
||||
free(replies);
|
||||
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
num = 10000;
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
@ -644,6 +747,19 @@ static void test_throughput(struct config config) {
|
||||
free(replies);
|
||||
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
|
465
src/acl.c
Normal file
465
src/acl.c
Normal file
@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* =============================================================================
|
||||
* Global state for ACLs
|
||||
* ==========================================================================*/
|
||||
|
||||
rax *Users; /* Table mapping usernames to user structures. */
|
||||
user *DefaultUser; /* Global reference to the default user.
|
||||
Every new connection is associated to it, if no
|
||||
AUTH or HELLO is used to authenticate with a
|
||||
different user. */
|
||||
|
||||
/* =============================================================================
|
||||
* Helper functions for the rest of the ACL implementation
|
||||
* ==========================================================================*/
|
||||
|
||||
/* Return zero if strings are the same, non-zero if they are not.
|
||||
* The comparison is performed in a way that prevents an attacker to obtain
|
||||
* information about the nature of the strings just monitoring the execution
|
||||
* time of the function.
|
||||
*
|
||||
* Note that limiting the comparison length to strings up to 512 bytes we
|
||||
* can avoid leaking any information about the password length and any
|
||||
* possible branch misprediction related leak.
|
||||
*/
|
||||
int time_independent_strcmp(char *a, char *b) {
|
||||
char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
|
||||
/* The above two strlen perform len(a) + len(b) operations where either
|
||||
* a or b are fixed (our password) length, and the difference is only
|
||||
* relative to the length of the user provided string, so no information
|
||||
* leak is possible in the following two lines of code. */
|
||||
unsigned int alen = strlen(a);
|
||||
unsigned int blen = strlen(b);
|
||||
unsigned int j;
|
||||
int diff = 0;
|
||||
|
||||
/* We can't compare strings longer than our static buffers.
|
||||
* Note that this will never pass the first test in practical circumstances
|
||||
* so there is no info leak. */
|
||||
if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;
|
||||
|
||||
memset(bufa,0,sizeof(bufa)); /* Constant time. */
|
||||
memset(bufb,0,sizeof(bufb)); /* Constant time. */
|
||||
/* Again the time of the following two copies is proportional to
|
||||
* len(a) + len(b) so no info is leaked. */
|
||||
memcpy(bufa,a,alen);
|
||||
memcpy(bufb,b,blen);
|
||||
|
||||
/* Always compare all the chars in the two buffers without
|
||||
* conditional expressions. */
|
||||
for (j = 0; j < sizeof(bufa); j++) {
|
||||
diff |= (bufa[j] ^ bufb[j]);
|
||||
}
|
||||
/* Length must be equal as well. */
|
||||
diff |= alen ^ blen;
|
||||
return diff; /* If zero strings are the same. */
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* Low level ACL API
|
||||
* ==========================================================================*/
|
||||
|
||||
/* Method for passwords/pattern comparison used for the user->passwords list
|
||||
* so that we can search for items with listSearchKey(). */
|
||||
int ACLListMatchSds(void *a, void *b) {
|
||||
return sdscmp(a,b) == 0;
|
||||
}
|
||||
|
||||
/* Create a new user with the specified name, store it in the list
|
||||
* of users (the Users global radix tree), and returns a reference to
|
||||
* the structure representing the user.
|
||||
*
|
||||
* If the user with such name already exists NULL is returned. */
|
||||
user *ACLCreateUser(const char *name, size_t namelen) {
|
||||
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
||||
user *u = zmalloc(sizeof(*u));
|
||||
u->name = sdsnewlen(name,namelen);
|
||||
u->flags = 0;
|
||||
u->allowed_subcommands = NULL;
|
||||
u->passwords = listCreate();
|
||||
u->patterns = listCreate();
|
||||
listSetMatchMethod(u->passwords,ACLListMatchSds);
|
||||
listSetMatchMethod(u->patterns,ACLListMatchSds);
|
||||
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
|
||||
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
|
||||
return u;
|
||||
}
|
||||
|
||||
/* Set user properties according to the string "op". The following
|
||||
* is a description of what different strings will do:
|
||||
*
|
||||
* on Enable the user: it is possible to authenticate as this user.
|
||||
* off Disable the user: it's no longer possible to authenticate
|
||||
* with this user, however the already authenticated connections
|
||||
* will still work.
|
||||
* +<command> Allow the execution of that command
|
||||
* -<command> Disallow the execution of that command
|
||||
* +@<category> Allow the execution of all the commands in such category
|
||||
* with valid categories being @set, @sortedset, @list, @hash,
|
||||
* @string, @bitmap, @hyperloglog,
|
||||
* @stream, @admin, @readonly,
|
||||
* @readwrite, @fast, @slow,
|
||||
* @pubsub.
|
||||
* The special category @all means all the commands.
|
||||
* +<command>|subcommand Allow a specific subcommand of an otherwise
|
||||
* disabled command. Note that this form is not
|
||||
* allowed as negative like -DEBUG|SEGFAULT, but
|
||||
* only additive starting with "+".
|
||||
* ~<pattern> Add a pattern of keys that can be mentioned as part of
|
||||
* commands. For instance ~* allows all the keys. The pattern
|
||||
* is a glob-style pattern like the one of KEYS.
|
||||
* It is possible to specify multiple patterns.
|
||||
* ><password> Add this passowrd to the list of valid password for the user.
|
||||
* For example >mypass will add "mypass" to the list.
|
||||
* This directive clears the "nopass" flag (see later).
|
||||
* <<password> Remove this password from the list of valid passwords.
|
||||
* nopass All the set passwords of the user are removed, and the user
|
||||
* is flagged as requiring no password: it means that every
|
||||
* password will work against this user. If this directive is
|
||||
* used for the default user, every new connection will be
|
||||
* immediately authenticated with the default user without
|
||||
* any explicit AUTH command required. Note that the "resetpass"
|
||||
* directive will clear this condition.
|
||||
* allcommands Alias for +@all
|
||||
* allkeys Alias for ~*
|
||||
* resetpass Flush the list of allowed passwords. Moreover removes the
|
||||
* "nopass" status. After "resetpass" the user has no associated
|
||||
* passwords and there is no way to authenticate without adding
|
||||
* some password (or setting it as "nopass" later).
|
||||
* resetkeys Flush the list of allowed keys patterns.
|
||||
* reset Performs the following actions: resetpass, resetkeys, off,
|
||||
* -@all. The user returns to the same state it has immediately
|
||||
* after its creation.
|
||||
*
|
||||
* The 'op' string must be null terminated. The 'oplen' argument should
|
||||
* specify the length of the 'op' string in case the caller requires to pass
|
||||
* binary data (for instance the >password form may use a binary password).
|
||||
* Otherwise the field can be set to -1 and the function will use strlen()
|
||||
* to determine the length.
|
||||
*
|
||||
* The function returns C_OK if the action to perform was understood because
|
||||
* the 'op' string made sense. Otherwise C_ERR is returned if the operation
|
||||
* is unknown or has some syntax error.
|
||||
*/
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
if (oplen == -1) oplen = strlen(op);
|
||||
if (!strcasecmp(op,"on")) {
|
||||
u->flags |= USER_FLAG_ENABLED;
|
||||
} else if (!strcasecmp(op,"off")) {
|
||||
u->flags &= ~USER_FLAG_ENABLED;
|
||||
} else if (!strcasecmp(op,"allkeys") ||
|
||||
!strcasecmp(op,"~*"))
|
||||
{
|
||||
u->flags |= USER_FLAG_ALLKEYS;
|
||||
if (u->patterns) listEmpty(u->patterns);
|
||||
} else if (!strcasecmp(op,"allcommands") ||
|
||||
!strcasecmp(op,"+@all"))
|
||||
{
|
||||
memset(u->allowed_commands,255,sizeof(u->allowed_commands));
|
||||
u->flags |= USER_FLAG_ALLCOMMANDS;
|
||||
} else if (!strcasecmp(op,"nopass")) {
|
||||
u->flags |= USER_FLAG_NOPASS;
|
||||
listEmpty(u->passwords);
|
||||
} else if (!strcasecmp(op,"resetpass")) {
|
||||
u->flags &= ~USER_FLAG_NOPASS;
|
||||
listEmpty(u->passwords);
|
||||
} else if (op[0] == '>') {
|
||||
sds newpass = sdsnewlen(op+1,oplen-1);
|
||||
listNode *ln = listSearchKey(u->passwords,newpass);
|
||||
/* Avoid re-adding the same password multiple times. */
|
||||
if (ln == NULL) listAddNodeTail(u->passwords,newpass);
|
||||
u->flags &= ~USER_FLAG_NOPASS;
|
||||
} else if (op[0] == '<') {
|
||||
sds delpass = sdsnewlen(op+1,oplen-1);
|
||||
listNode *ln = listSearchKey(u->passwords,delpass);
|
||||
if (ln) listDelNode(u->passwords,ln);
|
||||
sdsfree(delpass);
|
||||
} else if (op[0] == '~') {
|
||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||
listNode *ln = listSearchKey(u->patterns,newpat);
|
||||
/* Avoid re-adding the same pattern multiple times. */
|
||||
if (ln == NULL) listAddNodeTail(u->patterns,newpat);
|
||||
u->flags &= ~USER_FLAG_ALLKEYS;
|
||||
} else {
|
||||
return C_ERR;
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Return the first password of the default user or NULL.
|
||||
* This function is needed for backward compatibility with the old
|
||||
* directive "requirepass" when Redis supported a single global
|
||||
* password. */
|
||||
sds ACLDefaultUserFirstPassword(void) {
|
||||
if (listLength(DefaultUser->passwords) == 0) return NULL;
|
||||
listNode *first = listFirst(DefaultUser->passwords);
|
||||
return listNodeValue(first);
|
||||
}
|
||||
|
||||
/* Initialization of the ACL subsystem. */
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
DefaultUser = ACLCreateUser("default",7);
|
||||
ACLSetUser(DefaultUser,"+@all",-1);
|
||||
ACLSetUser(DefaultUser,"~*",-1);
|
||||
ACLSetUser(DefaultUser,"on",-1);
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
* otherwise C_ERR is returned and errno is set to:
|
||||
*
|
||||
* EINVAL: if the username-password do not match.
|
||||
* ENONENT: if the specified user does not exist at all.
|
||||
*/
|
||||
int ACLCheckUserCredentials(robj *username, robj *password) {
|
||||
user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr));
|
||||
if (u == NULL) {
|
||||
errno = ENOENT;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Disabled users can't login. */
|
||||
if ((u->flags & USER_FLAG_ENABLED) == 0) {
|
||||
errno = EINVAL;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* If the user is configured to don't require any password, we
|
||||
* are already fine here. */
|
||||
if (u->flags & USER_FLAG_NOPASS) return C_OK;
|
||||
|
||||
/* Check all the user passwords for at least one to match. */
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(u->passwords,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = listNodeValue(ln);
|
||||
if (!time_independent_strcmp(password->ptr, thispass))
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* If we reached this point, no password matched. */
|
||||
errno = EINVAL;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* For ACL purposes, every user has a bitmap with the commands that such
|
||||
* user is allowed to execute. In order to populate the bitmap, every command
|
||||
* should have an assigned ID (that is used to index the bitmap). This function
|
||||
* creates such an ID: it uses sequential IDs, reusing the same ID for the same
|
||||
* command name, so that a command retains the same ID in case of modules that
|
||||
* are unloaded and later reloaded. */
|
||||
unsigned long ACLGetCommandID(const char *cmdname) {
|
||||
static rax *map = NULL;
|
||||
static unsigned long nextid = 0;
|
||||
|
||||
if (map == NULL) map = raxNew();
|
||||
void *id = raxFind(map,(unsigned char*)cmdname,strlen(cmdname));
|
||||
if (id != raxNotFound) return (unsigned long)id;
|
||||
raxInsert(map,(unsigned char*)cmdname,strlen(cmdname),(void*)nextid,NULL);
|
||||
return nextid++;
|
||||
}
|
||||
|
||||
/* Return an username by its name, or NULL if the user does not exist. */
|
||||
user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
void *myuser = raxFind(Users,(unsigned char*)name,namelen);
|
||||
if (myuser == raxNotFound) return NULL;
|
||||
return myuser;
|
||||
}
|
||||
|
||||
/* Check if the command ready to be excuted in the client 'c', and already
|
||||
* referenced by c->cmd, can be executed by this client according to the
|
||||
* ACls associated to the client user c->user.
|
||||
*
|
||||
* If the user can execute the command ACL_OK is returned, otherwise
|
||||
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
|
||||
* command cannot be executed because the user is not allowed to run such
|
||||
* command, the second if the command is denied because the user is trying
|
||||
* to access keys that are not among the specified patterns. */
|
||||
int ACLCheckCommandPerm(client *c) {
|
||||
user *u = c->user;
|
||||
uint64_t id = c->cmd->id;
|
||||
|
||||
/* If there is no associated user, the connection can run anything. */
|
||||
if (u == NULL) return ACL_OK;
|
||||
|
||||
/* We have to deny every command with an ID that overflows the Redis
|
||||
* internal structures. Very unlikely to happen. */
|
||||
if (c->cmd->id >= USER_MAX_COMMAND_BIT) return ACL_DENIED_CMD;
|
||||
|
||||
/* Check if the user can execute this command. */
|
||||
if (!(u->flags & USER_FLAG_ALLCOMMANDS) &&
|
||||
c->cmd->proc != authCommand)
|
||||
{
|
||||
uint64_t wordid = id / sizeof(u->allowed_commands[0]) / 8;
|
||||
uint64_t bit = 1 << (id % (sizeof(u->allowed_commands[0] * 8)));
|
||||
/* If the bit is not set we have to check further, in case the
|
||||
* command is allowed just with that specific subcommand. */
|
||||
if (!(u->allowed_commands[wordid] & bit)) {
|
||||
/* Check if the subcommand matches. */
|
||||
if (u->allowed_subcommands == NULL || c->argc < 2)
|
||||
return ACL_DENIED_CMD;
|
||||
long subid = 0;
|
||||
while (1) {
|
||||
if (u->allowed_subcommands[id][subid] == NULL)
|
||||
return ACL_DENIED_CMD;
|
||||
if (!strcasecmp(c->argv[1]->ptr,
|
||||
u->allowed_subcommands[id][subid]))
|
||||
break; /* Subcommand match found. Stop here. */
|
||||
subid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the user can execute commands explicitly touching the keys
|
||||
* mentioned in the command arguments. */
|
||||
if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
|
||||
(c->cmd->getkeys_proc || c->cmd->firstkey))
|
||||
{
|
||||
int numkeys;
|
||||
int *keyidx = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
|
||||
for (int j = 0; j < numkeys; j++) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(u->passwords,&li);
|
||||
|
||||
/* Test this key against every pattern. */
|
||||
int match = 0;
|
||||
while((ln = listNext(&li))) {
|
||||
sds pattern = listNodeValue(ln);
|
||||
size_t plen = sdslen(pattern);
|
||||
int idx = keyidx[j];
|
||||
if (stringmatchlen(pattern,plen,c->argv[idx]->ptr,
|
||||
sdslen(c->argv[idx]->ptr),0))
|
||||
{
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) return ACL_DENIED_KEY;
|
||||
}
|
||||
getKeysFreeResult(keyidx);
|
||||
}
|
||||
|
||||
/* If we survived all the above checks, the user can execute the
|
||||
* command. */
|
||||
return ACL_OK;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL related commands
|
||||
* ==========================================================================*/
|
||||
|
||||
/* ACL -- show and modify the configuration of ACL users.
|
||||
* ACL HELP
|
||||
* ACL LIST
|
||||
* ACL SETUSER <username> ... user attribs ...
|
||||
* ACL DELUSER <username>
|
||||
* ACL GETUSER <username>
|
||||
*/
|
||||
void aclCommand(client *c) {
|
||||
char *sub = c->argv[1]->ptr;
|
||||
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
||||
sds username = c->argv[2]->ptr;
|
||||
user *u = ACLGetUserByName(username,sdslen(username));
|
||||
if (!u) u = ACLCreateUser(username,sdslen(username));
|
||||
serverAssert(u != NULL);
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
if (ACLSetUser(u,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
|
||||
addReplyErrorFormat(c,
|
||||
"Syntax error in ACL SETUSER modifier '%s'",
|
||||
c->argv[j]->ptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(sub,"whoami")) {
|
||||
if (c->user != NULL) {
|
||||
addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
}
|
||||
} else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
|
||||
user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
addReplyMapLen(c,2);
|
||||
|
||||
/* Flags */
|
||||
addReplyBulkCString(c,"flags");
|
||||
void *deflen = addReplyDeferredLen(c);
|
||||
int numflags = 0;
|
||||
if (u->flags & USER_FLAG_ENABLED) {
|
||||
addReplyBulkCString(c,"on");
|
||||
numflags++;
|
||||
} else {
|
||||
addReplyBulkCString(c,"off");
|
||||
numflags++;
|
||||
}
|
||||
if (u->flags & USER_FLAG_ALLKEYS) {
|
||||
addReplyBulkCString(c,"allkeys");
|
||||
numflags++;
|
||||
}
|
||||
if (u->flags & USER_FLAG_ALLCOMMANDS) {
|
||||
addReplyBulkCString(c,"allcommnads");
|
||||
numflags++;
|
||||
}
|
||||
if (u->flags & USER_FLAG_NOPASS) {
|
||||
addReplyBulkCString(c,"nopass");
|
||||
numflags++;
|
||||
}
|
||||
setDeferredSetLen(c,deflen,numflags);
|
||||
|
||||
/* Passwords */
|
||||
addReplyBulkCString(c,"passwords");
|
||||
addReplyArrayLen(c,listLength(u->passwords));
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(u->passwords,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = listNodeValue(ln);
|
||||
addReplyBulkCBuffer(c,thispass,sdslen(thispass));
|
||||
}
|
||||
} else if (!strcasecmp(sub,"help")) {
|
||||
const char *help[] = {
|
||||
"LIST -- List all the registered users.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"DELUSER <username> -- Delete a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"WHOAMI -- Return the current username.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
2
src/ae.c
2
src/ae.c
@ -351,8 +351,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
* if flags has AE_FILE_EVENTS set, file events are processed.
|
||||
* if flags has AE_TIME_EVENTS set, time events are processed.
|
||||
* if flags has AE_DONT_WAIT set the function returns ASAP until all
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
* the events that's possible to process without to wait are processed.
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
*
|
||||
* The function returns the number of events processed. */
|
||||
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
|
@ -222,6 +222,7 @@ static void killAppendOnlyChild(void) {
|
||||
/* Close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
@ -646,6 +647,8 @@ struct client *createFakeClient(void) {
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
c->resp = 2;
|
||||
c->user = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
|
@ -1002,7 +1002,7 @@ void bitfieldCommand(client *c) {
|
||||
highest_write_offset)) == NULL) return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,numops);
|
||||
addReplyArrayLen(c,numops);
|
||||
|
||||
/* Actually process the operations. */
|
||||
for (j = 0; j < numops; j++) {
|
||||
@ -1047,7 +1047,7 @@ void bitfieldCommand(client *c) {
|
||||
setSignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
} else {
|
||||
uint64_t oldval, newval, wrapped, retval;
|
||||
@ -1076,7 +1076,7 @@ void bitfieldCommand(client *c) {
|
||||
setUnsignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
}
|
||||
changes++;
|
||||
|
@ -187,7 +187,7 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
@ -436,8 +436,12 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
* array, since we have just one key. */
|
||||
addReplyMultiBulkLen(receiver,1);
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
if (receiver->resp == 2) {
|
||||
addReplyArrayLen(receiver,1);
|
||||
addReplyArrayLen(receiver,2);
|
||||
} else {
|
||||
addReplyMapLen(receiver,1);
|
||||
}
|
||||
addReplyBulk(receiver,rl->key);
|
||||
|
||||
streamPropInfo pi = {
|
||||
|
@ -4126,7 +4126,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
*/
|
||||
|
||||
int num_masters = 0;
|
||||
void *slot_replylen = addDeferredMultiBulkLength(c);
|
||||
void *slot_replylen = addReplyDeferredLen(c);
|
||||
|
||||
dictEntry *de;
|
||||
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
|
||||
@ -4146,7 +4146,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addDeferredMultiBulkLength(c);
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
@ -4162,7 +4162,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
@ -4172,19 +4172,19 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c, slot_replylen, num_masters);
|
||||
setDeferredArrayLen(c, slot_replylen, num_masters);
|
||||
}
|
||||
|
||||
void clusterCommand(client *c) {
|
||||
@ -4548,7 +4548,7 @@ NULL
|
||||
|
||||
keys = zmalloc(sizeof(robj*)*maxkeys);
|
||||
numkeys = getKeysInSlot(slot, keys, maxkeys);
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
addReplyArrayLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
addReplyBulk(c,keys[j]);
|
||||
decrRefCount(keys[j]);
|
||||
@ -4627,7 +4627,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,n->numslaves);
|
||||
addReplyArrayLen(c,n->numslaves);
|
||||
for (j = 0; j < n->numslaves; j++) {
|
||||
sds ni = clusterGenNodeDescription(n->slaves[j]);
|
||||
addReplyBulkCString(c,ni);
|
||||
@ -4836,7 +4836,7 @@ void dumpCommand(client *c) {
|
||||
|
||||
/* Check if the key is here. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
|
52
src/config.c
52
src/config.c
@ -531,7 +531,12 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||
goto loaderr;
|
||||
}
|
||||
server.requirepass = argv[1][0] ? zstrdup(argv[1]) : NULL;
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
|
||||
zfree(server.pidfile);
|
||||
server.pidfile = zstrdup(argv[1]);
|
||||
@ -919,8 +924,12 @@ void configSetCommand(client *c) {
|
||||
server.rdb_filename = zstrdup(o->ptr);
|
||||
} config_set_special_field("requirepass") {
|
||||
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
|
||||
zfree(server.requirepass);
|
||||
server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",o->ptr);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
} config_set_special_field("masterauth") {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
@ -1324,7 +1333,7 @@ badfmt: /* Bad format errors */
|
||||
|
||||
void configGetCommand(client *c) {
|
||||
robj *o = c->argv[2];
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
char *pattern = o->ptr;
|
||||
char buf[128];
|
||||
int matches = 0;
|
||||
@ -1332,7 +1341,6 @@ void configGetCommand(client *c) {
|
||||
|
||||
/* String values */
|
||||
config_get_string_field("dbfilename",server.rdb_filename);
|
||||
config_get_string_field("requirepass",server.requirepass);
|
||||
config_get_string_field("masterauth",server.masterauth);
|
||||
config_get_string_field("cluster-announce-ip",server.cluster_announce_ip);
|
||||
config_get_string_field("unixsocket",server.unixsocket);
|
||||
@ -1571,7 +1579,17 @@ void configGetCommand(client *c) {
|
||||
sdsfree(aux);
|
||||
matches++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,matches*2);
|
||||
if (stringmatch(pattern,"requirepass",1)) {
|
||||
addReplyBulkCString(c,"requirepass");
|
||||
sds password = ACLDefaultUserFirstPassword();
|
||||
if (password) {
|
||||
addReplyBulkCBuffer(c,password,sdslen(password));
|
||||
} else {
|
||||
addReplyBulkCString(c,"");
|
||||
}
|
||||
matches++;
|
||||
}
|
||||
setDeferredMapLen(c,replylen,matches);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@ -1981,6 +1999,26 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the requirepass option. */
|
||||
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
|
||||
int force = 1;
|
||||
sds line;
|
||||
sds password = ACLDefaultUserFirstPassword();
|
||||
|
||||
/* If there is no password set, we don't want the requirepass option
|
||||
* to be present in the configuration at all. */
|
||||
if (password == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
|
||||
line = sdsnew(option);
|
||||
line = sdscatlen(line, " ", 1);
|
||||
line = sdscatsds(line, password);
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Glue together the configuration lines in the current configuration
|
||||
* rewrite state into a single string, stripping multiple empty lines. */
|
||||
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
||||
@ -2161,7 +2199,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"replica-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigNumericalOption(state,"min-replicas-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
rewriteConfigNumericalOption(state,"min-replicas-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG);
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigRequirepassOption(state,"requirepass");
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
|
||||
rewriteConfigBytesOption(state,"proto-max-bulk-len",server.proto_max_bulk_len,CONFIG_DEFAULT_PROTO_MAX_BULK_LEN);
|
||||
|
11
src/db.c
11
src/db.c
@ -452,6 +452,7 @@ void flushallCommand(client *c) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
if (server.saveparamslen > 0) {
|
||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||
@ -526,7 +527,7 @@ void randomkeyCommand(client *c) {
|
||||
robj *key;
|
||||
|
||||
if ((key = dbRandomKey(c->db)) == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -540,7 +541,7 @@ void keysCommand(client *c) {
|
||||
sds pattern = c->argv[1]->ptr;
|
||||
int plen = sdslen(pattern), allkeys;
|
||||
unsigned long numkeys = 0;
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
@ -558,7 +559,7 @@ void keysCommand(client *c) {
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c,replylen,numkeys);
|
||||
setDeferredArrayLen(c,replylen,numkeys);
|
||||
}
|
||||
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
@ -783,10 +784,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
}
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyBulkLongLong(c,cursor);
|
||||
|
||||
addReplyMultiBulkLen(c, listLength(keys));
|
||||
addReplyArrayLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
addReplyBulk(c, kobj);
|
||||
|
54
src/debug.c
54
src/debug.c
@ -517,7 +517,7 @@ NULL
|
||||
sdsfree(d);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
|
||||
/* DEBUG DIGEST-VALUE key key key ... key. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
unsigned char digest[20];
|
||||
memset(digest,0,20); /* Start with a clean result */
|
||||
@ -529,6 +529,58 @@ NULL
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"protocol") && c->argc == 3) {
|
||||
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
|
||||
* attrib|push|verbatim|true|false|state|err|bloberr] */
|
||||
char *name = c->argv[2]->ptr;
|
||||
if (!strcasecmp(name,"string")) {
|
||||
addReplyBulkCString(c,"Hello World");
|
||||
} else if (!strcasecmp(name,"integer")) {
|
||||
addReplyLongLong(c,12345);
|
||||
} else if (!strcasecmp(name,"double")) {
|
||||
addReplyDouble(c,3.14159265359);
|
||||
} else if (!strcasecmp(name,"bignum")) {
|
||||
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
|
||||
} else if (!strcasecmp(name,"null")) {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(name,"array")) {
|
||||
addReplyArrayLen(c,3);
|
||||
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
|
||||
} else if (!strcasecmp(name,"set")) {
|
||||
addReplySetLen(c,3);
|
||||
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
|
||||
} else if (!strcasecmp(name,"map")) {
|
||||
addReplyMapLen(c,3);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
addReplyLongLong(c,j);
|
||||
addReplyBool(c, j == 1);
|
||||
}
|
||||
} else if (!strcasecmp(name,"attrib")) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
/* Attributes are not real replies, so a well formed reply should
|
||||
* also have a normal reply type after the attribute. */
|
||||
addReplyBulkCString(c,"Some real reply following the attribute");
|
||||
} else if (!strcasecmp(name,"push")) {
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCString(c,"server-cpu-usage");
|
||||
addReplyLongLong(c,42);
|
||||
/* Push replies are not synchronous replies, so we emit also a
|
||||
* normal reply in order for blocking clients just discarding the
|
||||
* push reply, to actually consume the reply and continue. */
|
||||
addReplyBulkCString(c,"Some real reply following the push reply");
|
||||
} else if (!strcasecmp(name,"true")) {
|
||||
addReplyBool(c,1);
|
||||
} else if (!strcasecmp(name,"false")) {
|
||||
addReplyBool(c,0);
|
||||
} else if (!strcasecmp(name,"verbatim")) {
|
||||
addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
|
||||
} else {
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
long long utime = dtime*1000000;
|
||||
|
32
src/geo.c
32
src/geo.c
@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* If no matching results, the user gets an empty reply. */
|
||||
if (ga->used == 0 && storekey == NULL) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
geoArrayFree(ga);
|
||||
return;
|
||||
}
|
||||
@ -597,11 +597,11 @@ void georadiusGeneric(client *c, int flags) {
|
||||
if (withhash)
|
||||
option_length++;
|
||||
|
||||
/* The multibulk len we send is exactly result_length. The result is
|
||||
/* The array len we send is exactly result_length. The result is
|
||||
* either all strings of just zset members *or* a nested multi-bulk
|
||||
* reply containing the zset member string _and_ all the additional
|
||||
* options the user enabled for this request. */
|
||||
addReplyMultiBulkLen(c, returned_items);
|
||||
addReplyArrayLen(c, returned_items);
|
||||
|
||||
/* Finally send results back to the caller */
|
||||
int i;
|
||||
@ -613,7 +613,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
* as a nested multi-bulk. Add 1 to account for result value
|
||||
* itself. */
|
||||
if (option_length)
|
||||
addReplyMultiBulkLen(c, option_length + 1);
|
||||
addReplyArrayLen(c, option_length + 1);
|
||||
|
||||
addReplyBulkSds(c,gp->member);
|
||||
gp->member = NULL;
|
||||
@ -625,7 +625,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
addReplyLongLong(c, gp->score);
|
||||
|
||||
if (withcoords) {
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyHumanLongDouble(c, gp->longitude);
|
||||
addReplyHumanLongDouble(c, gp->latitude);
|
||||
}
|
||||
@ -706,11 +706,11 @@ void geohashCommand(client *c) {
|
||||
|
||||
/* Geohash elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
/* The internal format we use for geocoding is a bit different
|
||||
* than the standard, since we use as initial latitude range
|
||||
@ -721,7 +721,7 @@ void geohashCommand(client *c) {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -759,19 +759,19 @@ void geoposCommand(client *c) {
|
||||
|
||||
/* Report elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
continue;
|
||||
}
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyHumanLongDouble(c,xy[0]);
|
||||
addReplyHumanLongDouble(c,xy[1]);
|
||||
}
|
||||
@ -797,7 +797,7 @@ void geodistCommand(client *c) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.nullbulk))
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Get the scores. We need both otherwise NULL is returned. */
|
||||
@ -805,13 +805,13 @@ void geodistCommand(client *c) {
|
||||
if (zsetScore(zobj, c->argv[2]->ptr, &score1) == C_ERR ||
|
||||
zsetScore(zobj, c->argv[3]->ptr, &score2) == C_ERR)
|
||||
{
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decode & compute the distance. */
|
||||
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
else
|
||||
addReplyDoubleDistance(c,
|
||||
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
|
||||
|
@ -1512,7 +1512,7 @@ void pfdebugCommand(client *c) {
|
||||
}
|
||||
|
||||
hdr = o->ptr;
|
||||
addReplyMultiBulkLen(c,HLL_REGISTERS);
|
||||
addReplyArrayLen(c,HLL_REGISTERS);
|
||||
for (j = 0; j < HLL_REGISTERS; j++) {
|
||||
uint8_t val;
|
||||
|
||||
|
@ -476,19 +476,19 @@ sds createLatencyReport(void) {
|
||||
/* latencyCommand() helper to produce a time-delay reply for all the samples
|
||||
* in memory for the specified time series. */
|
||||
void latencyCommandReplyWithSamples(client *c, struct latencyTimeSeries *ts) {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int samples = 0, j;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,ts->samples[i].time);
|
||||
addReplyLongLong(c,ts->samples[i].latency);
|
||||
samples++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,samples);
|
||||
setDeferredArrayLen(c,replylen,samples);
|
||||
}
|
||||
|
||||
/* latencyCommand() helper to produce the reply for the LATEST subcommand,
|
||||
@ -497,14 +497,14 @@ void latencyCommandReplyWithLatestEvents(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,dictSize(server.latency_events));
|
||||
addReplyArrayLen(c,dictSize(server.latency_events));
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyArrayLen(c,4);
|
||||
addReplyBulkCString(c,event);
|
||||
addReplyLongLong(c,ts->samples[last].time);
|
||||
addReplyLongLong(c,ts->samples[last].latency);
|
||||
@ -583,7 +583,7 @@ NULL
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
|
||||
if (ts == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
addReplyArrayLen(c,0);
|
||||
} else {
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
}
|
||||
|
46
src/module.c
46
src/module.c
@ -1123,10 +1123,10 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
|
||||
ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)*
|
||||
(ctx->postponed_arrays_count+1));
|
||||
ctx->postponed_arrays[ctx->postponed_arrays_count] =
|
||||
addDeferredMultiBulkLength(c);
|
||||
addReplyDeferredLen(c);
|
||||
ctx->postponed_arrays_count++;
|
||||
} else {
|
||||
addReplyMultiBulkLen(c,len);
|
||||
addReplyArrayLen(c,len);
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@ -1169,7 +1169,7 @@ void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
|
||||
return;
|
||||
}
|
||||
ctx->postponed_arrays_count--;
|
||||
setDeferredMultiBulkLength(c,
|
||||
setDeferredArrayLen(c,
|
||||
ctx->postponed_arrays[ctx->postponed_arrays_count],
|
||||
len);
|
||||
if (ctx->postponed_arrays_count == 0) {
|
||||
@ -1205,7 +1205,7 @@ int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) {
|
||||
int RM_ReplyWithNull(RedisModuleCtx *ctx) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@ -3669,8 +3669,8 @@ void moduleHandleBlockedClients(void) {
|
||||
* free the temporary client we just used for the replies. */
|
||||
if (c) {
|
||||
if (bc->reply_client->bufpos)
|
||||
addReplyString(c,bc->reply_client->buf,
|
||||
bc->reply_client->bufpos);
|
||||
addReplyProto(c,bc->reply_client->buf,
|
||||
bc->reply_client->bufpos);
|
||||
if (listLength(bc->reply_client->reply))
|
||||
listJoin(c->reply,bc->reply_client->reply);
|
||||
c->reply_bytes += bc->reply_client->reply_bytes;
|
||||
@ -4818,6 +4818,25 @@ int moduleUnload(sds name) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Helper function for the MODULE and HELLO command: send the list of the
|
||||
* loaded modules to the client. */
|
||||
void addReplyLoadedModules(client *c) {
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
|
||||
addReplyArrayLen(c,dictSize(modules));
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sds name = dictGetKey(de);
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
addReplyMapLen(c,2);
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCBuffer(c,name,sdslen(name));
|
||||
addReplyBulkCString(c,"ver");
|
||||
addReplyLongLong(c,module->ver);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Redis MODULE command.
|
||||
*
|
||||
* MODULE LOAD <path> [args...] */
|
||||
@ -4865,20 +4884,7 @@ NULL
|
||||
addReplyErrorFormat(c,"Error unloading module: %s",errmsg);
|
||||
}
|
||||
} else if (!strcasecmp(subcmd,"list") && c->argc == 2) {
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,dictSize(modules));
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sds name = dictGetKey(de);
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCBuffer(c,name,sdslen(name));
|
||||
addReplyBulkCString(c,"ver");
|
||||
addReplyLongLong(c,module->ver);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
addReplyLoadedModules(c);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
|
@ -134,7 +134,7 @@ void execCommand(client *c) {
|
||||
* in the second an EXECABORT error is returned. */
|
||||
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullmultibulk);
|
||||
shared.nullarray[c->resp]);
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
@ -159,7 +159,7 @@ void execCommand(client *c) {
|
||||
orig_argv = c->argv;
|
||||
orig_argc = c->argc;
|
||||
orig_cmd = c->cmd;
|
||||
addReplyMultiBulkLen(c,c->mstate.count);
|
||||
addReplyArrayLen(c,c->mstate.count);
|
||||
for (j = 0; j < c->mstate.count; j++) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
|
267
src/networking.c
267
src/networking.c
@ -107,6 +107,7 @@ client *createClient(int fd) {
|
||||
uint64_t client_id;
|
||||
atomicGetIncr(server.next_client_id,client_id,1);
|
||||
c->id = client_id;
|
||||
c->resp = 2;
|
||||
c->fd = fd;
|
||||
c->name = NULL;
|
||||
c->bufpos = 0;
|
||||
@ -118,12 +119,15 @@ client *createClient(int fd) {
|
||||
c->argc = 0;
|
||||
c->argv = NULL;
|
||||
c->cmd = c->lastcmd = NULL;
|
||||
c->user = DefaultUser;
|
||||
c->multibulklen = 0;
|
||||
c->bulklen = -1;
|
||||
c->sentlen = 0;
|
||||
c->flags = 0;
|
||||
c->ctime = c->lastinteraction = server.unixtime;
|
||||
c->authenticated = 0;
|
||||
/* If the default user does not require authentication, the user is
|
||||
* directly authenticated. */
|
||||
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) != 0;
|
||||
c->replstate = REPL_STATE_NONE;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
c->reploff = 0;
|
||||
@ -252,7 +256,7 @@ int _addReplyToBuffer(client *c, const char *s, size_t len) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
void _addReplyStringToList(client *c, const char *s, size_t len) {
|
||||
void _addReplyProtoToList(client *c, const char *s, size_t len) {
|
||||
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
|
||||
|
||||
listNode *ln = listLast(c->reply);
|
||||
@ -299,7 +303,7 @@ void addReply(client *c, robj *obj) {
|
||||
|
||||
if (sdsEncodedObject(obj)) {
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
|
||||
_addReplyStringToList(c,obj->ptr,sdslen(obj->ptr));
|
||||
_addReplyProtoToList(c,obj->ptr,sdslen(obj->ptr));
|
||||
} else if (obj->encoding == OBJ_ENCODING_INT) {
|
||||
/* For integer encoded strings we just convert it into a string
|
||||
* using our optimized function, and attach the resulting string
|
||||
@ -307,7 +311,7 @@ void addReply(client *c, robj *obj) {
|
||||
char buf[32];
|
||||
size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
|
||||
if (_addReplyToBuffer(c,buf,len) != C_OK)
|
||||
_addReplyStringToList(c,buf,len);
|
||||
_addReplyProtoToList(c,buf,len);
|
||||
} else {
|
||||
serverPanic("Wrong obj->encoding in addReply()");
|
||||
}
|
||||
@ -322,7 +326,7 @@ void addReplySds(client *c, sds s) {
|
||||
return;
|
||||
}
|
||||
if (_addReplyToBuffer(c,s,sdslen(s)) != C_OK)
|
||||
_addReplyStringToList(c,s,sdslen(s));
|
||||
_addReplyProtoToList(c,s,sdslen(s));
|
||||
sdsfree(s);
|
||||
}
|
||||
|
||||
@ -332,12 +336,12 @@ void addReplySds(client *c, sds s) {
|
||||
*
|
||||
* It is efficient because does not create an SDS object nor an Redis object
|
||||
* if not needed. The object will only be created by calling
|
||||
* _addReplyStringToList() if we fail to extend the existing tail object
|
||||
* _addReplyProtoToList() if we fail to extend the existing tail object
|
||||
* in the list of objects. */
|
||||
void addReplyString(client *c, const char *s, size_t len) {
|
||||
void addReplyProto(client *c, const char *s, size_t len) {
|
||||
if (prepareClientToWrite(c) != C_OK) return;
|
||||
if (_addReplyToBuffer(c,s,len) != C_OK)
|
||||
_addReplyStringToList(c,s,len);
|
||||
_addReplyProtoToList(c,s,len);
|
||||
}
|
||||
|
||||
/* Low level function called by the addReplyError...() functions.
|
||||
@ -351,9 +355,9 @@ void addReplyString(client *c, const char *s, size_t len) {
|
||||
void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
/* If the string already starts with "-..." then the error code
|
||||
* is provided by the caller. Otherwise we use "-ERR". */
|
||||
if (!len || s[0] != '-') addReplyString(c,"-ERR ",5);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
if (!len || s[0] != '-') addReplyProto(c,"-ERR ",5);
|
||||
addReplyProto(c,s,len);
|
||||
addReplyProto(c,"\r\n",2);
|
||||
|
||||
/* Sometimes it could be normal that a slave replies to a master with
|
||||
* an error and this function gets called. Actually the error will never
|
||||
@ -396,9 +400,9 @@ void addReplyErrorFormat(client *c, const char *fmt, ...) {
|
||||
}
|
||||
|
||||
void addReplyStatusLength(client *c, const char *s, size_t len) {
|
||||
addReplyString(c,"+",1);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
addReplyProto(c,"+",1);
|
||||
addReplyProto(c,s,len);
|
||||
addReplyProto(c,"\r\n",2);
|
||||
}
|
||||
|
||||
void addReplyStatus(client *c, const char *status) {
|
||||
@ -416,28 +420,28 @@ void addReplyStatusFormat(client *c, const char *fmt, ...) {
|
||||
|
||||
/* Adds an empty object to the reply list that will contain the multi bulk
|
||||
* length, which is not known when this function is called. */
|
||||
void *addDeferredMultiBulkLength(client *c) {
|
||||
void *addReplyDeferredLen(client *c) {
|
||||
/* Note that we install the write event here even if the object is not
|
||||
* ready to be sent, since we are sure that before returning to the
|
||||
* event loop setDeferredMultiBulkLength() will be called. */
|
||||
* event loop setDeferredAggregateLen() will be called. */
|
||||
if (prepareClientToWrite(c) != C_OK) return NULL;
|
||||
listAddNodeTail(c->reply,NULL); /* NULL is our placeholder. */
|
||||
return listLast(c->reply);
|
||||
}
|
||||
|
||||
/* Populate the length object and try gluing it to the next chunk. */
|
||||
void setDeferredMultiBulkLength(client *c, void *node, long length) {
|
||||
void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
|
||||
listNode *ln = (listNode*)node;
|
||||
clientReplyBlock *next;
|
||||
char lenstr[128];
|
||||
size_t lenstr_len = sprintf(lenstr, "*%ld\r\n", length);
|
||||
size_t lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
|
||||
|
||||
/* Abort when *node is NULL: when the client should not accept writes
|
||||
* we return NULL in addDeferredMultiBulkLength() */
|
||||
* we return NULL in addReplyDeferredLen() */
|
||||
if (node == NULL) return;
|
||||
serverAssert(!listNodeValue(ln));
|
||||
|
||||
/* Normally we fill this dummy NULL node, added by addDeferredMultiBulkLength(),
|
||||
/* Normally we fill this dummy NULL node, added by addReplyDeferredLen(),
|
||||
* with a new buffer structure containing the protocol needed to specify
|
||||
* the length of the array following. However sometimes when there is
|
||||
* little memory to move, we may instead remove this NULL node, and prefix
|
||||
@ -467,18 +471,55 @@ void setDeferredMultiBulkLength(client *c, void *node, long length) {
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
|
||||
void setDeferredArrayLen(client *c, void *node, long length) {
|
||||
setDeferredAggregateLen(c,node,length,'*');
|
||||
}
|
||||
|
||||
void setDeferredMapLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '%';
|
||||
if (c->resp == 2) length *= 2;
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
}
|
||||
|
||||
void setDeferredSetLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '~';
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
}
|
||||
|
||||
void setDeferredAttributeLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '|';
|
||||
if (c->resp == 2) length *= 2;
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
}
|
||||
|
||||
void setDeferredPushLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '>';
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
}
|
||||
|
||||
/* Add a double as a bulk reply */
|
||||
void addReplyDouble(client *c, double d) {
|
||||
char dbuf[128], sbuf[128];
|
||||
int dlen, slen;
|
||||
if (isinf(d)) {
|
||||
/* Libc in odd systems (Hi Solaris!) will format infinite in a
|
||||
* different way, so better to handle it in an explicit way. */
|
||||
addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
|
||||
if (c->resp == 2) {
|
||||
addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
|
||||
} else {
|
||||
addReplyProto(c, d > 0 ? ",inf\r\n" : "-inf\r\n",
|
||||
d > 0 ? 6 : 7);
|
||||
}
|
||||
} else {
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
|
||||
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
|
||||
addReplyString(c,sbuf,slen);
|
||||
char dbuf[MAX_LONG_DOUBLE_CHARS+3],
|
||||
sbuf[MAX_LONG_DOUBLE_CHARS+32];
|
||||
int dlen, slen;
|
||||
if (c->resp == 2) {
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
|
||||
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
|
||||
addReplyProto(c,sbuf,slen);
|
||||
} else {
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d);
|
||||
addReplyProto(c,dbuf,dlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,9 +527,17 @@ void addReplyDouble(client *c, double d) {
|
||||
* of the double instead of exposing the crude behavior of doubles to the
|
||||
* dear user. */
|
||||
void addReplyHumanLongDouble(client *c, long double d) {
|
||||
robj *o = createStringObjectFromLongDouble(d,1);
|
||||
addReplyBulk(c,o);
|
||||
decrRefCount(o);
|
||||
if (c->resp == 2) {
|
||||
robj *o = createStringObjectFromLongDouble(d,1);
|
||||
addReplyBulk(c,o);
|
||||
decrRefCount(o);
|
||||
} else {
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf,sizeof(buf),d,1);
|
||||
addReplyProto(c,",",1);
|
||||
addReplyProto(c,buf,len);
|
||||
addReplyProto(c,"\r\n",2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a long long as integer reply or bulk len / multi bulk count.
|
||||
@ -512,7 +561,7 @@ void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
|
||||
len = ll2string(buf+1,sizeof(buf)-1,ll);
|
||||
buf[len+1] = '\r';
|
||||
buf[len+2] = '\n';
|
||||
addReplyString(c,buf,len+3);
|
||||
addReplyProto(c,buf,len+3);
|
||||
}
|
||||
|
||||
void addReplyLongLong(client *c, long long ll) {
|
||||
@ -524,11 +573,65 @@ void addReplyLongLong(client *c, long long ll) {
|
||||
addReplyLongLongWithPrefix(c,ll,':');
|
||||
}
|
||||
|
||||
void addReplyMultiBulkLen(client *c, long length) {
|
||||
if (length < OBJ_SHARED_BULKHDR_LEN)
|
||||
void addReplyAggregateLen(client *c, long length, int prefix) {
|
||||
if (prefix == '*' && length < OBJ_SHARED_BULKHDR_LEN)
|
||||
addReply(c,shared.mbulkhdr[length]);
|
||||
else
|
||||
addReplyLongLongWithPrefix(c,length,'*');
|
||||
addReplyLongLongWithPrefix(c,length,prefix);
|
||||
}
|
||||
|
||||
void addReplyArrayLen(client *c, long length) {
|
||||
addReplyAggregateLen(c,length,'*');
|
||||
}
|
||||
|
||||
void addReplyMapLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '%';
|
||||
if (c->resp == 2) length *= 2;
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
}
|
||||
|
||||
void addReplySetLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '~';
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
}
|
||||
|
||||
void addReplyAttributeLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '|';
|
||||
if (c->resp == 2) length *= 2;
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
}
|
||||
|
||||
void addReplyPushLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '>';
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
}
|
||||
|
||||
void addReplyNull(client *c) {
|
||||
if (c->resp == 2) {
|
||||
addReplyProto(c,"$-1\r\n",5);
|
||||
} else {
|
||||
addReplyProto(c,"_\r\n",3);
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyBool(client *c, int b) {
|
||||
if (c->resp == 2) {
|
||||
addReply(c, b ? shared.cone : shared.czero);
|
||||
} else {
|
||||
addReplyProto(c, b ? "#t\r\n" : "#f\r\n",4);
|
||||
}
|
||||
}
|
||||
|
||||
/* A null array is a concept that no longer exists in RESP3. However
|
||||
* RESP2 had it, so API-wise we have this call, that will emit the correct
|
||||
* RESP2 protocol, however for RESP3 the reply will always be just the
|
||||
* Null type "_\r\n". */
|
||||
void addReplyNullArray(client *c) {
|
||||
if (c->resp == 2) {
|
||||
addReplyProto(c,"*-1\r\n",5);
|
||||
} else {
|
||||
addReplyProto(c,"_\r\n",3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the length prefix of a bulk reply, example: $2234 */
|
||||
@ -567,7 +670,7 @@ void addReplyBulk(client *c, robj *obj) {
|
||||
/* Add a C buffer as bulk reply */
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len) {
|
||||
addReplyLongLongWithPrefix(c,len,'$');
|
||||
addReplyString(c,p,len);
|
||||
addReplyProto(c,p,len);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
@ -581,7 +684,7 @@ void addReplyBulkSds(client *c, sds s) {
|
||||
/* Add a C null term string as bulk reply */
|
||||
void addReplyBulkCString(client *c, const char *s) {
|
||||
if (s == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,s,strlen(s));
|
||||
}
|
||||
@ -596,13 +699,42 @@ void addReplyBulkLongLong(client *c, long long ll) {
|
||||
addReplyBulkCBuffer(c,buf,len);
|
||||
}
|
||||
|
||||
/* Reply with a verbatim type having the specified extension.
|
||||
*
|
||||
* The 'ext' is the "extension" of the file, actually just a three
|
||||
* character type that describes the format of the verbatim string.
|
||||
* For instance "txt" means it should be interpreted as a text only
|
||||
* file by the receiver, "md " as markdown, and so forth. Only the
|
||||
* three first characters of the extension are used, and if the
|
||||
* provided one is shorter than that, the remaining is filled with
|
||||
* spaces. */
|
||||
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext) {
|
||||
if (c->resp == 2) {
|
||||
addReplyBulkCBuffer(c,s,len);
|
||||
} else {
|
||||
char buf[32];
|
||||
size_t preflen = snprintf(buf,sizeof(buf),"=%zu\r\nxxx:",len+4);
|
||||
char *p = buf+preflen-4;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (*ext == '\0') {
|
||||
p[i] = ' ';
|
||||
} else {
|
||||
p[i] = *ext++;
|
||||
}
|
||||
}
|
||||
addReplyProto(c,buf,preflen);
|
||||
addReplyProto(c,s,len);
|
||||
addReplyProto(c,"\r\n",2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add an array of C strings as status replies with a heading.
|
||||
* This function is typically invoked by from commands that support
|
||||
* subcommands in response to the 'help' subcommand. The help array
|
||||
* is terminated by NULL sentinel. */
|
||||
void addReplyHelp(client *c, const char **help) {
|
||||
sds cmd = sdsnew((char*) c->argv[0]->ptr);
|
||||
void *blenp = addDeferredMultiBulkLength(c);
|
||||
void *blenp = addReplyDeferredLen(c);
|
||||
int blen = 0;
|
||||
|
||||
sdstoupper(cmd);
|
||||
@ -613,7 +745,7 @@ void addReplyHelp(client *c, const char **help) {
|
||||
while (help[blen]) addReplyStatus(c,help[blen++]);
|
||||
|
||||
blen++; /* Account for the header line(s). */
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
setDeferredArrayLen(c,blenp,blen);
|
||||
}
|
||||
|
||||
/* Add a suggestive error reply.
|
||||
@ -678,7 +810,7 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
|
||||
* user what to do to fix it if needed. */
|
||||
if (server.protected_mode &&
|
||||
server.bindaddr_count == 0 &&
|
||||
server.requirepass == NULL &&
|
||||
DefaultUser->flags & USER_FLAG_NOPASS &&
|
||||
!(flags & CLIENT_UNIX_SOCKET) &&
|
||||
ip != NULL)
|
||||
{
|
||||
@ -1893,7 +2025,7 @@ NULL
|
||||
if (c->name)
|
||||
addReplyBulk(c,c->name);
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"pause") && c->argc == 3) {
|
||||
long long duration;
|
||||
|
||||
@ -1906,6 +2038,63 @@ NULL
|
||||
}
|
||||
}
|
||||
|
||||
/* HELLO <protocol-version> [AUTH <user> <password>] */
|
||||
void helloCommand(client *c) {
|
||||
long long ver;
|
||||
|
||||
if (getLongLongFromObject(c->argv[1],&ver) != C_OK ||
|
||||
ver < 2 || ver > 3)
|
||||
{
|
||||
addReplyError(c,"-NOPROTO unsupported protocol version");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Switching to protocol v2 is not allowed. But we send a specific
|
||||
* error message in this case. */
|
||||
if (ver == 2) {
|
||||
addReplyError(c,"Switching to RESP version 2 is not allowed.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* At this point we need to be authenticated to continue. */
|
||||
if (!c->authenticated) {
|
||||
addReplyError(c,"-NOAUTH HELLO must be called with the client already "
|
||||
"authenticated, otherwise the HELLO AUTH <user> <pass> "
|
||||
"option can be used to authenticate the client and "
|
||||
"select the RESP protocol version at the same time");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Let's switch to RESP3 mode. */
|
||||
c->resp = 3;
|
||||
addReplyMapLen(c,7);
|
||||
|
||||
addReplyBulkCString(c,"server");
|
||||
addReplyBulkCString(c,"redis");
|
||||
|
||||
addReplyBulkCString(c,"version");
|
||||
addReplyBulkCString(c,REDIS_VERSION);
|
||||
|
||||
addReplyBulkCString(c,"proto");
|
||||
addReplyLongLong(c,3);
|
||||
|
||||
addReplyBulkCString(c,"id");
|
||||
addReplyLongLong(c,c->id);
|
||||
|
||||
addReplyBulkCString(c,"mode");
|
||||
if (server.sentinel_mode) addReplyBulkCString(c,"sentinel");
|
||||
if (server.cluster_enabled) addReplyBulkCString(c,"cluster");
|
||||
else addReplyBulkCString(c,"standalone");
|
||||
|
||||
if (!server.sentinel_mode) {
|
||||
addReplyBulkCString(c,"role");
|
||||
addReplyBulkCString(c,server.masterhost ? "replica" : "master");
|
||||
}
|
||||
|
||||
addReplyBulkCString(c,"modules");
|
||||
addReplyLoadedModules(c);
|
||||
}
|
||||
|
||||
/* This callback is bound to POST and "Host:" command names. Those are not
|
||||
* really commands, but are used in security attacks in order to talk to
|
||||
* Redis instances via HTTP, with a technique called "cross protocol scripting"
|
||||
|
14
src/object.c
14
src/object.c
@ -1248,15 +1248,15 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyBulkCString(c,strEncoding(o->encoding));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
@ -1264,7 +1264,7 @@ NULL
|
||||
}
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
@ -1316,7 +1316,7 @@ NULL
|
||||
}
|
||||
}
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReply(c, shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize(dictGetVal(de),samples);
|
||||
@ -1326,7 +1326,7 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
addReplyMultiBulkLen(c,(25+mh->num_dbs)*2);
|
||||
addReplyMapLen(c,25+mh->num_dbs);
|
||||
|
||||
addReplyBulkCString(c,"peak.allocated");
|
||||
addReplyLongLong(c,mh->peak_allocated);
|
||||
@ -1356,7 +1356,7 @@ NULL
|
||||
char dbname[32];
|
||||
snprintf(dbname,sizeof(dbname),"db.%zd",mh->db[j].dbid);
|
||||
addReplyBulkCString(c,dbname);
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyMapLen(c,2);
|
||||
|
||||
addReplyBulkCString(c,"overhead.hashtable.main");
|
||||
addReplyLongLong(c,mh->db[j].overhead_ht_main);
|
||||
|
153
src/pubsub.c
153
src/pubsub.c
@ -29,6 +29,93 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
int clientSubscriptionsCount(client *c);
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub client replies API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Send a pubsub message of type "message" to the client. */
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
* with the "message" type delivered by addReplyPubsubMessage() is that
|
||||
* this message format also includes the pattern that matched the message. */
|
||||
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[4]);
|
||||
else
|
||||
addReplyPushLen(c,4);
|
||||
addReply(c,shared.pmessagebulk);
|
||||
addReplyBulk(c,pat);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send the pubsub subscription notification to the client. */
|
||||
void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub unsubscription notification to the client.
|
||||
* Channel can be NULL: this is useful when the client sends a mass
|
||||
* unsubscribe command but there are no channels to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
if (channel)
|
||||
addReplyBulk(c,channel);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern subscription notification to the client. */
|
||||
void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern unsubscription notification to the client.
|
||||
* Pattern can be NULL: this is useful when the client sends a mass
|
||||
* punsubscribe command but there are no pattern to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
if (pattern)
|
||||
addReplyBulk(c,pattern);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub low level API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@ -76,10 +163,7 @@ int pubsubSubscribeChannel(client *c, robj *channel) {
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
addReplyPubsubSubscribed(c,channel);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -111,14 +195,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
|
||||
}
|
||||
if (notify) addReplyPubsubUnsubscribed(c,channel);
|
||||
decrRefCount(channel); /* it is finally safe to release it */
|
||||
return retval;
|
||||
}
|
||||
@ -138,10 +215,7 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -162,13 +236,7 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
decrRefCount(pattern);
|
||||
return retval;
|
||||
}
|
||||
@ -186,13 +254,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
count += pubsubUnsubscribeChannel(c,channel,notify);
|
||||
}
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
if (notify && count == 0) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL);
|
||||
dictReleaseIterator(di);
|
||||
return count;
|
||||
}
|
||||
@ -210,14 +272,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
|
||||
count += pubsubUnsubscribePattern(c,pattern,notify);
|
||||
}
|
||||
if (notify && count == 0) {
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
if (notify && count == 0) addReplyPubsubPatUnsubscribed(c,NULL);
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -238,11 +293,7 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
listRewind(list,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = ln->value;
|
||||
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,message);
|
||||
addReplyPubsubMessage(c,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@ -256,12 +307,10 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0)) {
|
||||
addReply(pat->client,shared.mbulkhdr[4]);
|
||||
addReply(pat->client,shared.pmessagebulk);
|
||||
addReplyBulk(pat->client,pat->pattern);
|
||||
addReplyBulk(pat->client,channel);
|
||||
addReplyBulk(pat->client,message);
|
||||
sdslen(channel->ptr),0))
|
||||
{
|
||||
addReplyPubsubPatMessage(pat->client,
|
||||
pat->pattern,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@ -343,7 +392,7 @@ NULL
|
||||
long mblen = 0;
|
||||
void *replylen;
|
||||
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *cobj = dictGetKey(de);
|
||||
sds channel = cobj->ptr;
|
||||
@ -356,12 +405,12 @@ NULL
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c,replylen,mblen);
|
||||
setDeferredArrayLen(c,replylen,mblen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
|
||||
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
|
||||
int j;
|
||||
|
||||
addReplyMultiBulkLen(c,(c->argc-2)*2);
|
||||
addReplyArrayLen(c,(c->argc-2)*2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
|
||||
|
181
src/redis-cli.c
181
src/redis-cli.c
@ -810,6 +810,9 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
out = sdscatprintf(out,"(integer) %lld\n",r->integer);
|
||||
break;
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
out = sdscatprintf(out,"(double) %s\n",r->str);
|
||||
break;
|
||||
case REDIS_REPLY_STRING:
|
||||
/* If you are producing output for the standard output we want
|
||||
* a more interesting output with quoted characters and so forth */
|
||||
@ -819,9 +822,21 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
case REDIS_REPLY_NIL:
|
||||
out = sdscat(out,"(nil)\n");
|
||||
break;
|
||||
case REDIS_REPLY_BOOL:
|
||||
out = sdscat(out,r->integer ? "(true)\n" : "(false)\n");
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
if (r->elements == 0) {
|
||||
out = sdscat(out,"(empty list or set)\n");
|
||||
if (r->type == REDIS_REPLY_ARRAY)
|
||||
out = sdscat(out,"(empty array)\n");
|
||||
else if (r->type == REDIS_REPLY_MAP)
|
||||
out = sdscat(out,"(empty hash)\n");
|
||||
else if (r->type == REDIS_REPLY_SET)
|
||||
out = sdscat(out,"(empty set)\n");
|
||||
else
|
||||
out = sdscat(out,"(empty aggregate type)\n");
|
||||
} else {
|
||||
unsigned int i, idxlen = 0;
|
||||
char _prefixlen[16];
|
||||
@ -831,6 +846,7 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
|
||||
/* Calculate chars needed to represent the largest index */
|
||||
i = r->elements;
|
||||
if (r->type == REDIS_REPLY_MAP) i /= 2;
|
||||
do {
|
||||
idxlen++;
|
||||
i /= 10;
|
||||
@ -842,17 +858,35 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
_prefix = sdscat(sdsnew(prefix),_prefixlen);
|
||||
|
||||
/* Setup prefix format for every entry */
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);
|
||||
char numsep;
|
||||
if (r->type == REDIS_REPLY_SET) numsep = '~';
|
||||
else if (r->type == REDIS_REPLY_MAP) numsep = '#';
|
||||
else numsep = ')';
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud%c ",idxlen,numsep);
|
||||
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
unsigned int human_idx = (r->type == REDIS_REPLY_MAP) ?
|
||||
i/2 : i;
|
||||
human_idx++; /* Make it 1-based. */
|
||||
|
||||
/* Don't use the prefix for the first element, as the parent
|
||||
* caller already prepended the index number. */
|
||||
out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
|
||||
out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,human_idx);
|
||||
|
||||
/* Format the multi bulk entry */
|
||||
tmp = cliFormatReplyTTY(r->element[i],_prefix);
|
||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||
sdsfree(tmp);
|
||||
|
||||
/* For maps, format the value as well. */
|
||||
if (r->type == REDIS_REPLY_MAP) {
|
||||
i++;
|
||||
sdsrange(out,0,-2);
|
||||
out = sdscat(out," => ");
|
||||
tmp = cliFormatReplyTTY(r->element[i],_prefix);
|
||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||
sdsfree(tmp);
|
||||
}
|
||||
}
|
||||
sdsfree(_prefix);
|
||||
}
|
||||
@ -2934,6 +2968,70 @@ static int clusterManagerSetSlotOwner(clusterManagerNode *owner,
|
||||
return success;
|
||||
}
|
||||
|
||||
/* Get the hash for the values of the specified keys in *keys_reply for the
|
||||
* specified nodes *n1 and *n2, by calling DEBUG DIGEST-VALUE redis command
|
||||
* on both nodes. Every key with same name on both nodes but having different
|
||||
* values will be added to the *diffs list. Return 0 in case of reply
|
||||
* error. */
|
||||
static int clusterManagerCompareKeysValues(clusterManagerNode *n1,
|
||||
clusterManagerNode *n2,
|
||||
redisReply *keys_reply,
|
||||
list *diffs)
|
||||
{
|
||||
size_t i, argc = keys_reply->elements + 2;
|
||||
static const char *hash_zero = "0000000000000000000000000000000000000000";
|
||||
char **argv = zcalloc(argc * sizeof(char *));
|
||||
size_t *argv_len = zcalloc(argc * sizeof(size_t));
|
||||
argv[0] = "DEBUG";
|
||||
argv_len[0] = 5;
|
||||
argv[1] = "DIGEST-VALUE";
|
||||
argv_len[1] = 12;
|
||||
for (i = 0; i < keys_reply->elements; i++) {
|
||||
redisReply *entry = keys_reply->element[i];
|
||||
int idx = i + 2;
|
||||
argv[idx] = entry->str;
|
||||
argv_len[idx] = entry->len;
|
||||
}
|
||||
int success = 0;
|
||||
void *_reply1 = NULL, *_reply2 = NULL;
|
||||
redisReply *r1 = NULL, *r2 = NULL;
|
||||
redisAppendCommandArgv(n1->context,argc, (const char**)argv,argv_len);
|
||||
success = (redisGetReply(n1->context, &_reply1) == REDIS_OK);
|
||||
if (!success) goto cleanup;
|
||||
r1 = (redisReply *) _reply1;
|
||||
redisAppendCommandArgv(n2->context,argc, (const char**)argv,argv_len);
|
||||
success = (redisGetReply(n2->context, &_reply2) == REDIS_OK);
|
||||
if (!success) goto cleanup;
|
||||
r2 = (redisReply *) _reply2;
|
||||
success = (r1->type != REDIS_REPLY_ERROR && r2->type != REDIS_REPLY_ERROR);
|
||||
if (r1->type == REDIS_REPLY_ERROR) {
|
||||
CLUSTER_MANAGER_PRINT_REPLY_ERROR(n1, r1->str);
|
||||
success = 0;
|
||||
}
|
||||
if (r2->type == REDIS_REPLY_ERROR) {
|
||||
CLUSTER_MANAGER_PRINT_REPLY_ERROR(n2, r2->str);
|
||||
success = 0;
|
||||
}
|
||||
if (!success) goto cleanup;
|
||||
assert(keys_reply->elements == r1->elements &&
|
||||
keys_reply->elements == r2->elements);
|
||||
for (i = 0; i < keys_reply->elements; i++) {
|
||||
char *key = keys_reply->element[i]->str;
|
||||
char *hash1 = r1->element[i]->str;
|
||||
char *hash2 = r2->element[i]->str;
|
||||
/* Ignore keys that don't exist in both nodes. */
|
||||
if (strcmp(hash1, hash_zero) == 0 || strcmp(hash2, hash_zero) == 0)
|
||||
continue;
|
||||
if (strcmp(hash1, hash2) != 0) listAddNodeTail(diffs, key);
|
||||
}
|
||||
cleanup:
|
||||
if (r1) freeReplyObject(r1);
|
||||
if (r2) freeReplyObject(r2);
|
||||
zfree(argv);
|
||||
zfree(argv_len);
|
||||
return success;
|
||||
}
|
||||
|
||||
/* Migrate keys taken from reply->elements. It returns the reply from the
|
||||
* MIGRATE command, or NULL if something goes wrong. If the argument 'dots'
|
||||
* is not NULL, a dot will be printed for every migrated key. */
|
||||
@ -3014,8 +3112,10 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
|
||||
char **err)
|
||||
{
|
||||
int success = 1;
|
||||
int retry = (config.cluster_manager_command.flags &
|
||||
(CLUSTER_MANAGER_CMD_FLAG_FIX | CLUSTER_MANAGER_CMD_FLAG_REPLACE));
|
||||
int do_fix = config.cluster_manager_command.flags &
|
||||
CLUSTER_MANAGER_CMD_FLAG_FIX;
|
||||
int do_replace = config.cluster_manager_command.flags &
|
||||
CLUSTER_MANAGER_CMD_FLAG_REPLACE;
|
||||
while (1) {
|
||||
char *dots = NULL;
|
||||
redisReply *reply = NULL, *migrate_reply = NULL;
|
||||
@ -3049,6 +3149,8 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
|
||||
int is_busy = strstr(migrate_reply->str, "BUSYKEY") != NULL;
|
||||
int not_served = 0;
|
||||
if (!is_busy) {
|
||||
/* Check if the slot is unassigned (not served) in the
|
||||
* source node's configuration. */
|
||||
char *get_owner_err = NULL;
|
||||
clusterManagerNode *served_by =
|
||||
clusterManagerGetSlotOwner(source, slot, &get_owner_err);
|
||||
@ -3061,20 +3163,68 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retry && (is_busy || not_served)) {
|
||||
/* If the key already exists, try to migrate keys
|
||||
* adding REPLACE option.
|
||||
* If the key's slot is not served, try to assign slot
|
||||
/* Try to handle errors. */
|
||||
if (is_busy || not_served) {
|
||||
/* If the key's slot is not served, try to assign slot
|
||||
* to the target node. */
|
||||
if (not_served) {
|
||||
if (do_fix && not_served) {
|
||||
clusterManagerLogWarn("*** Slot was not served, setting "
|
||||
"owner to node %s:%d.\n",
|
||||
target->ip, target->port);
|
||||
clusterManagerSetSlot(source, target, slot, "node", NULL);
|
||||
}
|
||||
/* If the key already exists in the target node (BUSYKEY),
|
||||
* check whether its value is the same in both nodes.
|
||||
* In case of equal values, retry migration with the
|
||||
* REPLACE option.
|
||||
* In case of different values:
|
||||
* - If the migration is requested by the fix command, stop
|
||||
* and warn the user.
|
||||
* - In other cases (ie. reshard), proceed only if the user
|
||||
* launched the command with the --cluster-replace option.*/
|
||||
if (is_busy) {
|
||||
clusterManagerLogWarn("*** Target key exists. "
|
||||
"Replacing it for FIX.\n");
|
||||
clusterManagerLogWarn("\n*** Target key exists\n");
|
||||
if (!do_replace) {
|
||||
clusterManagerLogWarn("*** Checking key values on "
|
||||
"both nodes...\n");
|
||||
list *diffs = listCreate();
|
||||
success = clusterManagerCompareKeysValues(source,
|
||||
target, reply, diffs);
|
||||
if (!success) {
|
||||
clusterManagerLogErr("*** Value check failed!\n");
|
||||
listRelease(diffs);
|
||||
goto next;
|
||||
}
|
||||
if (listLength(diffs) > 0) {
|
||||
success = 0;
|
||||
clusterManagerLogErr(
|
||||
"*** Found %d key(s) in both source node and "
|
||||
"target node having different values.\n"
|
||||
" Source node: %s:%d\n"
|
||||
" Target node: %s:%d\n"
|
||||
" Keys(s):\n",
|
||||
listLength(diffs),
|
||||
source->ip, source->port,
|
||||
target->ip, target->port);
|
||||
listIter dli;
|
||||
listNode *dln;
|
||||
listRewind(diffs, &dli);
|
||||
while((dln = listNext(&dli)) != NULL) {
|
||||
char *k = dln->value;
|
||||
clusterManagerLogErr(" - %s\n", k);
|
||||
}
|
||||
clusterManagerLogErr("Please fix the above key(s) "
|
||||
"manually and try again "
|
||||
"or relaunch the command \n"
|
||||
"with --cluster-replace "
|
||||
"option to force key "
|
||||
"overriding.\n");
|
||||
listRelease(diffs);
|
||||
goto next;
|
||||
}
|
||||
listRelease(diffs);
|
||||
}
|
||||
clusterManagerLogWarn("*** Replacing target keys...\n");
|
||||
}
|
||||
freeReplyObject(migrate_reply);
|
||||
migrate_reply = clusterManagerMigrateKeysInReply(source,
|
||||
@ -5068,9 +5218,10 @@ static int clusterManagerCommandDeleteNode(int argc, char **argv) {
|
||||
if (!success) return 0;
|
||||
}
|
||||
|
||||
// Finally shutdown the node
|
||||
clusterManagerLogInfo(">>> SHUTDOWN the node.\n");
|
||||
redisReply *r = redisCommand(node->context, "SHUTDOWN");
|
||||
/* Finally send CLUSTER RESET to the node. */
|
||||
clusterManagerLogInfo(">>> Sending CLUSTER RESET SOFT to the "
|
||||
"deleted node.\n");
|
||||
redisReply *r = redisCommand(node->context, "CLUSTER RESET %s", "SOFT");
|
||||
success = clusterManagerCheckRedisReply(node, r, NULL);
|
||||
if (r) freeReplyObject(r);
|
||||
return success;
|
||||
|
@ -263,7 +263,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
* or are already in sync with the master. */
|
||||
|
||||
/* Add the multi bulk length. */
|
||||
addReplyMultiBulkLen(slave,argc);
|
||||
addReplyArrayLen(slave,argc);
|
||||
|
||||
/* Finally any additional argument that was not stored inside the
|
||||
* static buffer if any (from j to argc). */
|
||||
@ -296,7 +296,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
|
||||
|
||||
/* Don't feed slaves that are still waiting for BGSAVE to start */
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
|
||||
addReplyString(slave,buf,buflen);
|
||||
addReplyProto(slave,buf,buflen);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1080,6 +1080,7 @@ void replicationCreateMasterClient(int fd, int dbid) {
|
||||
server.master->authenticated = 1;
|
||||
server.master->reploff = server.master_initial_offset;
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
server.master->user = NULL; /* This client can do everything. */
|
||||
memcpy(server.master->replid, server.master_replid,
|
||||
sizeof(server.master_replid));
|
||||
/* If master offset is set to -1, this master is old and is not
|
||||
@ -1256,6 +1257,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
@ -2063,10 +2065,10 @@ void roleCommand(client *c) {
|
||||
void *mbcount;
|
||||
int slaves = 0;
|
||||
|
||||
addReplyMultiBulkLen(c,3);
|
||||
addReplyArrayLen(c,3);
|
||||
addReplyBulkCBuffer(c,"master",6);
|
||||
addReplyLongLong(c,server.master_repl_offset);
|
||||
mbcount = addDeferredMultiBulkLength(c);
|
||||
mbcount = addReplyDeferredLen(c);
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = ln->value;
|
||||
@ -2078,17 +2080,17 @@ void roleCommand(client *c) {
|
||||
slaveip = ip;
|
||||
}
|
||||
if (slave->replstate != SLAVE_STATE_ONLINE) continue;
|
||||
addReplyMultiBulkLen(c,3);
|
||||
addReplyArrayLen(c,3);
|
||||
addReplyBulkCString(c,slaveip);
|
||||
addReplyBulkLongLong(c,slave->slave_listening_port);
|
||||
addReplyBulkLongLong(c,slave->repl_ack_off);
|
||||
slaves++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,mbcount,slaves);
|
||||
setDeferredArrayLen(c,mbcount,slaves);
|
||||
} else {
|
||||
char *slavestate = NULL;
|
||||
|
||||
addReplyMultiBulkLen(c,5);
|
||||
addReplyArrayLen(c,5);
|
||||
addReplyBulkCBuffer(c,"slave",5);
|
||||
addReplyBulkCString(c,server.masterhost);
|
||||
addReplyLongLong(c,server.masterport);
|
||||
@ -2117,7 +2119,7 @@ void replicationSendAck(void) {
|
||||
|
||||
if (c != NULL) {
|
||||
c->flags |= CLIENT_MASTER_FORCE_REPLY;
|
||||
addReplyMultiBulkLen(c,3);
|
||||
addReplyArrayLen(c,3);
|
||||
addReplyBulkCString(c,"REPLCONF");
|
||||
addReplyBulkCString(c,"ACK");
|
||||
addReplyBulkLongLong(c,c->reploff);
|
||||
|
@ -42,7 +42,7 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype);
|
||||
int redis_math_random (lua_State *L);
|
||||
int redis_math_randomseed (lua_State *L);
|
||||
void ldbInit(void);
|
||||
@ -132,7 +132,9 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||||
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
|
||||
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
|
||||
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
|
||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply); break;
|
||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@ -180,22 +182,38 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
|
||||
return p+2;
|
||||
}
|
||||
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply) {
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
||||
char *p = strchr(reply+1,'\r');
|
||||
long long mbulklen;
|
||||
int j = 0;
|
||||
|
||||
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
lua_pushboolean(lua,0);
|
||||
return p;
|
||||
}
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
lua_pushnumber(lua,j+1);
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
lua_settable(lua,-3);
|
||||
if (server.lua_caller->resp == 2 || atype == '*') {
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
lua_pushboolean(lua,0);
|
||||
return p;
|
||||
}
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
lua_pushnumber(lua,j+1);
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
} else if (server.lua_caller->resp == 3) {
|
||||
/* Here we handle only Set and Map replies in RESP3 mode, since arrays
|
||||
* follow the above RESP2 code path. */
|
||||
p += 2;
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
if (atype == '%') {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
} else {
|
||||
lua_pushboolean(lua,1);
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@ -282,7 +300,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
|
||||
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.null[c->resp]);
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
|
||||
@ -315,7 +333,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
sdsfree(ok);
|
||||
lua_pop(lua,1);
|
||||
} else {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int j = 1, mbulklen = 0;
|
||||
|
||||
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
||||
@ -330,11 +348,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
luaReplyToRedisReply(c, lua);
|
||||
mbulklen++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,mbulklen);
|
||||
setDeferredArrayLen(c,replylen,mbulklen);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
}
|
||||
@ -1501,7 +1519,7 @@ NULL
|
||||
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
|
||||
int j;
|
||||
|
||||
addReplyMultiBulkLen(c, c->argc-2);
|
||||
addReplyArrayLen(c, c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (dictFind(server.lua_scripts,c->argv[j]->ptr))
|
||||
addReply(c,shared.cone);
|
||||
|
@ -453,7 +453,8 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0}
|
||||
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0},
|
||||
{"hello",helloCommand,-2,"sF",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/* This function overwrites a few normal Redis config default with Sentinel
|
||||
@ -885,17 +886,17 @@ void sentinelPendingScriptsCommand(client *c) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
addReplyMultiBulkLen(c,listLength(sentinel.scripts_queue));
|
||||
addReplyArrayLen(c,listLength(sentinel.scripts_queue));
|
||||
listRewind(sentinel.scripts_queue,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sentinelScriptJob *sj = ln->value;
|
||||
int j = 0;
|
||||
|
||||
addReplyMultiBulkLen(c,10);
|
||||
addReplyMapLen(c,5);
|
||||
|
||||
addReplyBulkCString(c,"argv");
|
||||
while (sj->argv[j]) j++;
|
||||
addReplyMultiBulkLen(c,j);
|
||||
addReplyArrayLen(c,j);
|
||||
j = 0;
|
||||
while (sj->argv[j]) addReplyBulkCString(c,sj->argv[j++]);
|
||||
|
||||
@ -1960,7 +1961,7 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
} else if (ri->flags & SRI_SLAVE) {
|
||||
auth_pass = ri->master->auth_pass;
|
||||
} else if (ri->flags & SRI_SENTINEL) {
|
||||
if (server.requirepass) auth_pass = server.requirepass;
|
||||
auth_pass = ACLDefaultUserFirstPassword();
|
||||
}
|
||||
|
||||
if (auth_pass) {
|
||||
@ -2741,7 +2742,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
void *mbl;
|
||||
int fields = 0;
|
||||
|
||||
mbl = addDeferredMultiBulkLength(c);
|
||||
mbl = addReplyDeferredLen(c);
|
||||
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCString(c,ri->name);
|
||||
@ -2922,7 +2923,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
fields++;
|
||||
}
|
||||
|
||||
setDeferredMultiBulkLength(c,mbl,fields*2);
|
||||
setDeferredMapLen(c,mbl,fields);
|
||||
}
|
||||
|
||||
/* Output a number of instances contained inside a dictionary as
|
||||
@ -2932,7 +2933,7 @@ void addReplyDictOfRedisInstances(client *c, dict *instances) {
|
||||
dictEntry *de;
|
||||
|
||||
di = dictGetIterator(instances);
|
||||
addReplyMultiBulkLen(c,dictSize(instances));
|
||||
addReplyArrayLen(c,dictSize(instances));
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
|
||||
@ -3062,7 +3063,7 @@ void sentinelCommand(client *c) {
|
||||
|
||||
/* Reply with a three-elements multi-bulk reply:
|
||||
* down state, leader, vote epoch. */
|
||||
addReplyMultiBulkLen(c,3);
|
||||
addReplyArrayLen(c,3);
|
||||
addReply(c, isdown ? shared.cone : shared.czero);
|
||||
addReplyBulkCString(c, leader ? leader : "*");
|
||||
addReplyLongLong(c, (long long)leader_epoch);
|
||||
@ -3078,11 +3079,11 @@ void sentinelCommand(client *c) {
|
||||
if (c->argc != 3) goto numargserr;
|
||||
ri = sentinelGetMasterByName(c->argv[2]->ptr);
|
||||
if (ri == NULL) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
|
||||
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,addr->ip);
|
||||
addReplyBulkLongLong(c,addr->port);
|
||||
}
|
||||
@ -3232,7 +3233,7 @@ void sentinelCommand(client *c) {
|
||||
* 3.) other master name
|
||||
* ...
|
||||
*/
|
||||
addReplyMultiBulkLen(c,dictSize(masters_local) * 2);
|
||||
addReplyArrayLen(c,dictSize(masters_local) * 2);
|
||||
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
@ -3240,25 +3241,25 @@ void sentinelCommand(client *c) {
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
addReplyBulkCBuffer(c,ri->name,strlen(ri->name));
|
||||
addReplyMultiBulkLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c, now - ri->info_refresh);
|
||||
if (ri->info)
|
||||
addReplyBulkCBuffer(c,ri->info,sdslen(ri->info));
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
|
||||
dictIterator *sdi;
|
||||
dictEntry *sde;
|
||||
sdi = dictGetIterator(ri->slaves);
|
||||
while ((sde = dictNext(sdi)) != NULL) {
|
||||
sentinelRedisInstance *sri = dictGetVal(sde);
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c, now - sri->info_refresh);
|
||||
if (sri->info)
|
||||
addReplyBulkCBuffer(c,sri->info,sdslen(sri->info));
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
dictReleaseIterator(sdi);
|
||||
}
|
||||
@ -3282,7 +3283,7 @@ void sentinelCommand(client *c) {
|
||||
serverLog(LL_WARNING,"Failure simulation: this Sentinel "
|
||||
"will crash after promoting the selected replica to master");
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"help")) {
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"crash-after-election");
|
||||
addReplyBulkCString(c,"crash-after-promotion");
|
||||
} else {
|
||||
@ -3382,9 +3383,9 @@ void sentinelRoleCommand(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c,"sentinel",8);
|
||||
addReplyMultiBulkLen(c,dictSize(sentinel.masters));
|
||||
addReplyArrayLen(c,dictSize(sentinel.masters));
|
||||
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
|
560
src/server.c
560
src/server.c
@ -90,6 +90,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
* in MSET the step is two since arguments are key,val,key,val,...
|
||||
* microseconds: microseconds of total execution time for this command.
|
||||
* calls: total number of calls of this command.
|
||||
* id: command bit identifier for ACLs.
|
||||
*
|
||||
* The flags, microseconds and calls fields are computed by Redis and should
|
||||
* always be set to zero.
|
||||
@ -114,7 +115,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
* is deterministic.
|
||||
* l: Allow command while loading the database.
|
||||
* t: Allow command while a slave has stale data but is not allowed to
|
||||
* server this data. Normally no command is accepted in this condition
|
||||
* serve this data. Normally no command is accepted in this condition
|
||||
* but just a few.
|
||||
* M: Do not automatically propagate the command on MONITOR.
|
||||
* k: Perform an implicit ASKING for this command, so the command will be
|
||||
@ -125,206 +126,208 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
* are not fast commands.
|
||||
*/
|
||||
struct redisCommand redisCommandTable[] = {
|
||||
{"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
|
||||
{"unlink",unlinkCommand,-2,"wF",0,NULL,1,-1,1,0,0},
|
||||
{"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
|
||||
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"bitfield",bitfieldCommand,-2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"incr",incrCommand,2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"mget",mgetCommand,-2,"rF",0,NULL,1,-1,1,0,0},
|
||||
{"rpush",rpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"lpush",lpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"rpushx",rpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"lpushx",lpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"rpop",rpopCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"lpop",lpopCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"brpop",brpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
|
||||
{"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0},
|
||||
{"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
|
||||
{"llen",llenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0},
|
||||
{"sadd",saddCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"srem",sremCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"smove",smoveCommand,4,"wF",0,NULL,1,2,1,0,0},
|
||||
{"sismember",sismemberCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"scard",scardCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"spop",spopCommand,-2,"wRF",0,NULL,1,1,1,0,0},
|
||||
{"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0},
|
||||
{"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0},
|
||||
{"sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0},
|
||||
{"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"sscan",sscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"zadd",zaddCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"zincrby",zincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"zrem",zremCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcount",zcountCommand,4,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zlexcount",zlexcountCommand,4,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcard",zcardCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zscore",zscoreCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hmset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hmget",hmgetCommand,-3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hgetall",hgetallCommand,2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"incrby",incrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"decrby",decrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"incrbyfloat",incrbyfloatCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
|
||||
{"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
|
||||
{"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"select",selectCommand,2,"lF",0,NULL,0,0,0,0,0},
|
||||
{"swapdb",swapdbCommand,3,"wF",0,NULL,0,0,0,0,0},
|
||||
{"move",moveCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0},
|
||||
{"renamenx",renamenxCommand,3,"wF",0,NULL,1,2,1,0,0},
|
||||
{"expire",expireCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"expireat",expireatCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"pexpire",pexpireCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"pexpireat",pexpireatCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
|
||||
{"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
|
||||
{"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0},
|
||||
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,-1,"as",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"aslt",0,NULL,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"RF",0,NULL,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
|
||||
{"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
|
||||
{"discard",discardCommand,1,"sF",0,NULL,0,0,0,0,0},
|
||||
{"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
{"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0},
|
||||
{"replconf",replconfCommand,-1,"aslt",0,NULL,0,0,0,0,0},
|
||||
{"flushdb",flushdbCommand,-1,"w",0,NULL,0,0,0,0,0},
|
||||
{"flushall",flushallCommand,-1,"w",0,NULL,0,0,0,0,0},
|
||||
{"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
|
||||
{"info",infoCommand,-1,"ltR",0,NULL,0,0,0,0,0},
|
||||
{"monitor",monitorCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
{"ttl",ttlCommand,2,"rFR",0,NULL,1,1,1,0,0},
|
||||
{"touch",touchCommand,-2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"rFR",0,NULL,1,1,1,0,0},
|
||||
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"slaveof",replicaofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"replicaof",replicaofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"last",0,NULL,0,0,0,0,0},
|
||||
{"subscribe",subscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"unsubscribe",unsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pltF",0,NULL,0,0,0,0,0},
|
||||
{"pubsub",pubsubCommand,-2,"pltR",0,NULL,0,0,0,0,0},
|
||||
{"watch",watchCommand,-2,"sF",0,NULL,1,-1,1,0,0},
|
||||
{"unwatch",unwatchCommand,1,"sF",0,NULL,0,0,0,0,0},
|
||||
{"cluster",clusterCommand,-2,"a",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,-6,"wR",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"asking",askingCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"rR",0,NULL,2,2,1,0,0},
|
||||
{"memory",memoryCommand,-2,"rR",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"aR",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"s",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"RF",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"ltR",0,NULL,0,0,0,0,0},
|
||||
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"georadius",georadiusCommand,-6,"w",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadius_ro",georadiusroCommand,-6,"r",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadiusbymember",georadiusbymemberCommand,-5,"w",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadiusbymember_ro",georadiusbymemberroCommand,-5,"r",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"geohash",geohashCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"geopos",geoposCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"geodist",geodistCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"a",0,NULL,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0},
|
||||
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
|
||||
{"xadd",xaddCommand,-5,"wmFR",0,NULL,1,1,1,0,0},
|
||||
{"xrange",xrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"xrevrange",xrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"xlen",xlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"xread",xreadCommand,-4,"rs",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xreadgroup",xreadCommand,-7,"ws",0,xreadGetKeys,1,1,1,0,0},
|
||||
{"xgroup",xgroupCommand,-2,"wm",0,NULL,2,2,1,0,0},
|
||||
{"xsetid",xsetidCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"xack",xackCommand,-4,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xpending",xpendingCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"xclaim",xclaimCommand,-6,"wRF",0,NULL,1,1,1,0,0},
|
||||
{"xinfo",xinfoCommand,-2,"rR",0,NULL,2,2,1,0,0},
|
||||
{"xdel",xdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"xtrim",xtrimCommand,-2,"wFR",0,NULL,1,1,1,0,0},
|
||||
{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
|
||||
{"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0},
|
||||
{"lolwut",lolwutCommand,-1,"r",0,NULL,0,0,0,0,0}
|
||||
{"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0,0},
|
||||
{"unlink",unlinkCommand,-2,"wF",0,NULL,1,-1,1,0,0,0},
|
||||
{"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0,0},
|
||||
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"bitfield",bitfieldCommand,-2,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"incr",incrCommand,2,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"mget",mgetCommand,-2,"rF",0,NULL,1,-1,1,0,0,0},
|
||||
{"rpush",rpushCommand,-3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"lpush",lpushCommand,-3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"rpushx",rpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"lpushx",lpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"rpop",rpopCommand,2,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"lpop",lpopCommand,2,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"brpop",brpopCommand,-3,"ws",0,NULL,1,-2,1,0,0,0},
|
||||
{"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0,0},
|
||||
{"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0,0},
|
||||
{"llen",llenCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0,0},
|
||||
{"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0,0},
|
||||
{"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0,0},
|
||||
{"sadd",saddCommand,-3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"srem",sremCommand,-3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"smove",smoveCommand,4,"wF",0,NULL,1,2,1,0,0,0},
|
||||
{"sismember",sismemberCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"scard",scardCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"spop",spopCommand,-2,"wRF",0,NULL,1,1,1,0,0,0},
|
||||
{"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0,0},
|
||||
{"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0,0},
|
||||
{"sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0,0},
|
||||
{"sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0,0},
|
||||
{"sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0,0},
|
||||
{"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0,0},
|
||||
{"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0,0},
|
||||
{"sscan",sscanCommand,-3,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"zadd",zaddCommand,-4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"zincrby",zincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"zrem",zremCommand,-3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0,0},
|
||||
{"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0,0},
|
||||
{"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0,0},
|
||||
{"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0,0},
|
||||
{"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0,0},
|
||||
{"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"zcount",zcountCommand,4,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"zlexcount",zlexcountCommand,4,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"zcard",zcardCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"zscore",zscoreCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0,0},
|
||||
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"hmset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"hmget",hmgetCommand,-3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0,0},
|
||||
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0,0},
|
||||
{"hgetall",hgetallCommand,2,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"incrby",incrbyCommand,3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"decrby",decrbyCommand,3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"incrbyfloat",incrbyfloatCommand,3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0,0},
|
||||
{"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0,0},
|
||||
{"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0,0},
|
||||
{"select",selectCommand,2,"lF",0,NULL,0,0,0,0,0,0},
|
||||
{"swapdb",swapdbCommand,3,"wF",0,NULL,0,0,0,0,0,0},
|
||||
{"move",moveCommand,3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0,0},
|
||||
{"renamenx",renamenxCommand,3,"wF",0,NULL,1,2,1,0,0,0},
|
||||
{"expire",expireCommand,3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"expireat",expireatCommand,3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"pexpire",pexpireCommand,3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"pexpireat",pexpireatCommand,3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0,0},
|
||||
{"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0,0},
|
||||
{"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0,0},
|
||||
{"auth",authCommand,-2,"sltF",0,NULL,0,0,0,0,0,0},
|
||||
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,-1,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"aslt",0,NULL,0,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"RF",0,NULL,0,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0,0},
|
||||
{"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0,0},
|
||||
{"discard",discardCommand,1,"sF",0,NULL,0,0,0,0,0,0},
|
||||
{"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0,0},
|
||||
{"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0,0},
|
||||
{"replconf",replconfCommand,-1,"aslt",0,NULL,0,0,0,0,0,0},
|
||||
{"flushdb",flushdbCommand,-1,"w",0,NULL,0,0,0,0,0,0},
|
||||
{"flushall",flushallCommand,-1,"w",0,NULL,0,0,0,0,0,0},
|
||||
{"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0,0},
|
||||
{"info",infoCommand,-1,"ltR",0,NULL,0,0,0,0,0,0},
|
||||
{"monitor",monitorCommand,1,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"ttl",ttlCommand,2,"rFR",0,NULL,1,1,1,0,0,0},
|
||||
{"touch",touchCommand,-2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"pttl",pttlCommand,2,"rFR",0,NULL,1,1,1,0,0,0},
|
||||
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"slaveof",replicaofCommand,3,"ast",0,NULL,0,0,0,0,0,0},
|
||||
{"replicaof",replicaofCommand,3,"ast",0,NULL,0,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"last",0,NULL,0,0,0,0,0,0},
|
||||
{"subscribe",subscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0,0},
|
||||
{"unsubscribe",unsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pltF",0,NULL,0,0,0,0,0,0},
|
||||
{"pubsub",pubsubCommand,-2,"pltR",0,NULL,0,0,0,0,0,0},
|
||||
{"watch",watchCommand,-2,"sF",0,NULL,1,-1,1,0,0,0},
|
||||
{"unwatch",unwatchCommand,1,"sF",0,NULL,0,0,0,0,0,0},
|
||||
{"cluster",clusterCommand,-2,"a",0,NULL,0,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0,0},
|
||||
{"migrate",migrateCommand,-6,"wR",0,migrateGetKeys,0,0,0,0,0,0},
|
||||
{"asking",askingCommand,1,"F",0,NULL,0,0,0,0,0,0},
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"object",objectCommand,-2,"rR",0,NULL,2,2,1,0,0,0},
|
||||
{"memory",memoryCommand,-2,"rR",0,NULL,0,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0,0},
|
||||
{"hello",helloCommand,-2,"sF",0,NULL,0,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"aR",0,NULL,0,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"s",0,NULL,0,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"RF",0,NULL,0,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"ltR",0,NULL,0,0,0,0,0,0},
|
||||
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0,0},
|
||||
{"georadius",georadiusCommand,-6,"w",0,georadiusGetKeys,1,1,1,0,0,0},
|
||||
{"georadius_ro",georadiusroCommand,-6,"r",0,georadiusGetKeys,1,1,1,0,0,0},
|
||||
{"georadiusbymember",georadiusbymemberCommand,-5,"w",0,georadiusGetKeys,1,1,1,0,0,0},
|
||||
{"georadiusbymember_ro",georadiusbymemberroCommand,-5,"r",0,georadiusGetKeys,1,1,1,0,0,0},
|
||||
{"geohash",geohashCommand,-2,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"geopos",geoposCommand,-2,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"geodist",geodistCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"a",0,NULL,0,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0,0},
|
||||
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0,0},
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0,0},
|
||||
{"xadd",xaddCommand,-5,"wmFR",0,NULL,1,1,1,0,0,0},
|
||||
{"xrange",xrangeCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"xrevrange",xrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0,0},
|
||||
{"xlen",xlenCommand,2,"rF",0,NULL,1,1,1,0,0,0},
|
||||
{"xread",xreadCommand,-4,"rs",0,xreadGetKeys,1,1,1,0,0,0},
|
||||
{"xreadgroup",xreadCommand,-7,"ws",0,xreadGetKeys,1,1,1,0,0,0},
|
||||
{"xgroup",xgroupCommand,-2,"wm",0,NULL,2,2,1,0,0,0},
|
||||
{"xsetid",xsetidCommand,3,"wmF",0,NULL,1,1,1,0,0,0},
|
||||
{"xack",xackCommand,-4,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"xpending",xpendingCommand,-3,"rR",0,NULL,1,1,1,0,0,0},
|
||||
{"xclaim",xclaimCommand,-6,"wRF",0,NULL,1,1,1,0,0,0},
|
||||
{"xinfo",xinfoCommand,-2,"rR",0,NULL,2,2,1,0,0,0},
|
||||
{"xdel",xdelCommand,-3,"wF",0,NULL,1,1,1,0,0,0},
|
||||
{"xtrim",xtrimCommand,-2,"wFR",0,NULL,1,1,1,0,0,0},
|
||||
{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0,0},
|
||||
{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0,0},
|
||||
{"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0,0},
|
||||
{"lolwut",lolwutCommand,-1,"r",0,NULL,0,0,0,0,0,0},
|
||||
{"acl",aclCommand,-2,"ast",0,NULL,0,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@ -1428,10 +1431,7 @@ void createSharedObjects(void) {
|
||||
shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
|
||||
shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
|
||||
shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
|
||||
shared.cnegone = createObject(OBJ_STRING,sdsnew(":-1\r\n"));
|
||||
shared.nullbulk = createObject(OBJ_STRING,sdsnew("$-1\r\n"));
|
||||
shared.nullmultibulk = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
|
||||
shared.emptymultibulk = createObject(OBJ_STRING,sdsnew("*0\r\n"));
|
||||
shared.emptyarray = createObject(OBJ_STRING,sdsnew("*0\r\n"));
|
||||
shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
|
||||
shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
|
||||
shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
|
||||
@ -1471,6 +1471,17 @@ void createSharedObjects(void) {
|
||||
shared.colon = createObject(OBJ_STRING,sdsnew(":"));
|
||||
shared.plus = createObject(OBJ_STRING,sdsnew("+"));
|
||||
|
||||
/* The shared NULL depends on the protocol version. */
|
||||
shared.null[0] = NULL;
|
||||
shared.null[1] = NULL;
|
||||
shared.null[2] = createObject(OBJ_STRING,sdsnew("$-1\r\n"));
|
||||
shared.null[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
|
||||
|
||||
shared.nullarray[0] = NULL;
|
||||
shared.nullarray[1] = NULL;
|
||||
shared.nullarray[2] = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
|
||||
shared.nullarray[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
|
||||
|
||||
for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) {
|
||||
char dictid_str[64];
|
||||
int dictid_len;
|
||||
@ -1585,7 +1596,6 @@ void initServerConfig(void) {
|
||||
server.pidfile = NULL;
|
||||
server.rdb_filename = zstrdup(CONFIG_DEFAULT_RDB_FILENAME);
|
||||
server.aof_filename = zstrdup(CONFIG_DEFAULT_AOF_FILENAME);
|
||||
server.requirepass = NULL;
|
||||
server.rdb_compression = CONFIG_DEFAULT_RDB_COMPRESSION;
|
||||
server.rdb_checksum = CONFIG_DEFAULT_RDB_CHECKSUM;
|
||||
server.stop_writes_on_bgsave_err = CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
|
||||
@ -2192,6 +2202,8 @@ void populateCommandTable(void) {
|
||||
char *f = c->sflags;
|
||||
int retval1, retval2;
|
||||
|
||||
/* Translate the command string flags description into an actual
|
||||
* set of flags. */
|
||||
while(*f != '\0') {
|
||||
switch(*f) {
|
||||
case 'w': c->flags |= CMD_WRITE; break;
|
||||
@ -2212,6 +2224,7 @@ void populateCommandTable(void) {
|
||||
f++;
|
||||
}
|
||||
|
||||
c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
|
||||
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
|
||||
/* Populate an additional dictionary that will be unaffected
|
||||
* by rename-command statements in redis.conf. */
|
||||
@ -2573,13 +2586,31 @@ int processCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Check if the user is authenticated */
|
||||
if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
|
||||
if (!(DefaultUser->flags & USER_FLAG_NOPASS) &&
|
||||
!c->authenticated &&
|
||||
(c->cmd->proc != authCommand || c->cmd->proc == helloCommand))
|
||||
{
|
||||
flagTransaction(c);
|
||||
addReply(c,shared.noautherr);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Check if the user can run this command according to the current
|
||||
* ACLs. */
|
||||
int acl_retval = ACLCheckCommandPerm(c);
|
||||
if (acl_retval != ACL_OK) {
|
||||
flagTransaction(c);
|
||||
if (acl_retval == ACL_DENIED_CMD)
|
||||
addReplyErrorFormat(c,
|
||||
"-NOPERM this user has no permissions to run "
|
||||
"the '%s' command", c->cmd->name);
|
||||
else
|
||||
addReplyErrorFormat(c,
|
||||
"-NOPERM this user has no permissions to access "
|
||||
"one of the keys used as arguments");
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* If cluster is enabled perform the cluster redirection here.
|
||||
* However we don't perform the redirection if:
|
||||
* 1) The sender of this command is our master.
|
||||
@ -2672,8 +2703,9 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
|
||||
if (c->flags & CLIENT_PUBSUB &&
|
||||
/* Only allow a subset of commands in the context of Pub/Sub if the
|
||||
* connection is in RESP2 mode. With RESP3 there are no limits. */
|
||||
if ((c->flags & CLIENT_PUBSUB && c->resp == 2) &&
|
||||
c->cmd->proc != pingCommand &&
|
||||
c->cmd->proc != subscribeCommand &&
|
||||
c->cmd->proc != unsubscribeCommand &&
|
||||
@ -2705,6 +2737,7 @@ int processCommand(client *c) {
|
||||
/* Lua script too slow? Only allow a limited number of commands. */
|
||||
if (server.lua_timedout &&
|
||||
c->cmd->proc != authCommand &&
|
||||
c->cmd->proc != helloCommand &&
|
||||
c->cmd->proc != replconfCommand &&
|
||||
!(c->cmd->proc == shutdownCommand &&
|
||||
c->argc == 2 &&
|
||||
@ -2851,58 +2884,49 @@ int writeCommandsDeniedByDiskError(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Return zero if strings are the same, non-zero if they are not.
|
||||
* The comparison is performed in a way that prevents an attacker to obtain
|
||||
* information about the nature of the strings just monitoring the execution
|
||||
* time of the function.
|
||||
/* AUTH <passowrd>
|
||||
* AUTH <username> <password> (Redis >= 6.0 form)
|
||||
*
|
||||
* Note that limiting the comparison length to strings up to 512 bytes we
|
||||
* can avoid leaking any information about the password length and any
|
||||
* possible branch misprediction related leak.
|
||||
*/
|
||||
int time_independent_strcmp(char *a, char *b) {
|
||||
char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
|
||||
/* The above two strlen perform len(a) + len(b) operations where either
|
||||
* a or b are fixed (our password) length, and the difference is only
|
||||
* relative to the length of the user provided string, so no information
|
||||
* leak is possible in the following two lines of code. */
|
||||
unsigned int alen = strlen(a);
|
||||
unsigned int blen = strlen(b);
|
||||
unsigned int j;
|
||||
int diff = 0;
|
||||
|
||||
/* We can't compare strings longer than our static buffers.
|
||||
* Note that this will never pass the first test in practical circumstances
|
||||
* so there is no info leak. */
|
||||
if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;
|
||||
|
||||
memset(bufa,0,sizeof(bufa)); /* Constant time. */
|
||||
memset(bufb,0,sizeof(bufb)); /* Constant time. */
|
||||
/* Again the time of the following two copies is proportional to
|
||||
* len(a) + len(b) so no info is leaked. */
|
||||
memcpy(bufa,a,alen);
|
||||
memcpy(bufb,b,blen);
|
||||
|
||||
/* Always compare all the chars in the two buffers without
|
||||
* conditional expressions. */
|
||||
for (j = 0; j < sizeof(bufa); j++) {
|
||||
diff |= (bufa[j] ^ bufb[j]);
|
||||
}
|
||||
/* Length must be equal as well. */
|
||||
diff |= alen ^ blen;
|
||||
return diff; /* If zero strings are the same. */
|
||||
}
|
||||
|
||||
* When the user is omitted it means that we are trying to authenticate
|
||||
* against the default user. */
|
||||
void authCommand(client *c) {
|
||||
if (!server.requirepass) {
|
||||
addReplyError(c,"Client sent AUTH, but no password is set");
|
||||
} else if (!time_independent_strcmp(c->argv[1]->ptr, server.requirepass)) {
|
||||
c->authenticated = 1;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
c->authenticated = 0;
|
||||
addReplyError(c,"invalid password");
|
||||
/* Only two or three argument forms are allowed. */
|
||||
if (c->argc > 3) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle the two different forms here. The form with two arguments
|
||||
* will just use "default" as username. */
|
||||
robj *username, *password;
|
||||
if (c->argc == 2) {
|
||||
/* Mimic the old behavior of giving an error for the two commands
|
||||
* from if no password is configured. */
|
||||
if (DefaultUser->flags & USER_FLAG_NOPASS) {
|
||||
addReplyError(c,"AUTH <password> called without any password "
|
||||
"configured for the default user. Are you sure "
|
||||
"your configuration is correct?");
|
||||
return;
|
||||
}
|
||||
|
||||
username = createStringObject("default",7);
|
||||
password = c->argv[1];
|
||||
} else {
|
||||
username = c->argv[1];
|
||||
password = c->argv[2];
|
||||
}
|
||||
|
||||
if (ACLCheckUserCredentials(username,password) == C_OK) {
|
||||
c->authenticated = 1;
|
||||
c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,"-WRONGPASS invalid username-password pair");
|
||||
}
|
||||
|
||||
/* Free the "default" string object we created for the two
|
||||
* arguments form. */
|
||||
if (c->argc == 2) decrRefCount(username);
|
||||
}
|
||||
|
||||
/* The PING command. It works in a different way if the client is in
|
||||
@ -2915,7 +2939,7 @@ void pingCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->flags & CLIENT_PUBSUB) {
|
||||
if (c->flags & CLIENT_PUBSUB && c->resp == 2) {
|
||||
addReply(c,shared.mbulkhdr[2]);
|
||||
addReplyBulkCBuffer(c,"pong",4);
|
||||
if (c->argc == 1)
|
||||
@ -2940,7 +2964,7 @@ void timeCommand(client *c) {
|
||||
/* gettimeofday() can only fail if &tv is a bad address so we
|
||||
* don't check for errors. */
|
||||
gettimeofday(&tv,NULL);
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkLongLong(c,tv.tv_sec);
|
||||
addReplyBulkLongLong(c,tv.tv_usec);
|
||||
}
|
||||
@ -2957,15 +2981,15 @@ int addReplyCommandFlag(client *c, struct redisCommand *cmd, int f, char *reply)
|
||||
/* Output the representation of a Redis command. Used by the COMMAND command. */
|
||||
void addReplyCommand(client *c, struct redisCommand *cmd) {
|
||||
if (!cmd) {
|
||||
addReply(c, shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
/* We are adding: command name, arg count, flags, first, last, offset */
|
||||
addReplyMultiBulkLen(c, 6);
|
||||
addReplyArrayLen(c, 6);
|
||||
addReplyBulkCString(c, cmd->name);
|
||||
addReplyLongLong(c, cmd->arity);
|
||||
|
||||
int flagcount = 0;
|
||||
void *flaglen = addDeferredMultiBulkLength(c);
|
||||
void *flaglen = addReplyDeferredLen(c);
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_WRITE, "write");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_READONLY, "readonly");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_DENYOOM, "denyoom");
|
||||
@ -2985,7 +3009,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
|
||||
addReplyStatus(c, "movablekeys");
|
||||
flagcount += 1;
|
||||
}
|
||||
setDeferredMultiBulkLength(c, flaglen, flagcount);
|
||||
setDeferredSetLen(c, flaglen, flagcount);
|
||||
|
||||
addReplyLongLong(c, cmd->firstkey);
|
||||
addReplyLongLong(c, cmd->lastkey);
|
||||
@ -3008,7 +3032,7 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (c->argc == 1) {
|
||||
addReplyMultiBulkLen(c, dictSize(server.commands));
|
||||
addReplyArrayLen(c, dictSize(server.commands));
|
||||
di = dictGetIterator(server.commands);
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
addReplyCommand(c, dictGetVal(de));
|
||||
@ -3016,7 +3040,7 @@ NULL
|
||||
dictReleaseIterator(di);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "info")) {
|
||||
int i;
|
||||
addReplyMultiBulkLen(c, c->argc-2);
|
||||
addReplyArrayLen(c, c->argc-2);
|
||||
for (i = 2; i < c->argc; i++) {
|
||||
addReplyCommand(c, dictFetchValue(server.commands, c->argv[i]->ptr));
|
||||
}
|
||||
@ -3043,7 +3067,7 @@ NULL
|
||||
if (!keys) {
|
||||
addReplyError(c,"Invalid arguments specified for command");
|
||||
} else {
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
addReplyArrayLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
|
||||
getKeysFreeResult(keys);
|
||||
}
|
||||
@ -4047,6 +4071,8 @@ int main(int argc, char **argv) {
|
||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
||||
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
||||
initServerConfig();
|
||||
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
|
||||
basic networking code and client creation depends on it. */
|
||||
moduleInitModulesSystem();
|
||||
|
||||
/* Store the executable path and arguments in a safe place in order
|
||||
|
94
src/server.h
94
src/server.h
@ -707,11 +707,51 @@ typedef struct readyList {
|
||||
robj *key;
|
||||
} readyList;
|
||||
|
||||
/* This structure represents a Redis user. This is useful for ACLs, the
|
||||
* user is associated to the connection after the connection is authenticated.
|
||||
* If there is no associated user, the connection uses the default user. */
|
||||
#define USER_MAX_COMMAND_BIT 1024 /* The first *not valid* bit that
|
||||
would overflow. So check for >= */
|
||||
#define USER_FLAG_ENABLED (1<<0) /* The user is active. */
|
||||
#define USER_FLAG_ALLKEYS (1<<1) /* The user can mention any key. */
|
||||
#define USER_FLAG_ALLCOMMANDS (1<<2) /* The user can run all commands. */
|
||||
#define USER_FLAG_NOPASS (1<<3) /* The user requires no password, any
|
||||
provided password will work. For the
|
||||
default user, this also means that
|
||||
no AUTH is needed, and every
|
||||
connection is immediately
|
||||
authenticated. */
|
||||
typedef struct user {
|
||||
sds name; /* The username as an SDS string. */
|
||||
uint64_t flags; /* See USER_FLAG_* */
|
||||
|
||||
/* The bit in allowed_commands is set if this user has the right to
|
||||
* execute this command. In commands having subcommands, if this bit is
|
||||
* set, then all the subcommands are also available.
|
||||
*
|
||||
* If the bit for a given command is NOT set and the command has
|
||||
* subcommands, Redis will also check allowed_subcommands in order to
|
||||
* understand if the command can be executed. */
|
||||
uint64_t allowed_commands[USER_MAX_COMMAND_BIT/64];
|
||||
|
||||
/* This array points, for each command ID (corresponding to the command
|
||||
* bit set in allowed_commands), to an array of SDS strings, terminated by
|
||||
* a NULL pointer, with all the sub commands that can be executed for
|
||||
* this command. When no subcommands matching is used, the field is just
|
||||
* set to NULL to avoid allocating USER_MAX_COMMAND_BIT pointers. */
|
||||
sds **allowed_subcommands;
|
||||
list *passwords; /* A list of SDS valid passwords for this user. */
|
||||
list *patterns; /* A list of allowed key patterns. If this field is NULL
|
||||
the user cannot mention any key in a command, unless
|
||||
the flag ALLKEYS is set in the user. */
|
||||
} user;
|
||||
|
||||
/* With multiplexing we need to take per-client state.
|
||||
* Clients are taken in a linked list. */
|
||||
typedef struct client {
|
||||
uint64_t id; /* Client incremental unique ID. */
|
||||
int fd; /* Client socket. */
|
||||
int resp; /* RESP protocol version. Can be 2 or 3. */
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
sds querybuf; /* Buffer we use to accumulate client queries. */
|
||||
@ -724,6 +764,9 @@ typedef struct client {
|
||||
int argc; /* Num of arguments of current command. */
|
||||
robj **argv; /* Arguments of current command. */
|
||||
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
|
||||
user *user; /* User associated with this connection. If the
|
||||
user is set to NULL the connection can do
|
||||
anything (admin). */
|
||||
int reqtype; /* Request protocol type: PROTO_REQ_* */
|
||||
int multibulklen; /* Number of multi bulk arguments left to read. */
|
||||
long bulklen; /* Length of bulk argument in multi bulk request. */
|
||||
@ -735,7 +778,7 @@ typedef struct client {
|
||||
time_t lastinteraction; /* Time of the last interaction, used for timeout */
|
||||
time_t obuf_soft_limit_reached_time;
|
||||
int flags; /* Client flags: CLIENT_* macros. */
|
||||
int authenticated; /* When requirepass is non-NULL. */
|
||||
int authenticated; /* Needed when the default user requires auth. */
|
||||
int replstate; /* Replication state if this is a slave. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
|
||||
int repldbfd; /* Replication DB file descriptor. */
|
||||
@ -780,9 +823,9 @@ struct moduleLoadQueueEntry {
|
||||
};
|
||||
|
||||
struct sharedObjectsStruct {
|
||||
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space,
|
||||
*colon, *nullbulk, *nullmultibulk, *queued,
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
|
||||
*colon, *queued, *null[4], *nullarray[4],
|
||||
*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
@ -945,7 +988,6 @@ struct redisServer {
|
||||
int shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||
int activerehashing; /* Incremental rehash in serverCron() */
|
||||
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
|
||||
char *requirepass; /* Pass for AUTH command, or NULL */
|
||||
char *pidfile; /* PID file path */
|
||||
int arch_bits; /* 32 or 64 depending on sizeof(long) */
|
||||
int cronloops; /* Number of times the cron function run */
|
||||
@ -1304,6 +1346,11 @@ struct redisCommand {
|
||||
int lastkey; /* The last argument that's a key */
|
||||
int keystep; /* The step between first and last key */
|
||||
long long microseconds, calls;
|
||||
int id; /* Command ID. This is a progressive ID starting from 0 that
|
||||
is assigned at runtime, and is used in order to check
|
||||
ACLs. A connection is able to execute a given command if
|
||||
the user associated to the connection has this command
|
||||
bit set in the bitmap of allowed commands. */
|
||||
};
|
||||
|
||||
struct redisFunctionSym {
|
||||
@ -1424,15 +1471,23 @@ void freeClient(client *c);
|
||||
void freeClientAsync(client *c);
|
||||
void resetClient(client *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void *addDeferredMultiBulkLength(client *c);
|
||||
void setDeferredMultiBulkLength(client *c, void *node, long length);
|
||||
void *addReplyDeferredLen(client *c);
|
||||
void setDeferredArrayLen(client *c, void *node, long length);
|
||||
void setDeferredMapLen(client *c, void *node, long length);
|
||||
void setDeferredSetLen(client *c, void *node, long length);
|
||||
void setDeferredAttributeLen(client *c, void *node, long length);
|
||||
void setDeferredPushLen(client *c, void *node, long length);
|
||||
void processInputBuffer(client *c);
|
||||
void processInputBufferAndReplicate(client *c);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReplyString(client *c, const char *s, size_t len);
|
||||
void addReplyNull(client *c);
|
||||
void addReplyNullArray(client *c);
|
||||
void addReplyBool(client *c, int b);
|
||||
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
|
||||
void addReplyProto(client *c, const char *s, size_t len);
|
||||
void addReplyBulk(client *c, robj *obj);
|
||||
void addReplyBulkCString(client *c, const char *s);
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
|
||||
@ -1445,9 +1500,14 @@ void addReplyStatus(client *c, const char *status);
|
||||
void addReplyDouble(client *c, double d);
|
||||
void addReplyHumanLongDouble(client *c, long double d);
|
||||
void addReplyLongLong(client *c, long long ll);
|
||||
void addReplyMultiBulkLen(client *c, long length);
|
||||
void addReplyArrayLen(client *c, long length);
|
||||
void addReplyMapLen(client *c, long length);
|
||||
void addReplySetLen(client *c, long length);
|
||||
void addReplyAttributeLen(client *c, long length);
|
||||
void addReplyPushLen(client *c, long length);
|
||||
void addReplyHelp(client *c, const char **help);
|
||||
void addReplySubcommandSyntaxError(client *c);
|
||||
void addReplyLoadedModules(client *c);
|
||||
void copyClientOutputBuffer(client *dst, client *src);
|
||||
size_t sdsZmallocSize(sds s);
|
||||
size_t getStringObjectSdsUsedMemory(robj *o);
|
||||
@ -1634,6 +1694,20 @@ void closeChildInfoPipe(void);
|
||||
void sendChildInfo(int process_type);
|
||||
void receiveChildInfo(void);
|
||||
|
||||
/* acl.c -- Authentication related prototypes. */
|
||||
extern user *DefaultUser;
|
||||
void ACLInit(void);
|
||||
/* Return values for ACLCheckUserCredentials(). */
|
||||
#define ACL_OK 0
|
||||
#define ACL_DENIED_CMD 1
|
||||
#define ACL_DENIED_KEY 2
|
||||
int ACLCheckUserCredentials(robj *username, robj *password);
|
||||
unsigned long ACLGetCommandID(const char *cmdname);
|
||||
user *ACLGetUserByName(const char *name, size_t namelen);
|
||||
int ACLCheckCommandPerm(client *c);
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
||||
sds ACLDefaultUserFirstPassword(void);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Input flags. */
|
||||
@ -2082,6 +2156,7 @@ void dumpCommand(client *c);
|
||||
void objectCommand(client *c);
|
||||
void memoryCommand(client *c);
|
||||
void clientCommand(client *c);
|
||||
void helloCommand(client *c);
|
||||
void evalCommand(client *c);
|
||||
void evalShaCommand(client *c);
|
||||
void scriptCommand(client *c);
|
||||
@ -2123,6 +2198,7 @@ void xinfoCommand(client *c);
|
||||
void xdelCommand(client *c);
|
||||
void xtrimCommand(client *c);
|
||||
void lolwutCommand(client *c);
|
||||
void aclCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
@ -169,23 +169,23 @@ NULL
|
||||
return;
|
||||
|
||||
listRewind(server.slowlog,&li);
|
||||
totentries = addDeferredMultiBulkLength(c);
|
||||
totentries = addReplyDeferredLen(c);
|
||||
while(count-- && (ln = listNext(&li))) {
|
||||
int j;
|
||||
|
||||
se = ln->value;
|
||||
addReplyMultiBulkLen(c,6);
|
||||
addReplyArrayLen(c,6);
|
||||
addReplyLongLong(c,se->id);
|
||||
addReplyLongLong(c,se->time);
|
||||
addReplyLongLong(c,se->duration);
|
||||
addReplyMultiBulkLen(c,se->argc);
|
||||
addReplyArrayLen(c,se->argc);
|
||||
for (j = 0; j < se->argc; j++)
|
||||
addReplyBulk(c,se->argv[j]);
|
||||
addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));
|
||||
addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));
|
||||
sent++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,totentries,sent);
|
||||
setDeferredArrayLen(c,totentries,sent);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
|
@ -505,7 +505,7 @@ void sortCommand(client *c) {
|
||||
addReplyError(c,"One or more scores can't be converted into double");
|
||||
} else if (storekey == NULL) {
|
||||
/* STORE option not specified, sent the sorting result to client */
|
||||
addReplyMultiBulkLen(c,outputlen);
|
||||
addReplyArrayLen(c,outputlen);
|
||||
for (j = start; j <= end; j++) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
@ -519,7 +519,7 @@ void sortCommand(client *c) {
|
||||
|
||||
if (sop->type == SORT_OP_GET) {
|
||||
if (!val) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
addReplyBulk(c,val);
|
||||
decrRefCount(val);
|
||||
|
29
src/t_hash.c
29
src/t_hash.c
@ -641,7 +641,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
int ret;
|
||||
|
||||
if (o == NULL) {
|
||||
addReply(c, shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -652,7 +652,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
|
||||
ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
|
||||
if (ret < 0) {
|
||||
addReply(c, shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
if (vstr) {
|
||||
addReplyBulkCBuffer(c, vstr, vlen);
|
||||
@ -664,7 +664,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
} else if (o->encoding == OBJ_ENCODING_HT) {
|
||||
sds value = hashTypeGetFromHashTable(o, field);
|
||||
if (value == NULL)
|
||||
addReply(c, shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
else
|
||||
addReplyBulkCBuffer(c, value, sdslen(value));
|
||||
} else {
|
||||
@ -675,7 +675,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
|
||||
void hgetCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL ||
|
||||
checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
addHashFieldToReply(c, o, c->argv[2]->ptr);
|
||||
@ -693,7 +693,7 @@ void hmgetCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c, c->argc-2);
|
||||
addReplyArrayLen(c, c->argc-2);
|
||||
for (i = 2; i < c->argc; i++) {
|
||||
addHashFieldToReply(c, o, c->argv[i]->ptr);
|
||||
}
|
||||
@ -766,17 +766,19 @@ static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int wh
|
||||
void genericHgetallCommand(client *c, int flags) {
|
||||
robj *o;
|
||||
hashTypeIterator *hi;
|
||||
int multiplier = 0;
|
||||
int length, count = 0;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL
|
||||
|| checkType(c,o,OBJ_HASH)) return;
|
||||
|
||||
if (flags & OBJ_HASH_KEY) multiplier++;
|
||||
if (flags & OBJ_HASH_VALUE) multiplier++;
|
||||
|
||||
length = hashTypeLength(o) * multiplier;
|
||||
addReplyMultiBulkLen(c, length);
|
||||
/* We return a map if the user requested keys and values, like in the
|
||||
* HGETALL case. Otherwise to use a flat array makes more sense. */
|
||||
length = hashTypeLength(o);
|
||||
if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {
|
||||
addReplyMapLen(c, length);
|
||||
} else {
|
||||
addReplyArrayLen(c, length);
|
||||
}
|
||||
|
||||
hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
@ -791,6 +793,9 @@ void genericHgetallCommand(client *c, int flags) {
|
||||
}
|
||||
|
||||
hashTypeReleaseIterator(hi);
|
||||
|
||||
/* Make sure we returned the right number of elements. */
|
||||
if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) count /= 2;
|
||||
serverAssert(count == length);
|
||||
}
|
||||
|
||||
|
32
src/t_list.c
32
src/t_list.c
@ -298,7 +298,7 @@ void linsertCommand(client *c) {
|
||||
server.dirty++;
|
||||
} else {
|
||||
/* Notify client of a failed insert */
|
||||
addReply(c,shared.cnegone);
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -312,7 +312,7 @@ void llenCommand(client *c) {
|
||||
}
|
||||
|
||||
void lindexCommand(client *c) {
|
||||
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk);
|
||||
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]);
|
||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||
long index;
|
||||
robj *value = NULL;
|
||||
@ -331,7 +331,7 @@ void lindexCommand(client *c) {
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
@ -365,12 +365,12 @@ void lsetCommand(client *c) {
|
||||
}
|
||||
|
||||
void popGenericCommand(client *c, int where) {
|
||||
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
|
||||
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]);
|
||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||
|
||||
robj *value = listTypePop(o,where);
|
||||
if (value == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
|
||||
|
||||
@ -402,7 +402,7 @@ void lrangeCommand(client *c) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL
|
||||
|| checkType(c,o,OBJ_LIST)) return;
|
||||
llen = listTypeLength(o);
|
||||
|
||||
@ -414,14 +414,14 @@ void lrangeCommand(client *c) {
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
if (end >= llen) end = llen-1;
|
||||
rangelen = (end-start)+1;
|
||||
|
||||
/* Return the result in form of a multi-bulk reply */
|
||||
addReplyMultiBulkLen(c,rangelen);
|
||||
addReplyArrayLen(c,rangelen);
|
||||
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
listTypeIterator *iter = listTypeInitIterator(o, start, LIST_TAIL);
|
||||
|
||||
@ -564,13 +564,13 @@ void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value) {
|
||||
|
||||
void rpoplpushCommand(client *c) {
|
||||
robj *sobj, *value;
|
||||
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,sobj,OBJ_LIST)) return;
|
||||
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,sobj,OBJ_LIST)) return;
|
||||
|
||||
if (listTypeLength(sobj) == 0) {
|
||||
/* This may only happen after loading very old RDB files. Recent
|
||||
* versions of Redis delete keys of empty lists. */
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
|
||||
robj *touchedkey = c->argv[1];
|
||||
@ -639,10 +639,10 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
|
||||
/* BRPOP/BLPOP */
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
addReplyArrayLen(receiver,2);
|
||||
addReplyBulk(receiver,key);
|
||||
addReplyBulk(receiver,value);
|
||||
|
||||
|
||||
/* Notify event. */
|
||||
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,event,key,receiver->db->id);
|
||||
@ -704,7 +704,7 @@ void blockingPopGenericCommand(client *c, int where) {
|
||||
robj *value = listTypePop(o,where);
|
||||
serverAssert(value != NULL);
|
||||
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
@ -731,7 +731,7 @@ void blockingPopGenericCommand(client *c, int where) {
|
||||
/* If we are inside a MULTI/EXEC and the list is empty the only thing
|
||||
* we can do is treating it as a timeout (even with timeout 0). */
|
||||
if (c->flags & CLIENT_MULTI) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -759,7 +759,7 @@ void brpoplpushCommand(client *c) {
|
||||
if (c->flags & CLIENT_MULTI) {
|
||||
/* Blocking against an empty list in a multi state
|
||||
* returns immediately. */
|
||||
addReply(c, shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
/* The list is empty and the client blocks. */
|
||||
blockForKeys(c,BLOCKED_LIST,c->argv + 1,1,timeout,c->argv[2],NULL);
|
||||
|
30
src/t_set.c
30
src/t_set.c
@ -415,13 +415,13 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set. Otherwise, return nil */
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk))
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
/* If count is zero, serve an empty multibulk ASAP to avoid special
|
||||
* cases later. */
|
||||
if (count == 0) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -455,7 +455,7 @@ void spopWithCountCommand(client *c) {
|
||||
robj *propargv[3];
|
||||
propargv[0] = createStringObject("SREM",4);
|
||||
propargv[1] = c->argv[1];
|
||||
addReplyMultiBulkLen(c,count);
|
||||
addReplySetLen(c,count);
|
||||
|
||||
/* Common iteration vars. */
|
||||
sds sdsele;
|
||||
@ -566,8 +566,8 @@ void spopCommand(client *c) {
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set */
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,set,OBJ_SET)) return;
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
/* Get a random element from the set */
|
||||
encoding = setTypeRandomElement(set,&sdsele,&llele);
|
||||
@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
|
||||
uniq = 0;
|
||||
}
|
||||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk))
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
size = setTypeSize(set);
|
||||
|
||||
/* If count is zero, serve it ASAP to avoid special cases later. */
|
||||
if (count == 0) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -647,7 +647,7 @@ void srandmemberWithCountCommand(client *c) {
|
||||
* This case is trivial and can be served without auxiliary data
|
||||
* structures. */
|
||||
if (!uniq) {
|
||||
addReplyMultiBulkLen(c,count);
|
||||
addReplySetLen(c,count);
|
||||
while(count--) {
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == OBJ_ENCODING_INTSET) {
|
||||
@ -737,7 +737,7 @@ void srandmemberWithCountCommand(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,count);
|
||||
addReplySetLen(c,count);
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL)
|
||||
addReplyBulk(c,dictGetKey(de));
|
||||
@ -760,8 +760,8 @@ void srandmemberCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,set,OBJ_SET)) return;
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == OBJ_ENCODING_INTSET) {
|
||||
@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -833,7 +833,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
* to the output list and save the pointer to later modify it with the
|
||||
* right length */
|
||||
if (!dstkey) {
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
} else {
|
||||
/* If we have a target key where to store the resulting set
|
||||
* create this key with an empty set inside */
|
||||
@ -911,7 +911,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
} else {
|
||||
setDeferredMultiBulkLength(c,replylen,cardinality);
|
||||
setDeferredSetLen(c,replylen,cardinality);
|
||||
}
|
||||
zfree(sets);
|
||||
}
|
||||
@ -1057,7 +1057,7 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
|
||||
/* Output the content of the resulting set, if not in STORE mode */
|
||||
if (!dstkey) {
|
||||
addReplyMultiBulkLen(c,cardinality);
|
||||
addReplySetLen(c,cardinality);
|
||||
si = setTypeInitIterator(dstset);
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
addReplyBulkCBuffer(c,ele,sdslen(ele));
|
||||
|
@ -914,7 +914,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
}
|
||||
|
||||
if (!(flags & STREAM_RWR_RAWENTRIES))
|
||||
arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
arraylen_ptr = addReplyDeferredLen(c);
|
||||
streamIteratorStart(&si,s,start,end,rev);
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
/* Update the group last_id if needed. */
|
||||
@ -925,9 +925,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
|
||||
/* Emit a two elements array for each item. The first is
|
||||
* the ID, the second is an array of field-value pairs. */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyStreamID(c,&id);
|
||||
addReplyMultiBulkLen(c,numfields*2);
|
||||
|
||||
addReplyMapLen(c,numfields);
|
||||
|
||||
/* Emit the field-value pairs. */
|
||||
while(numfields--) {
|
||||
@ -993,7 +994,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
if (count && count == arraylen) break;
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
if (arraylen_ptr) setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
|
||||
if (arraylen_ptr) setDeferredArrayLen(c,arraylen_ptr,arraylen);
|
||||
return arraylen;
|
||||
}
|
||||
|
||||
@ -1018,7 +1019,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
|
||||
if (end) streamEncodeID(endkey,end);
|
||||
|
||||
size_t arraylen = 0;
|
||||
void *arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||
raxStart(&ri,consumer->pel);
|
||||
raxSeek(&ri,">=",startkey,sizeof(startkey));
|
||||
while(raxNext(&ri) && (!count || arraylen < count)) {
|
||||
@ -1032,11 +1033,11 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
|
||||
* about a message that's no longer here because was removed
|
||||
* by the user by other means. In that case we signal it emitting
|
||||
* the ID but then a NULL entry for the fields. */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
streamID id;
|
||||
streamDecodeID(ri.key,&id);
|
||||
addReplyStreamID(c,&id);
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
streamNACK *nack = ri.data;
|
||||
nack->delivery_time = mstime();
|
||||
@ -1045,7 +1046,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
|
||||
arraylen++;
|
||||
}
|
||||
raxStop(&ri);
|
||||
setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen);
|
||||
return arraylen;
|
||||
}
|
||||
|
||||
@ -1286,12 +1287,13 @@ void xrangeGenericCommand(client *c, int rev) {
|
||||
}
|
||||
|
||||
/* Return the specified range to the user. */
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|
||||
|| checkType(c,o,OBJ_STREAM)) return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL ||
|
||||
checkType(c,o,OBJ_STREAM)) return;
|
||||
|
||||
s = o->ptr;
|
||||
|
||||
if (count == 0) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
if (count == -1) count = 0;
|
||||
streamReplyWithRange(c,s,&startid,&endid,count,rev,NULL,NULL,0,NULL);
|
||||
@ -1505,7 +1507,7 @@ void xreadCommand(client *c) {
|
||||
|
||||
if (serve_synchronously) {
|
||||
arraylen++;
|
||||
if (arraylen == 1) arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
if (arraylen == 1) arraylen_ptr = addReplyDeferredLen(c);
|
||||
/* streamReplyWithRange() handles the 'start' ID as inclusive,
|
||||
* so start from the next ID, since we want only messages with
|
||||
* IDs greater than start. */
|
||||
@ -1514,7 +1516,7 @@ void xreadCommand(client *c) {
|
||||
|
||||
/* Emit the two elements sub-array consisting of the name
|
||||
* of the stream and the data we extracted from it. */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
if (c->resp == 2) addReplyArrayLen(c,2);
|
||||
addReplyBulk(c,c->argv[streams_arg+i]);
|
||||
streamConsumer *consumer = NULL;
|
||||
if (groups) consumer = streamLookupConsumer(groups[i],
|
||||
@ -1532,7 +1534,10 @@ void xreadCommand(client *c) {
|
||||
|
||||
/* We replied synchronously! Set the top array len and return to caller. */
|
||||
if (arraylen) {
|
||||
setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
|
||||
if (c->resp == 2)
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen);
|
||||
else
|
||||
setDeferredMapLen(c,arraylen_ptr,arraylen);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@ -1541,7 +1546,7 @@ void xreadCommand(client *c) {
|
||||
/* If we are inside a MULTI/EXEC and the list is empty the only thing
|
||||
* we can do is treating it as a timeout (even with timeout 0). */
|
||||
if (c->flags & CLIENT_MULTI) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
goto cleanup;
|
||||
}
|
||||
blockForKeys(c, BLOCKED_STREAM, c->argv+streams_arg, streams_count,
|
||||
@ -1570,7 +1575,7 @@ void xreadCommand(client *c) {
|
||||
|
||||
/* No BLOCK option, nor any stream we can serve. Reply as with a
|
||||
* timeout happened. */
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
/* Continue to cleanup... */
|
||||
|
||||
cleanup: /* Cleanup. */
|
||||
@ -1960,14 +1965,14 @@ void xpendingCommand(client *c) {
|
||||
|
||||
/* XPENDING <key> <group> variant. */
|
||||
if (justinfo) {
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyArrayLen(c,4);
|
||||
/* Total number of messages in the PEL. */
|
||||
addReplyLongLong(c,raxSize(group->pel));
|
||||
/* First and last IDs. */
|
||||
if (raxSize(group->pel) == 0) {
|
||||
addReply(c,shared.nullbulk); /* Start. */
|
||||
addReply(c,shared.nullbulk); /* End. */
|
||||
addReply(c,shared.nullmultibulk); /* Clients. */
|
||||
addReplyNull(c); /* Start. */
|
||||
addReplyNull(c); /* End. */
|
||||
addReplyNullArray(c); /* Clients. */
|
||||
} else {
|
||||
/* Start. */
|
||||
raxIterator ri;
|
||||
@ -1987,17 +1992,17 @@ void xpendingCommand(client *c) {
|
||||
/* Consumers with pending messages. */
|
||||
raxStart(&ri,group->consumers);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
void *arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||
size_t arraylen = 0;
|
||||
while(raxNext(&ri)) {
|
||||
streamConsumer *consumer = ri.data;
|
||||
if (raxSize(consumer->pel) == 0) continue;
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c,ri.key,ri.key_len);
|
||||
addReplyBulkLongLong(c,raxSize(consumer->pel));
|
||||
arraylen++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen);
|
||||
raxStop(&ri);
|
||||
}
|
||||
}
|
||||
@ -2010,7 +2015,7 @@ void xpendingCommand(client *c) {
|
||||
/* If a consumer name was mentioned but it does not exist, we can
|
||||
* just return an empty array. */
|
||||
if (consumername && consumer == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
addReplyArrayLen(c,0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2024,7 +2029,7 @@ void xpendingCommand(client *c) {
|
||||
streamEncodeID(endkey,&endid);
|
||||
raxStart(&ri,pel);
|
||||
raxSeek(&ri,">=",startkey,sizeof(startkey));
|
||||
void *arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||
size_t arraylen = 0;
|
||||
|
||||
while(count && raxNext(&ri) && memcmp(ri.key,endkey,ri.key_len) <= 0) {
|
||||
@ -2032,7 +2037,7 @@ void xpendingCommand(client *c) {
|
||||
|
||||
arraylen++;
|
||||
count--;
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyArrayLen(c,4);
|
||||
|
||||
/* Entry ID. */
|
||||
streamID id;
|
||||
@ -2052,7 +2057,7 @@ void xpendingCommand(client *c) {
|
||||
addReplyLongLong(c,nack->delivery_count);
|
||||
}
|
||||
raxStop(&ri);
|
||||
setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2221,7 +2226,7 @@ void xclaimCommand(client *c) {
|
||||
|
||||
/* Do the actual claiming. */
|
||||
streamConsumer *consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
|
||||
void *arraylenptr = addDeferredMultiBulkLength(c);
|
||||
void *arraylenptr = addReplyDeferredLen(c);
|
||||
size_t arraylen = 0;
|
||||
for (int j = 5; j <= last_id_arg; j++) {
|
||||
streamID id;
|
||||
@ -2284,7 +2289,7 @@ void xclaimCommand(client *c) {
|
||||
} else {
|
||||
size_t emitted = streamReplyWithRange(c,o->ptr,&id,&id,1,0,
|
||||
NULL,NULL,STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!emitted) addReply(c,shared.nullbulk);
|
||||
if (!emitted) addReplyNull(c);
|
||||
}
|
||||
arraylen++;
|
||||
|
||||
@ -2298,7 +2303,7 @@ void xclaimCommand(client *c) {
|
||||
streamPropagateGroupID(c,c->argv[1],group,c->argv[2]);
|
||||
server.dirty++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,arraylenptr,arraylen);
|
||||
setDeferredArrayLen(c,arraylenptr,arraylen);
|
||||
preventCommandPropagation(c);
|
||||
}
|
||||
|
||||
@ -2463,7 +2468,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,raxSize(cg->consumers));
|
||||
addReplyArrayLen(c,raxSize(cg->consumers));
|
||||
raxIterator ri;
|
||||
raxStart(&ri,cg->consumers);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
@ -2473,7 +2478,7 @@ NULL
|
||||
mstime_t idle = now - consumer->seen_time;
|
||||
if (idle < 0) idle = 0;
|
||||
|
||||
addReplyMultiBulkLen(c,6);
|
||||
addReplyMapLen(c,3);
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCBuffer(c,consumer->name,sdslen(consumer->name));
|
||||
addReplyBulkCString(c,"pending");
|
||||
@ -2485,17 +2490,17 @@ NULL
|
||||
} else if (!strcasecmp(opt,"GROUPS") && c->argc == 3) {
|
||||
/* XINFO GROUPS <key>. */
|
||||
if (s->cgroups == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
addReplyArrayLen(c,0);
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,raxSize(s->cgroups));
|
||||
addReplyArrayLen(c,raxSize(s->cgroups));
|
||||
raxIterator ri;
|
||||
raxStart(&ri,s->cgroups);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
streamCG *cg = ri.data;
|
||||
addReplyMultiBulkLen(c,8);
|
||||
addReplyMapLen(c,4);
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCBuffer(c,ri.key,ri.key_len);
|
||||
addReplyBulkCString(c,"consumers");
|
||||
@ -2508,7 +2513,7 @@ NULL
|
||||
raxStop(&ri);
|
||||
} else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
|
||||
/* XINFO STREAM <key> (or the alias XINFO <key>). */
|
||||
addReplyMultiBulkLen(c,14);
|
||||
addReplyMapLen(c,7);
|
||||
addReplyBulkCString(c,"length");
|
||||
addReplyLongLong(c,s->length);
|
||||
addReplyBulkCString(c,"radix-tree-keys");
|
||||
@ -2529,11 +2534,11 @@ NULL
|
||||
addReplyBulkCString(c,"first-entry");
|
||||
count = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!count) addReply(c,shared.nullbulk);
|
||||
if (!count) addReplyNull(c);
|
||||
addReplyBulkCString(c,"last-entry");
|
||||
count = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
|
||||
STREAM_RWR_RAWENTRIES,NULL);
|
||||
if (!count) addReply(c,shared.nullbulk);
|
||||
if (!count) addReplyNull(c);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
|
||||
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
|
||||
{
|
||||
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
return;
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
@ -157,7 +157,7 @@ void psetexCommand(client *c) {
|
||||
int getGenericCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL)
|
||||
return C_OK;
|
||||
|
||||
if (o->type != OBJ_STRING) {
|
||||
@ -285,14 +285,14 @@ void getrangeCommand(client *c) {
|
||||
void mgetCommand(client *c) {
|
||||
int j;
|
||||
|
||||
addReplyMultiBulkLen(c,c->argc-1);
|
||||
addReplyArrayLen(c,c->argc-1);
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
if (o->type != OBJ_STRING) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
addReplyBulk(c,o);
|
||||
}
|
||||
|
85
src/t_zset.c
85
src/t_zset.c
@ -1638,7 +1638,7 @@ reply_to_client:
|
||||
if (processed)
|
||||
addReplyDouble(c,score);
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else { /* ZADD. */
|
||||
addReplyLongLong(c,ch ? added+updated : added);
|
||||
}
|
||||
@ -2427,7 +2427,7 @@ void zrangeGenericCommand(client *c, int reverse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL
|
||||
|| checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
/* Sanitize indexes. */
|
||||
@ -2439,14 +2439,19 @@ void zrangeGenericCommand(client *c, int reverse) {
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
if (end >= llen) end = llen-1;
|
||||
rangelen = (end-start)+1;
|
||||
|
||||
/* Return the result in form of a multi-bulk reply */
|
||||
addReplyMultiBulkLen(c, withscores ? (rangelen*2) : rangelen);
|
||||
/* Return the result in form of a multi-bulk reply. RESP3 clients
|
||||
* will receive sub arrays with score->element, while RESP2 returned
|
||||
* a flat array. */
|
||||
if (withscores && c->resp == 2)
|
||||
addReplyArrayLen(c, rangelen*2);
|
||||
else
|
||||
addReplyArrayLen(c, rangelen);
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
@ -2466,13 +2471,13 @@ void zrangeGenericCommand(client *c, int reverse) {
|
||||
while (rangelen--) {
|
||||
serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
|
||||
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
|
||||
|
||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
||||
if (vstr == NULL)
|
||||
addReplyBulkLongLong(c,vlong);
|
||||
else
|
||||
addReplyBulkCBuffer(c,vstr,vlen);
|
||||
|
||||
if (withscores)
|
||||
addReplyDouble(c,zzlGetScore(sptr));
|
||||
if (withscores) addReplyDouble(c,zzlGetScore(sptr));
|
||||
|
||||
if (reverse)
|
||||
zzlPrev(zl,&eptr,&sptr);
|
||||
@ -2500,9 +2505,9 @@ void zrangeGenericCommand(client *c, int reverse) {
|
||||
while(rangelen--) {
|
||||
serverAssertWithInfo(c,zobj,ln != NULL);
|
||||
ele = ln->ele;
|
||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c,ele,sdslen(ele));
|
||||
if (withscores)
|
||||
addReplyDouble(c,ln->score);
|
||||
if (withscores) addReplyDouble(c,ln->score);
|
||||
ln = reverse ? ln->backward : ln->level[0].forward;
|
||||
}
|
||||
} else {
|
||||
@ -2570,7 +2575,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
}
|
||||
|
||||
/* Ok, lookup the key and get the range */
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@ -2590,7 +2595,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
|
||||
/* No "first" element in the specified interval. */
|
||||
if (eptr == NULL) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2601,7 +2606,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
/* We don't know in advance how many matching elements there are in the
|
||||
* list, so we push this object that will represent the multi-bulk
|
||||
* length in the output buffer, and will "fix" it later */
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
|
||||
/* If there is an offset, just traverse the number of elements without
|
||||
* checking the score because that is done in the next loop. */
|
||||
@ -2623,19 +2628,18 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
if (!zslValueLteMax(score,&range)) break;
|
||||
}
|
||||
|
||||
/* We know the element exists, so ziplistGet should always succeed */
|
||||
/* We know the element exists, so ziplistGet should always
|
||||
* succeed */
|
||||
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
|
||||
|
||||
rangelen++;
|
||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
||||
if (vstr == NULL) {
|
||||
addReplyBulkLongLong(c,vlong);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,vstr,vlen);
|
||||
}
|
||||
|
||||
if (withscores) {
|
||||
addReplyDouble(c,score);
|
||||
}
|
||||
if (withscores) addReplyDouble(c,score);
|
||||
|
||||
/* Move to next node */
|
||||
if (reverse) {
|
||||
@ -2658,14 +2662,14 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
|
||||
/* No "first" element in the specified interval. */
|
||||
if (ln == NULL) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We don't know in advance how many matching elements there are in the
|
||||
* list, so we push this object that will represent the multi-bulk
|
||||
* length in the output buffer, and will "fix" it later */
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
|
||||
/* If there is an offset, just traverse the number of elements without
|
||||
* checking the score because that is done in the next loop. */
|
||||
@ -2686,11 +2690,9 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
}
|
||||
|
||||
rangelen++;
|
||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));
|
||||
|
||||
if (withscores) {
|
||||
addReplyDouble(c,ln->score);
|
||||
}
|
||||
if (withscores) addReplyDouble(c,ln->score);
|
||||
|
||||
/* Move to next node */
|
||||
if (reverse) {
|
||||
@ -2703,11 +2705,8 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (withscores) {
|
||||
rangelen *= 2;
|
||||
}
|
||||
|
||||
setDeferredMultiBulkLength(c, replylen, rangelen);
|
||||
if (withscores && c->resp == 2) rangelen *= 2;
|
||||
setDeferredArrayLen(c, replylen, rangelen);
|
||||
}
|
||||
|
||||
void zrangebyscoreCommand(client *c) {
|
||||
@ -2918,7 +2917,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
}
|
||||
|
||||
/* Ok, lookup the key and get the range */
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
checkType(c,zobj,OBJ_ZSET))
|
||||
{
|
||||
zslFreeLexRange(&range);
|
||||
@ -2941,7 +2940,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
|
||||
/* No "first" element in the specified interval. */
|
||||
if (eptr == NULL) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
zslFreeLexRange(&range);
|
||||
return;
|
||||
}
|
||||
@ -2953,7 +2952,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
/* We don't know in advance how many matching elements there are in the
|
||||
* list, so we push this object that will represent the multi-bulk
|
||||
* length in the output buffer, and will "fix" it later */
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
|
||||
/* If there is an offset, just traverse the number of elements without
|
||||
* checking the score because that is done in the next loop. */
|
||||
@ -3005,7 +3004,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
|
||||
/* No "first" element in the specified interval. */
|
||||
if (ln == NULL) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
zslFreeLexRange(&range);
|
||||
return;
|
||||
}
|
||||
@ -3013,7 +3012,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
/* We don't know in advance how many matching elements there are in the
|
||||
* list, so we push this object that will represent the multi-bulk
|
||||
* length in the output buffer, and will "fix" it later */
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
|
||||
/* If there is an offset, just traverse the number of elements without
|
||||
* checking the score because that is done in the next loop. */
|
||||
@ -3048,7 +3047,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
}
|
||||
|
||||
zslFreeLexRange(&range);
|
||||
setDeferredMultiBulkLength(c, replylen, rangelen);
|
||||
setDeferredArrayLen(c, replylen, rangelen);
|
||||
}
|
||||
|
||||
void zrangebylexCommand(client *c) {
|
||||
@ -3074,11 +3073,11 @@ void zscoreCommand(client *c) {
|
||||
robj *zobj;
|
||||
double score;
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
if (zsetScore(zobj,c->argv[2]->ptr,&score) == C_ERR) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
addReplyDouble(c,score);
|
||||
}
|
||||
@ -3090,7 +3089,7 @@ void zrankGenericCommand(client *c, int reverse) {
|
||||
robj *zobj;
|
||||
long rank;
|
||||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
|
||||
checkType(c,zobj,OBJ_ZSET)) return;
|
||||
|
||||
serverAssertWithInfo(c,ele,sdsEncodedObject(ele));
|
||||
@ -3098,7 +3097,7 @@ void zrankGenericCommand(client *c, int reverse) {
|
||||
if (rank >= 0) {
|
||||
addReplyLongLong(c,rank);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3156,11 +3155,11 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
|
||||
/* No candidate for zpopping, return empty. */
|
||||
if (!zobj) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
void *arraylen_ptr = addDeferredMultiBulkLength(c);
|
||||
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||
long arraylen = 0;
|
||||
|
||||
/* We emit the key only for the blocking variant. */
|
||||
@ -3227,7 +3226,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
}
|
||||
} while(--count);
|
||||
|
||||
setDeferredMultiBulkLength(c,arraylen_ptr,arraylen + (emitkey != 0));
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen + (emitkey != 0));
|
||||
}
|
||||
|
||||
/* ZPOPMIN key [<count>] */
|
||||
@ -3282,7 +3281,7 @@ void blockingGenericZpopCommand(client *c, int where) {
|
||||
/* If we are inside a MULTI/EXEC and the zset is empty the only thing
|
||||
* we can do is treating it as a timeout (even with timeout 0). */
|
||||
if (c->flags & CLIENT_MULTI) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -275,7 +275,6 @@ start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
test "Master stream is correctly processed while the replica has a script in -BUSY state" {
|
||||
set slave [srv 0 client]
|
||||
puts [srv 0 port]
|
||||
$slave config set lua-time-limit 500
|
||||
$slave slaveof $master_host $master_port
|
||||
|
||||
|
@ -2,14 +2,14 @@ start_server {tags {"auth"}} {
|
||||
test {AUTH fails if there is no password configured server side} {
|
||||
catch {r auth foo} err
|
||||
set _ $err
|
||||
} {ERR*no password*}
|
||||
} {ERR*any password*}
|
||||
}
|
||||
|
||||
start_server {tags {"auth"} overrides {requirepass foobar}} {
|
||||
test {AUTH fails when a wrong password is given} {
|
||||
catch {r auth wrong!} err
|
||||
set _ $err
|
||||
} {ERR*invalid password}
|
||||
} {WRONGPASS*}
|
||||
|
||||
test {Arbitrary command gives an error when AUTH is required} {
|
||||
catch {r set foo bar} err
|
||||
|
@ -362,7 +362,7 @@ start_server {tags {"dump"}} {
|
||||
r -1 lpush list a b c d
|
||||
$second config set requirepass foobar2
|
||||
catch {r -1 migrate $second_host $second_port list 9 5000 AUTH foobar} err
|
||||
assert_match {*invalid password*} $err
|
||||
assert_match {*WRONGPASS*} $err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user