From 009a9292694491ff9eec78c024d38b0b5ca83f2e Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 12 Dec 2018 11:55:30 +0100 Subject: [PATCH 001/122] Remove debugging printf from replication.tcl test. --- tests/integration/replication.tcl | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/replication.tcl b/tests/integration/replication.tcl index b61dfac1..0e50c20a 100644 --- a/tests/integration/replication.tcl +++ b/tests/integration/replication.tcl @@ -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 From 143bfa1e6e65cf8be1eaad0b8169e2d95ca62f9a Mon Sep 17 00:00:00 2001 From: artix Date: Tue, 18 Dec 2018 17:38:57 +0100 Subject: [PATCH 002/122] Cluster Manager: compare key values after BUSYKEY error (migration). If a key exists in the target node during a migration (BUSYKEY), the value of the key on both nodes (source and target) will be compared. If the key has the same value on both keys, the migration will be automatically retried with the REPLACE argument in order to override the target's key. If the key has different values, the behaviour will depend on such cases: - In case of 'fix' command, the migration will stop and the user will be warned to manually check the key(s). - In other cases (ie. reshard), if the user launched the command with the --cluster-replace option, the migration will be retried with the REPLACE argument, elsewhere the migration will stop and the user will be warned. --- src/redis-cli.c | 133 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 9 deletions(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index a93bd9b1..b0a12ebb 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -2934,6 +2934,68 @@ 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); + 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 +3076,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 +3113,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 +3127,69 @@ 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, " + "checking values...\n"); + list *diffs = listCreate(); + success = clusterManagerCompareKeysValues(source, + target, reply, diffs); + if (!success && (do_fix || !do_replace)) { + listRelease(diffs); + clusterManagerLogErr("*** Value check failed!\n"); + goto next; + } + if (listLength(diffs) > 0 && (do_fix || !do_replace)) { + 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 "); + if (do_fix) + clusterManagerLogErr("and try again!\n"); + else { + clusterManagerLogErr("or relaunch the command " + "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, From cc29590188a22eb73cfbbef39fce73c7467b1edf Mon Sep 17 00:00:00 2001 From: artix Date: Tue, 18 Dec 2018 18:39:21 +0100 Subject: [PATCH 003/122] Fixed memory leak in clusterManagerCompareKeysValues. --- src/redis-cli.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/redis-cli.c b/src/redis-cli.c index b0a12ebb..705c7483 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -2993,6 +2993,8 @@ static int clusterManagerCompareKeysValues(clusterManagerNode *n1, cleanup: if (r1) freeReplyObject(r1); if (r2) freeReplyObject(r2); + zfree(argv); + zfree(argv_len); return success; } From 503fd229e4181e932ba74b3ca8a222712d80ebca Mon Sep 17 00:00:00 2001 From: artix Date: Wed, 19 Dec 2018 17:27:58 +0100 Subject: [PATCH 004/122] Cluster Manager: enable --cluster-replace also for 'fix' command. --- src/redis-cli.c | 69 ++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index 705c7483..6fe93e66 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -3149,48 +3149,47 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source, * - In other cases (ie. reshard), proceed only if the user * launched the command with the --cluster-replace option.*/ if (is_busy) { - clusterManagerLogWarn("\n*** Target key exists, " - "checking values...\n"); - list *diffs = listCreate(); - success = clusterManagerCompareKeysValues(source, - target, reply, diffs); - if (!success && (do_fix || !do_replace)) { - listRelease(diffs); - clusterManagerLogErr("*** Value check failed!\n"); - goto next; - } - if (listLength(diffs) > 0 && (do_fix || !do_replace)) { - 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); + 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; } - clusterManagerLogErr("Please fix the above key(s) " - "manually "); - if (do_fix) - clusterManagerLogErr("and try again!\n"); - else { - clusterManagerLogErr("or relaunch the command " + 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); - goto next; } - listRelease(diffs); clusterManagerLogWarn("*** Replacing target keys...\n"); } freeReplyObject(migrate_reply); From 17797660f133b4d0491098f656944f46757e2edd Mon Sep 17 00:00:00 2001 From: artix Date: Thu, 27 Dec 2018 17:20:38 +0100 Subject: [PATCH 005/122] Cluster Manager del-node: use CLUSTER RESET in place of SHUTDOWN See issue #5687 --- src/redis-cli.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index 6fe93e66..bfd245b4 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -5184,9 +5184,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; From cc47dacd18723665f00ad43f71fd28b991d21044 Mon Sep 17 00:00:00 2001 From: Uman Shahzad Date: Thu, 3 Jan 2019 17:47:19 +0500 Subject: [PATCH 006/122] Remove documentation about geohash-int in deps repo. --- README.md | 2 +- deps/README.md | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 4b1a9832..2b4eeb19 100644 --- a/README.md +++ b/README.md @@ -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, diff --git a/deps/README.md b/deps/README.md index 367ee162..685dbb40 100644 --- a/deps/README.md +++ b/deps/README.md @@ -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 --- From 30d8d05cd677d1cc9f5a9bf85337af43416422ec Mon Sep 17 00:00:00 2001 From: chenyangyang Date: Sun, 6 Jan 2019 15:01:25 +0800 Subject: [PATCH 007/122] Update ae.c Update comment --- src/ae.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ae.c b/src/ae.c index 1ea67156..53629ef7 100644 --- a/src/ae.c +++ b/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) From 914ee43108c5a742c160c7a50d190c15714f9c66 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 7 Nov 2018 17:40:35 +0100 Subject: [PATCH 008/122] RESP3: Double replies and aggregate lengths initial functions. --- src/networking.c | 49 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/networking.c b/src/networking.c index 74b857e0..51feefad 100644 --- a/src/networking.c +++ b/src/networking.c @@ -469,16 +469,15 @@ void setDeferredMultiBulkLength(client *c, void *node, long length) { /* 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"); + addReplyString(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]; + int dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d); + addReplyString(c,dbuf,dlen); } } @@ -486,9 +485,11 @@ 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); + char buf[MAX_LONG_DOUBLE_CHARS]; + int len = ld2string(buf,sizeof(buf),d,1); + addReplyString(c,",",1); + addReplyString(c,buf,len); + addReplyString(c,"\r\n",2); } /* Add a long long as integer reply or bulk len / multi bulk count. @@ -524,11 +525,35 @@ 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) { + addReplyAggregateLen(c,length,'%'); +} + +void addReplySetLen(client *c, long length) { + addReplyAggregateLen(c,length,'~'); +} + +void addReplyAttributeLen(client *c, long length) { + addReplyAggregateLen(c,length,'|'); +} + +void addReplyPushLen(client *c, long length) { + addReplyAggregateLen(c,length,'>'); +} + +void addReplyHelloLen(client *c, long length) { + addReplyAggregateLen(c,length,'@'); } /* Create the length prefix of a bulk reply, example: $2234 */ From 57c5a766a23130cb47a801200abc2ca7cf4a2a38 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 8 Nov 2018 12:28:56 +0100 Subject: [PATCH 009/122] RESP3: Aggregate deferred lengths functions. --- src/networking.c | 40 ++++++++++++++++++++++++++++++++-------- src/server.h | 9 +++++++-- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/networking.c b/src/networking.c index 51feefad..37e94706 100644 --- a/src/networking.c +++ b/src/networking.c @@ -416,28 +416,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,6 +467,30 @@ 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) { + setDeferredAggregateLen(c,node,length,'%'); +} + +void setDeferredSetLen(client *c, void *node, long length) { + setDeferredAggregateLen(c,node,length,'~'); +} + +void setDeferredAttributeLen(client *c, void *node, long length) { + setDeferredAggregateLen(c,node,length,'|'); +} + +void setDeferredPushLen(client *c, void *node, long length) { + setDeferredAggregateLen(c,node,length,'>'); +} + +void setDeferredHelloLen(client *c, void *node, long length) { + setDeferredAggregateLen(c,node,length,'@'); +} + /* Add a double as a bulk reply */ void addReplyDouble(client *c, double d) { if (isinf(d)) { @@ -627,7 +651,7 @@ void addReplyBulkLongLong(client *c, long long ll) { * 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); @@ -638,7 +662,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. diff --git a/src/server.h b/src/server.h index da4c6d45..1ebccf46 100644 --- a/src/server.h +++ b/src/server.h @@ -1424,8 +1424,13 @@ 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 setDeferredHelloLen(client *c, void *node, long length); void processInputBuffer(client *c); void processInputBufferAndReplicate(client *c); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); From 073293693e84b412ad3eaed392f79643a4b2d7a5 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 8 Nov 2018 13:05:50 +0100 Subject: [PATCH 010/122] RESP3: Use new deferred len API in server.c. --- src/server.c | 14 +++++++------- src/server.h | 7 ++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/server.c b/src/server.c index 9371b2ba..b686e2fc 100644 --- a/src/server.c +++ b/src/server.c @@ -2940,7 +2940,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); } @@ -2960,12 +2960,12 @@ void addReplyCommand(client *c, struct redisCommand *cmd) { addReply(c, shared.nullbulk); } 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 +2985,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 +3008,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 +3016,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 +3043,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); } diff --git a/src/server.h b/src/server.h index 1ebccf46..3ed559d6 100644 --- a/src/server.h +++ b/src/server.h @@ -1450,7 +1450,12 @@ 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 addReplyHelloLen(client *c, long length); void addReplyHelp(client *c, const char **help); void addReplySubcommandSyntaxError(client *c); void copyClientOutputBuffer(client *dst, client *src); From 07bce54093b29342f63d7a4a250ebc626dae5343 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 8 Nov 2018 13:27:34 +0100 Subject: [PATCH 011/122] RESP3: Use new deferred len API in replication.c. --- src/replication.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/replication.c b/src/replication.c index a3110661..9508528d 100644 --- a/src/replication.c +++ b/src/replication.c @@ -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). */ @@ -2062,10 +2062,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; @@ -2077,17 +2077,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); @@ -2116,7 +2116,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); From a577230a58800a9dc88d29b75d2ada605dd85d17 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 8 Nov 2018 13:28:04 +0100 Subject: [PATCH 012/122] RESP3: Use new deferred len API in t_string.c. --- src/t_string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t_string.c b/src/t_string.c index db6f7aa6..2dfb327f 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -285,7 +285,7 @@ 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) { From 470c28380f1323593a660e5fa63a0ba21c6fafed Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 8 Nov 2018 16:24:54 +0100 Subject: [PATCH 013/122] RESP3: Use new deferred len API in t_zset.c. --- src/t_zset.c | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/t_zset.c b/src/t_zset.c index 84a61ca4..9246c37c 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -2446,7 +2446,7 @@ void zrangeGenericCommand(client *c, int reverse) { rangelen = (end-start)+1; /* Return the result in form of a multi-bulk reply */ - addReplyMultiBulkLen(c, withscores ? (rangelen*2) : rangelen); + addReplyArrayLen(c, rangelen); if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *zl = zobj->ptr; @@ -2466,13 +2466,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) 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 +2500,9 @@ void zrangeGenericCommand(client *c, int reverse) { while(rangelen--) { serverAssertWithInfo(c,zobj,ln != NULL); ele = ln->ele; + if (withscores) 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 { @@ -2601,7 +2601,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 +2623,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) 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) { @@ -2665,7 +2664,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. */ @@ -2686,11 +2685,9 @@ void genericZrangebyscoreCommand(client *c, int reverse) { } rangelen++; + if (withscores) 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 +2700,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { serverPanic("Unknown sorted set encoding"); } - if (withscores) { - rangelen *= 2; - } - - setDeferredMultiBulkLength(c, replylen, rangelen); + setDeferredArrayLen(c, replylen, rangelen); } void zrangebyscoreCommand(client *c) { @@ -2953,7 +2946,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. */ @@ -3013,7 +3006,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 +3041,7 @@ void genericZrangebylexCommand(client *c, int reverse) { } zslFreeLexRange(&range); - setDeferredMultiBulkLength(c, replylen, rangelen); + setDeferredArrayLen(c, replylen, rangelen); } void zrangebylexCommand(client *c) { @@ -3160,7 +3153,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey 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 +3220,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 [] */ From cdd10193c5bcd85eabf6924d57e1d5357c670d03 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 8 Nov 2018 17:54:50 +0100 Subject: [PATCH 014/122] RESP3: Use new deferred len API in config.c. --- src/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.c b/src/config.c index 9f51bba8..7e6d9233 100644 --- a/src/config.c +++ b/src/config.c @@ -1324,7 +1324,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; @@ -1571,7 +1571,7 @@ void configGetCommand(client *c) { sdsfree(aux); matches++; } - setDeferredMultiBulkLength(c,replylen,matches*2); + setDeferredMapLen(c,replylen,matches); } /*----------------------------------------------------------------------------- From b5076547167eef56187a7c8be2c75d80d37d7b16 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 9 Nov 2018 12:59:00 +0100 Subject: [PATCH 015/122] RESP3: Use new deferred len API in dict.c. --- src/db.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db.c b/src/db.c index 62c8aa13..49306311 100644 --- a/src/db.c +++ b/src/db.c @@ -539,7 +539,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'); @@ -557,7 +557,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 @@ -782,10 +782,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); From f44e00b6910513d0dc31a1711a6dcc81526aa77d Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 9 Nov 2018 13:16:21 +0100 Subject: [PATCH 016/122] RESP3: Use new API and types in t_hash.c. --- src/t_hash.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/t_hash.c b/src/t_hash.c index fa3a893a..fe0e71fb 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -693,7 +693,7 @@ void hmgetCommand(client *c) { return; } - addReplyMultiBulkLen(c, c->argc-2); + addReplyMapLen(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 || 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) { From 1ac692664708108eaa38476a3b9549c9dcca3764 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 21 Nov 2018 11:53:18 +0100 Subject: [PATCH 017/122] RESP3: put RESP version in the client structure. --- src/networking.c | 1 + src/server.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/networking.c b/src/networking.c index 37e94706..f1ccdaf1 100644 --- a/src/networking.c +++ b/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; diff --git a/src/server.h b/src/server.h index 3ed559d6..603bbd2e 100644 --- a/src/server.h +++ b/src/server.h @@ -712,6 +712,7 @@ typedef struct readyList { 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. */ From e14aabf9369aae4faf20a40b63e616e47f43d9c5 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 21 Nov 2018 12:49:39 +0100 Subject: [PATCH 018/122] RESP3: addReply*Len() support for RESP2 backward comp. --- src/networking.c | 16 ++++++++-------- src/server.h | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/networking.c b/src/networking.c index f1ccdaf1..533df2f2 100644 --- a/src/networking.c +++ b/src/networking.c @@ -562,23 +562,23 @@ void addReplyArrayLen(client *c, long length) { } void addReplyMapLen(client *c, long length) { - addReplyAggregateLen(c,length,'%'); + int prefix = c->resp == 2 ? '*' : '%'; + addReplyAggregateLen(c,length,prefix); } void addReplySetLen(client *c, long length) { - addReplyAggregateLen(c,length,'~'); + int prefix = c->resp == 2 ? '*' : '~'; + addReplyAggregateLen(c,length,prefix); } void addReplyAttributeLen(client *c, long length) { - addReplyAggregateLen(c,length,'|'); + int prefix = c->resp == 2 ? '*' : '|'; + addReplyAggregateLen(c,length,prefix); } void addReplyPushLen(client *c, long length) { - addReplyAggregateLen(c,length,'>'); -} - -void addReplyHelloLen(client *c, long length) { - addReplyAggregateLen(c,length,'@'); + int prefix = c->resp == 2 ? '*' : '>'; + addReplyAggregateLen(c,length,prefix); } /* Create the length prefix of a bulk reply, example: $2234 */ diff --git a/src/server.h b/src/server.h index 603bbd2e..4b865500 100644 --- a/src/server.h +++ b/src/server.h @@ -1456,7 +1456,6 @@ 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 addReplyHelloLen(client *c, long length); void addReplyHelp(client *c, const char **help); void addReplySubcommandSyntaxError(client *c); void copyClientOutputBuffer(client *dst, client *src); From 13966522ea739d5f810153d2bf5525cf02482048 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 21 Nov 2018 16:20:17 +0100 Subject: [PATCH 019/122] RESP3: bring RESP2 compatibility to previous changes. --- src/networking.c | 58 +++++++++++++++++++++++++++++++++--------------- src/server.h | 1 - 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/networking.c b/src/networking.c index 533df2f2..6f077be7 100644 --- a/src/networking.c +++ b/src/networking.c @@ -473,23 +473,25 @@ void setDeferredArrayLen(client *c, void *node, long length) { } void setDeferredMapLen(client *c, void *node, long length) { - setDeferredAggregateLen(c,node,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) { - setDeferredAggregateLen(c,node,length,'~'); + int prefix = c->resp == 2 ? '*' : '~'; + setDeferredAggregateLen(c,node,length,prefix); } void setDeferredAttributeLen(client *c, void *node, long length) { - setDeferredAggregateLen(c,node,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) { - setDeferredAggregateLen(c,node,length,'>'); -} - -void setDeferredHelloLen(client *c, void *node, long length) { - setDeferredAggregateLen(c,node,length,'@'); + int prefix = c->resp == 2 ? '*' : '>'; + setDeferredAggregateLen(c,node,length,prefix); } /* Add a double as a bulk reply */ @@ -497,12 +499,24 @@ void addReplyDouble(client *c, double d) { 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. */ - addReplyString(c, d > 0 ? ",inf\r\n" : "-inf\r\n", - d > 0 ? 6 : 7); + if (c->resp == 2) { + addReplyBulkCString(c, d > 0 ? "inf" : "-inf"); + } else { + addReplyString(c, d > 0 ? ",inf\r\n" : "-inf\r\n", + d > 0 ? 6 : 7); + } } else { - char dbuf[MAX_LONG_DOUBLE_CHARS+3]; - int dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d); - addReplyString(c,dbuf,dlen); + 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); + addReplyString(c,sbuf,slen); + } else { + dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d); + addReplyString(c,dbuf,dlen); + } } } @@ -510,11 +524,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) { - char buf[MAX_LONG_DOUBLE_CHARS]; - int len = ld2string(buf,sizeof(buf),d,1); - addReplyString(c,",",1); - addReplyString(c,buf,len); - addReplyString(c,"\r\n",2); + 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); + addReplyString(c,",",1); + addReplyString(c,buf,len); + addReplyString(c,"\r\n",2); + } } /* Add a long long as integer reply or bulk len / multi bulk count. @@ -563,6 +583,7 @@ void addReplyArrayLen(client *c, long length) { void addReplyMapLen(client *c, long length) { int prefix = c->resp == 2 ? '*' : '%'; + if (c->resp == 2) length *= 2; addReplyAggregateLen(c,length,prefix); } @@ -573,6 +594,7 @@ void addReplySetLen(client *c, long length) { void addReplyAttributeLen(client *c, long length) { int prefix = c->resp == 2 ? '*' : '|'; + if (c->resp == 2) length *= 2; addReplyAggregateLen(c,length,prefix); } diff --git a/src/server.h b/src/server.h index 4b865500..272db662 100644 --- a/src/server.h +++ b/src/server.h @@ -1431,7 +1431,6 @@ 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 setDeferredHelloLen(client *c, void *node, long length); void processInputBuffer(client *c); void processInputBufferAndReplicate(client *c); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); From fe67418ba4222dc5580aab36524e3125855ee754 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 21 Nov 2018 17:27:25 +0100 Subject: [PATCH 020/122] RESP3: Use new deferred len API in object.c. --- src/object.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/object.c b/src/object.c index 48ffa42b..041af8b0 100644 --- a/src/object.c +++ b/src/object.c @@ -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); From c7f80e4f1a6374b3ea3791fb6f80bcaba09e1604 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 23 Nov 2018 12:22:27 +0100 Subject: [PATCH 021/122] RESP3: Make WITHSCORES reply back with a flat array in RESP2. --- src/t_zset.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/t_zset.c b/src/t_zset.c index 9246c37c..f1b14018 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -2446,6 +2446,7 @@ void zrangeGenericCommand(client *c, int reverse) { rangelen = (end-start)+1; /* Return the result in form of a multi-bulk reply */ + if (withscores && c->resp == 2) rangelen *= 2; addReplyArrayLen(c, rangelen); if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { @@ -2467,7 +2468,7 @@ void zrangeGenericCommand(client *c, int reverse) { serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL); serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong)); - if (withscores) addReplyArrayLen(c,2); + if (withscores && c->resp > 2) addReplyArrayLen(c,2); if (vstr == NULL) addReplyBulkLongLong(c,vlong); else @@ -2500,7 +2501,7 @@ void zrangeGenericCommand(client *c, int reverse) { while(rangelen--) { serverAssertWithInfo(c,zobj,ln != NULL); ele = ln->ele; - if (withscores) addReplyArrayLen(c,2); + if (withscores && c->resp > 2) addReplyArrayLen(c,2); addReplyBulkCBuffer(c,ele,sdslen(ele)); if (withscores) addReplyDouble(c,ln->score); ln = reverse ? ln->backward : ln->level[0].forward; @@ -2628,7 +2629,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong)); rangelen++; - if (withscores) addReplyArrayLen(c,2); + if (withscores && c->resp > 2) addReplyArrayLen(c,2); if (vstr == NULL) { addReplyBulkLongLong(c,vlong); } else { @@ -2685,7 +2686,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { } rangelen++; - if (withscores) addReplyArrayLen(c,2); + if (withscores && c->resp > 2) addReplyArrayLen(c,2); addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele)); if (withscores) addReplyDouble(c,ln->score); @@ -2700,6 +2701,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { serverPanic("Unknown sorted set encoding"); } + if (withscores && c->resp == 2) rangelen *= 2; setDeferredArrayLen(c, replylen, rangelen); } From dcbd40cea4497936e20100f14bf7ad4b010d1803 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 23 Nov 2018 12:40:01 +0100 Subject: [PATCH 022/122] RESP3: Use new aggregate reply API in cluster.c. --- src/cluster.c | 16 ++++++++-------- src/pubsub.c | 6 +++--- src/sort.c | 2 +- src/t_list.c | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/cluster.c b/src/cluster.c index b253efe1..fb25405f 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -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); diff --git a/src/pubsub.c b/src/pubsub.c index 859eb46a..c407cadf 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -343,7 +343,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 +356,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]); diff --git a/src/sort.c b/src/sort.c index 4b300d86..322f0272 100644 --- a/src/sort.c +++ b/src/sort.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; diff --git a/src/t_list.c b/src/t_list.c index c8f4703d..dd4ec953 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -421,7 +421,7 @@ void lrangeCommand(client *c) { 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); @@ -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); From feb6b318325d8982e2f1e424f0dcf75ed86f5c50 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 26 Nov 2018 16:17:19 +0100 Subject: [PATCH 023/122] RESP3: Use new aggregate reply API in t_set.c. --- src/t_set.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/t_set.c b/src/t_set.c index f67073fe..e78957d9 100644 --- a/src/t_set.c +++ b/src/t_set.c @@ -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; @@ -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)); @@ -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)); From f07f3d729fcd037885d54287fa670dc25243977e Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 26 Nov 2018 16:20:01 +0100 Subject: [PATCH 024/122] RESP3: Use new aggregate reply API in slowlog.c. --- src/multi.c | 2 +- src/slowlog.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/multi.c b/src/multi.c index d7f7d4ae..84a7bb86 100644 --- a/src/multi.c +++ b/src/multi.c @@ -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; diff --git a/src/slowlog.c b/src/slowlog.c index 8e183fca..1d715e39 100644 --- a/src/slowlog.c +++ b/src/slowlog.c @@ -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); } From 9330bcc7eef8598145d20d19133b56a09faf3e88 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 26 Nov 2018 16:22:27 +0100 Subject: [PATCH 025/122] RESP3: Fix API in scripting.c leaving Lua conversions RESP2. --- src/scripting.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scripting.c b/src/scripting.c index 260b3679..7aedc9de 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -315,7 +315,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,7 +330,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { luaReplyToRedisReply(c, lua); mbulklen++; } - setDeferredMultiBulkLength(c,replylen,mbulklen); + setDeferredArrayLen(c,replylen,mbulklen); } break; default: @@ -1501,7 +1501,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); From a1feda2388d556e6bc0c2f831e0b2f90f5f71abd Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 26 Nov 2018 16:44:00 +0100 Subject: [PATCH 026/122] RESP3: Scripting RESP3 mode set/map protocol -> Lua conversion. --- src/scripting.c | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/scripting.c b/src/scripting.c index 7aedc9de..9b8d5fd3 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -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; } From b7e8b734c92d623f51869e70c874dd03fc9023d2 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 26 Nov 2018 18:55:05 +0100 Subject: [PATCH 027/122] RESP3: remove certain constants to spot places to fix. --- src/server.c | 5 ++--- src/server.h | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/server.c b/src/server.c index b686e2fc..e2534a4d 100644 --- a/src/server.c +++ b/src/server.c @@ -1429,9 +1429,8 @@ void createSharedObjects(void) { 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.null = createObject(OBJ_STRING,sdsnew("_\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")); diff --git a/src/server.h b/src/server.h index 272db662..2bb7e365 100644 --- a/src/server.h +++ b/src/server.h @@ -782,8 +782,8 @@ struct moduleLoadQueueEntry { struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space, - *colon, *nullbulk, *nullmultibulk, *queued, - *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, + *colon, *queued, *null, + *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, *busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, From fc9a3de97df95294903e1135c8daf5e29bba76c3 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 26 Nov 2018 18:57:37 +0100 Subject: [PATCH 028/122] RESP3: remove other pointless shared object. --- src/server.c | 2 -- src/server.h | 4 ++-- src/t_list.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server.c b/src/server.c index e2534a4d..7d84d39f 100644 --- a/src/server.c +++ b/src/server.c @@ -1428,9 +1428,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.emptyarray = createObject(OBJ_STRING,sdsnew("*0\r\n")); - shared.null = createObject(OBJ_STRING,sdsnew("_\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")); diff --git a/src/server.h b/src/server.h index 2bb7e365..456d11ca 100644 --- a/src/server.h +++ b/src/server.h @@ -781,8 +781,8 @@ struct moduleLoadQueueEntry { }; struct sharedObjectsStruct { - robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space, - *colon, *queued, *null, + robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, + *colon, *queued, *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, diff --git a/src/t_list.c b/src/t_list.c index dd4ec953..60c8e9ab 100644 --- a/src/t_list.c +++ b/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; } From 1b7298e66ae002b9011c22d8270436506a9dc9b1 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 27 Nov 2018 11:58:55 +0100 Subject: [PATCH 029/122] RESP3: addReplyNull() added. --- src/networking.c | 8 ++++++++ src/server.c | 2 +- src/server.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/networking.c b/src/networking.c index 6f077be7..bb51619f 100644 --- a/src/networking.c +++ b/src/networking.c @@ -603,6 +603,14 @@ void addReplyPushLen(client *c, long length) { addReplyAggregateLen(c,length,prefix); } +void addReplyNull(client *c) { + if (c->resp == 2) { + addReplyString(c,"$-1\r\n",5); + } else { + addReplyString(c,"_\r\n",3); + } +} + /* Create the length prefix of a bulk reply, example: $2234 */ void addReplyBulkLen(client *c, robj *obj) { size_t len; diff --git a/src/server.c b/src/server.c index 7d84d39f..5b6a9c4a 100644 --- a/src/server.c +++ b/src/server.c @@ -2954,7 +2954,7 @@ 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 */ addReplyArrayLen(c, 6); diff --git a/src/server.h b/src/server.h index 456d11ca..a483e1a6 100644 --- a/src/server.h +++ b/src/server.h @@ -1437,6 +1437,7 @@ 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 addReplyNull(client *c); void addReplyString(client *c, const char *s, size_t len); void addReplyBulk(client *c, robj *obj); void addReplyBulkCString(client *c, const char *s); From 317f8b9d383f1b7f171aef7ea29f9e05abf0ba83 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 09:41:54 +0100 Subject: [PATCH 030/122] RESP3: most null replies converted. --- src/cluster.c | 2 +- src/db.c | 2 +- src/multi.c | 2 +- src/networking.c | 4 ++-- src/object.c | 10 +++++----- src/pubsub.c | 4 ++-- src/scripting.c | 4 ++-- src/server.c | 6 ++++++ src/server.h | 2 +- src/sort.c | 2 +- src/t_hash.c | 10 +++++----- src/t_list.c | 22 +++++++++++----------- src/t_set.c | 18 +++++++++--------- src/t_string.c | 8 ++++---- src/t_zset.c | 30 +++++++++++++++--------------- 15 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/cluster.c b/src/cluster.c index fb25405f..1a3a348b 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -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; } diff --git a/src/db.c b/src/db.c index 49306311..74965f66 100644 --- a/src/db.c +++ b/src/db.c @@ -525,7 +525,7 @@ void randomkeyCommand(client *c) { robj *key; if ((key = dbRandomKey(c->db)) == NULL) { - addReply(c,shared.nullbulk); + addReplyNull(c); return; } diff --git a/src/multi.c b/src/multi.c index 84a7bb86..b84b75f7 100644 --- a/src/multi.c +++ b/src/multi.c @@ -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.null[c->resp]); discardTransaction(c); goto handle_monitor; } diff --git a/src/networking.c b/src/networking.c index bb51619f..72f44f07 100644 --- a/src/networking.c +++ b/src/networking.c @@ -661,7 +661,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)); } @@ -1973,7 +1973,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; diff --git a/src/object.c b/src/object.c index 041af8b0..ec0bd02e 100644 --- a/src/object.c +++ b/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); diff --git a/src/pubsub.c b/src/pubsub.c index c407cadf..335c52d9 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -189,7 +189,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) { if (notify && count == 0) { addReply(c,shared.mbulkhdr[3]); addReply(c,shared.unsubscribebulk); - addReply(c,shared.nullbulk); + addReplyNull(c); addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } @@ -214,7 +214,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) { /* We were subscribed to nothing? Still reply to the client. */ addReply(c,shared.mbulkhdr[3]); addReply(c,shared.punsubscribebulk); - addReply(c,shared.nullbulk); + addReplyNull(c); addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } diff --git a/src/scripting.c b/src/scripting.c index 9b8d5fd3..f6df3840 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -300,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)); @@ -352,7 +352,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { } break; default: - addReply(c,shared.nullbulk); + addReplyNull(c); } lua_pop(lua,1); } diff --git a/src/server.c b/src/server.c index 5b6a9c4a..a736a1e5 100644 --- a/src/server.c +++ b/src/server.c @@ -1468,6 +1468,12 @@ 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")); + for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) { char dictid_str[64]; int dictid_len; diff --git a/src/server.h b/src/server.h index a483e1a6..2958c635 100644 --- a/src/server.h +++ b/src/server.h @@ -782,7 +782,7 @@ struct moduleLoadQueueEntry { struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, - *colon, *queued, + *colon, *queued, *null[4], *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, diff --git a/src/sort.c b/src/sort.c index 322f0272..8608cd8b 100644 --- a/src/sort.c +++ b/src/sort.c @@ -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); diff --git a/src/t_hash.c b/src/t_hash.c index fe0e71fb..a50da579 100644 --- a/src/t_hash.c +++ b/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); @@ -768,7 +768,7 @@ void genericHgetallCommand(client *c, int flags) { hashTypeIterator *hi; 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; /* We return a map if the user requested keys and values, like in the diff --git a/src/t_list.c b/src/t_list.c index 60c8e9ab..d6daad69 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -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,7 +414,7 @@ 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; @@ -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]; @@ -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); + addReplyNull(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); diff --git a/src/t_set.c b/src/t_set.c index e78957d9..61013dbc 100644 --- a/src/t_set.c +++ b/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; } @@ -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; } @@ -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; } diff --git a/src/t_string.c b/src/t_string.c index 2dfb327f..5800c5c0 100644 --- a/src/t_string.c +++ b/src/t_string.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) { @@ -289,10 +289,10 @@ void mgetCommand(client *c) { 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); } diff --git a/src/t_zset.c b/src/t_zset.c index f1b14018..1c943cae 100644 --- a/src/t_zset.c +++ b/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,7 +2439,7 @@ 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; @@ -2571,7 +2571,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) { @@ -2591,7 +2591,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { /* No "first" element in the specified interval. */ if (eptr == NULL) { - addReply(c, shared.emptymultibulk); + addReplyNull(c); return; } @@ -2658,7 +2658,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { /* No "first" element in the specified interval. */ if (ln == NULL) { - addReply(c, shared.emptymultibulk); + addReplyNull(c); return; } @@ -2913,7 +2913,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); @@ -2936,7 +2936,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; } @@ -3000,7 +3000,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; } @@ -3069,11 +3069,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); } @@ -3085,7 +3085,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)); @@ -3093,7 +3093,7 @@ void zrankGenericCommand(client *c, int reverse) { if (rank >= 0) { addReplyLongLong(c,rank); } else { - addReply(c,shared.nullbulk); + addReplyNull(c); } } @@ -3151,7 +3151,7 @@ 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; } @@ -3277,7 +3277,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); + addReplyNull(c); return; } From 3a3d806989ea5ee17482c0a085788adb48fd5917 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 10:40:54 +0100 Subject: [PATCH 031/122] RESP3: bitops.c updated. --- src/bitops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bitops.c b/src/bitops.c index 23f2266a..8d03a769 100644 --- a/src/bitops.c +++ b/src/bitops.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++; From 9705c12d85b5428d4923476889a16a55f3589593 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 10:46:55 +0100 Subject: [PATCH 032/122] RESP3: sentinel.c updated. --- src/sentinel.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sentinel.c b/src/sentinel.c index adff9d4f..a94159f6 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -885,17 +885,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++]); @@ -2741,7 +2741,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 +2922,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 +2932,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 +3062,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 +3078,11 @@ void sentinelCommand(client *c) { if (c->argc != 3) goto numargserr; ri = sentinelGetMasterByName(c->argv[2]->ptr); if (ri == NULL) { - addReply(c,shared.nullmultibulk); + addReplyNull(c); } else { sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri); - addReplyMultiBulkLen(c,2); + addReplyArrayLen(c,2); addReplyBulkCString(c,addr->ip); addReplyBulkLongLong(c,addr->port); } @@ -3232,7 +3232,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 +3240,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 +3282,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 +3382,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) { From 071da9844ce7b22bd08ea0234655d6482f2d01ad Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 10:50:33 +0100 Subject: [PATCH 033/122] RESP3: blocked.c updated. --- src/blocked.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blocked.c b/src/blocked.c index 2b43f2b7..1f22a20a 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -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); + addReplyNull(c); } else if (c->btype == BLOCKED_WAIT) { addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset)); } else if (c->btype == BLOCKED_MODULE) { @@ -436,8 +436,8 @@ 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); + addReplyArrayLen(receiver,1); + addReplyArrayLen(receiver,2); addReplyBulk(receiver,rl->key); streamPropInfo pi = { From 86c30a92f984a764ba5f4e941bf04119b173f7cb Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 11:07:07 +0100 Subject: [PATCH 034/122] RESP3: geo.c updated. --- src/geo.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/geo.c b/src/geo.c index c78fadfc..d14f537b 100644 --- a/src/geo.c +++ b/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); + addReplyNull(c); } else { /* Decode... */ double xy[2]; if (!decodeGeohash(score,xy)) { - addReply(c,shared.nullmultibulk); + addReplyNull(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); From 1a17cdfadf43ae6179dc7556caae579436066a2f Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 16:31:02 +0100 Subject: [PATCH 035/122] RESP3: addReplyNullArray() added for better RESP2 compat. --- src/networking.c | 12 ++++++++++++ src/server.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/networking.c b/src/networking.c index 72f44f07..8be22629 100644 --- a/src/networking.c +++ b/src/networking.c @@ -611,6 +611,18 @@ void addReplyNull(client *c) { } } +/* 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) { + addReplyString(c,"*-1\r\n",5); + } else { + addReplyString(c,"_\r\n",3); + } +} + /* Create the length prefix of a bulk reply, example: $2234 */ void addReplyBulkLen(client *c, robj *obj) { size_t len; diff --git a/src/server.h b/src/server.h index 2958c635..d4558df5 100644 --- a/src/server.h +++ b/src/server.h @@ -1438,6 +1438,7 @@ 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 addReplyNull(client *c); +void addReplyNullArray(client *c); void addReplyString(client *c, const char *s, size_t len); void addReplyBulk(client *c, robj *obj); void addReplyBulkCString(client *c, const char *s); From 2ad6e875ba0db0a1d2f78df2b7e651010137b576 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 16:32:06 +0100 Subject: [PATCH 036/122] RESP3: add shared.nullarray for better RESP2 compat. --- src/server.c | 7 ++++++- src/server.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/server.c b/src/server.c index a736a1e5..79df0282 100644 --- a/src/server.c +++ b/src/server.c @@ -1471,9 +1471,14 @@ void createSharedObjects(void) { /* 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[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; diff --git a/src/server.h b/src/server.h index d4558df5..c12dca59 100644 --- a/src/server.h +++ b/src/server.h @@ -782,7 +782,7 @@ struct moduleLoadQueueEntry { struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, - *colon, *queued, *null[4], + *colon, *queued, *null[4], nullarray[4], *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, From 3fd78f41e868c4ecf5b1a3b238dfb2ebe81ff579 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 16:36:55 +0100 Subject: [PATCH 037/122] RESP3: restore the concept of null array for RESP2 compat. --- src/blocked.c | 2 +- src/geo.c | 4 ++-- src/multi.c | 2 +- src/sentinel.c | 2 +- src/server.h | 2 +- src/t_list.c | 2 +- src/t_zset.c | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/blocked.c b/src/blocked.c index 1f22a20a..b8cf02c8 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -187,7 +187,7 @@ void replyToBlockedClientTimedOut(client *c) { if (c->btype == BLOCKED_LIST || c->btype == BLOCKED_ZSET || c->btype == BLOCKED_STREAM) { - addReplyNull(c); + addReplyNullArray(c); } else if (c->btype == BLOCKED_WAIT) { addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset)); } else if (c->btype == BLOCKED_MODULE) { diff --git a/src/geo.c b/src/geo.c index d14f537b..91a0421f 100644 --- a/src/geo.c +++ b/src/geo.c @@ -763,12 +763,12 @@ void geoposCommand(client *c) { for (j = 2; j < c->argc; j++) { double score; if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) { - addReplyNull(c); + addReplyNullArray(c); } else { /* Decode... */ double xy[2]; if (!decodeGeohash(score,xy)) { - addReplyNull(c); + addReplyNullArray(c); continue; } addReplyArrayLen(c,2); diff --git a/src/multi.c b/src/multi.c index b84b75f7..71090d8e 100644 --- a/src/multi.c +++ b/src/multi.c @@ -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.null[c->resp]); + shared.nullarray[c->resp]); discardTransaction(c); goto handle_monitor; } diff --git a/src/sentinel.c b/src/sentinel.c index a94159f6..536edcdd 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -3078,7 +3078,7 @@ void sentinelCommand(client *c) { if (c->argc != 3) goto numargserr; ri = sentinelGetMasterByName(c->argv[2]->ptr); if (ri == NULL) { - addReplyNull(c); + addReplyNullArray(c); } else { sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri); diff --git a/src/server.h b/src/server.h index c12dca59..0cc14f08 100644 --- a/src/server.h +++ b/src/server.h @@ -782,7 +782,7 @@ struct moduleLoadQueueEntry { struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, - *colon, *queued, *null[4], nullarray[4], + *colon, *queued, *null[4], *nullarray[4], *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, diff --git a/src/t_list.c b/src/t_list.c index d6daad69..451ffb4b 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -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) { - addReplyNull(c); + addReplyNullArray(c); return; } diff --git a/src/t_zset.c b/src/t_zset.c index 1c943cae..2efdcd26 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -3277,7 +3277,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) { - addReplyNull(c); + addReplyNullArray(c); return; } From ddb98ad56f9e9eb658f79ee8faf2b027e97c0af7 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 16:37:48 +0100 Subject: [PATCH 038/122] RESP3: hyperloglog.c updated. --- src/hyperloglog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperloglog.c b/src/hyperloglog.c index ba3a3ab6..fc21ea00 100644 --- a/src/hyperloglog.c +++ b/src/hyperloglog.c @@ -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; From 920611a4197cd2606504e6ce4bf66ee213c76b36 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 17:10:51 +0100 Subject: [PATCH 039/122] RESP3: latency.c updated. --- src/latency.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/latency.c b/src/latency.c index 97e6a702..33aa1245 100644 --- a/src/latency.c +++ b/src/latency.c @@ -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 */ ts = dictFetchValue(server.latency_events,c->argv[2]->ptr); if (ts == NULL) { - addReplyMultiBulkLen(c,0); + addReplyArrayLen(c,0); } else { latencyCommandReplyWithSamples(c,ts); } From baf5b3f93a5ac1efeef6c8b18ae5c61cbbbacb45 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 30 Nov 2018 17:12:32 +0100 Subject: [PATCH 040/122] RESP3: module.c updated. --- src/module.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/module.c b/src/module.c index 20d159d3..c79f1133 100644 --- a/src/module.c +++ b/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; } @@ -4868,11 +4868,11 @@ NULL dictIterator *di = dictGetIterator(modules); dictEntry *de; - addReplyMultiBulkLen(c,dictSize(modules)); + addReplyArrayLen(c,dictSize(modules)); while ((de = dictNext(di)) != NULL) { sds name = dictGetKey(de); struct RedisModule *module = dictGetVal(de); - addReplyMultiBulkLen(c,4); + addReplyMapLen(c,2); addReplyBulkCString(c,"name"); addReplyBulkCBuffer(c,name,sdslen(name)); addReplyBulkCString(c,"ver"); From 8a0391fbc957983d2f42fdc6e9f7da625178d8f5 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 3 Dec 2018 16:24:04 +0100 Subject: [PATCH 041/122] RESP3: t_stream.c updated. --- src/blocked.c | 8 +++-- src/t_stream.c | 81 +++++++++++++++++++++++++++----------------------- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/blocked.c b/src/blocked.c index b8cf02c8..f9e19662 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -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. */ - addReplyArrayLen(receiver,1); - addReplyArrayLen(receiver,2); + if (receiver->resp == 2) { + addReplyArrayLen(receiver,1); + addReplyArrayLen(receiver,2); + } else { + addReplyMapLen(receiver,1); + } addReplyBulk(receiver,rl->key); streamPropInfo pi = { diff --git a/src/t_stream.c b/src/t_stream.c index f51f6c46..1a5acac4 100644 --- a/src/t_stream.c +++ b/src/t_stream.c @@ -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 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 . */ 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 (or the alias XINFO ). */ - 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); } From c2e5be04214acea0bb93f21155333853ab73641a Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 3 Dec 2018 18:02:57 +0100 Subject: [PATCH 042/122] RESP3: fix zrangeGenericCommand() proto dependent array len. --- src/t_zset.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/t_zset.c b/src/t_zset.c index 2efdcd26..0427ee88 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -2445,9 +2445,13 @@ void zrangeGenericCommand(client *c, int reverse) { if (end >= llen) end = llen-1; rangelen = (end-start)+1; - /* Return the result in form of a multi-bulk reply */ - if (withscores && c->resp == 2) rangelen *= 2; - addReplyArrayLen(c, 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; From 0652b05caf32ea6ff5d9aeb17607f35446df55c4 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 3 Dec 2018 18:08:00 +0100 Subject: [PATCH 043/122] RESP3: fix genericHgetallCommand() assert. --- src/t_hash.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/t_hash.c b/src/t_hash.c index a50da579..70a7b2f4 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -793,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); } From e5fdd6b6bfa769d83a49643bf58cbe681ed2dfe2 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 3 Dec 2018 19:18:28 +0100 Subject: [PATCH 044/122] RESP3: fix HMGET bug introduced with RESP3 changes. --- src/t_hash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t_hash.c b/src/t_hash.c index 70a7b2f4..d8aee657 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -693,7 +693,7 @@ void hmgetCommand(client *c) { return; } - addReplyMapLen(c, c->argc-2); + addReplyArrayLen(c, c->argc-2); for (i = 2; i < c->argc; i++) { addHashFieldToReply(c, o, c->argv[i]->ptr); } From 4f0860cbfd84bb28c895eae525a7168014dc37f8 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 4 Dec 2018 12:46:16 +0100 Subject: [PATCH 045/122] RESP3: initial implementation of the HELLO command. --- src/module.c | 34 ++++++++++++++++++++-------------- src/networking.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/sentinel.c | 3 ++- src/server.c | 1 + src/server.h | 2 ++ 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/module.c b/src/module.c index c79f1133..ac935d5c 100644 --- a/src/module.c +++ b/src/module.c @@ -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 [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; - - 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); + addReplyLoadedModules(c); } else { addReplySubcommandSyntaxError(c); return; diff --git a/src/networking.c b/src/networking.c index 8be22629..5e442e28 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1998,6 +1998,54 @@ NULL } } +/* HELLO [AUTH ] */ +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; + } + + /* 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" diff --git a/src/sentinel.c b/src/sentinel.c index 536edcdd..1696b121 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -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 diff --git a/src/server.c b/src/server.c index 79df0282..8cdbdad7 100644 --- a/src/server.c +++ b/src/server.c @@ -284,6 +284,7 @@ struct redisCommand redisCommandTable[] = { {"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}, + {"hello",helloCommand,-2,"sF",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}, diff --git a/src/server.h b/src/server.h index 0cc14f08..6a398dc3 100644 --- a/src/server.h +++ b/src/server.h @@ -1459,6 +1459,7 @@ 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); @@ -2093,6 +2094,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); From 809e3a44a7b53f26ba6bfe49012a88a5203fae3e Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 4 Dec 2018 18:00:35 +0100 Subject: [PATCH 046/122] RESP3: addReplyBool() implemented. --- src/networking.c | 8 ++++++++ src/server.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/networking.c b/src/networking.c index 5e442e28..789ea822 100644 --- a/src/networking.c +++ b/src/networking.c @@ -611,6 +611,14 @@ void addReplyNull(client *c) { } } +void addReplyBool(client *c, int b) { + if (c->resp == 3) { + addReply(c, b ? shared.cone : shared.czero); + } else { + addReplyString(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 diff --git a/src/server.h b/src/server.h index 6a398dc3..0362c03a 100644 --- a/src/server.h +++ b/src/server.h @@ -1439,6 +1439,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); void addReplyNull(client *c); void addReplyNullArray(client *c); +void addReplyBool(client *c, int b); void addReplyString(client *c, const char *s, size_t len); void addReplyBulk(client *c, robj *obj); void addReplyBulkCString(client *c, const char *s); From d5c54f0b3a908595afead2cbcd93555f2886f22f Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 4 Dec 2018 19:01:33 +0100 Subject: [PATCH 047/122] RESP3: hiredis updated with recent version + some RESP3 support. --- deps/hiredis/CHANGELOG.md | 53 ++++++++-- deps/hiredis/Makefile | 24 ++--- deps/hiredis/adapters/libevent.h | 4 +- deps/hiredis/adapters/libuv.h | 9 +- deps/hiredis/appveyor.yml | 17 +-- deps/hiredis/async.c | 67 ++++++++---- deps/hiredis/async.h | 5 + deps/hiredis/fmacros.h | 19 +--- deps/hiredis/hiredis.c | 49 ++++----- deps/hiredis/hiredis.h | 33 ++---- deps/hiredis/net.c | 75 ++++++++++--- deps/hiredis/net.h | 5 +- deps/hiredis/read.c | 174 +++++++++++++++++++++++-------- deps/hiredis/read.h | 8 ++ deps/hiredis/sds.c | 29 +++++- deps/hiredis/test.c | 154 +++++++++++++++++++++++---- 16 files changed, 499 insertions(+), 226 deletions(-) diff --git a/deps/hiredis/CHANGELOG.md b/deps/hiredis/CHANGELOG.md index f92bcb3c..a7fe3ac1 100644 --- a/deps/hiredis/CHANGELOG.md +++ b/deps/hiredis/CHANGELOG.md @@ -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 diff --git a/deps/hiredis/Makefile b/deps/hiredis/Makefile index 9a4de836..06ca9946 100644 --- a/deps/hiredis/Makefile +++ b/deps/hiredis/Makefile @@ -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) diff --git a/deps/hiredis/adapters/libevent.h b/deps/hiredis/adapters/libevent.h index 273d8b2d..7d2bef18 100644 --- a/deps/hiredis/adapters/libevent.h +++ b/deps/hiredis/adapters/libevent.h @@ -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); } diff --git a/deps/hiredis/adapters/libuv.h b/deps/hiredis/adapters/libuv.h index ff08c25e..39ef7cf5 100644 --- a/deps/hiredis/adapters/libuv.h +++ b/deps/hiredis/adapters/libuv.h @@ -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); } } diff --git a/deps/hiredis/appveyor.yml b/deps/hiredis/appveyor.yml index 06bbef11..819efbd5 100644 --- a/deps/hiredis/appveyor.yml +++ b/deps/hiredis/appveyor.yml @@ -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: diff --git a/deps/hiredis/async.c b/deps/hiredis/async.c index d955203f..0cecd30d 100644 --- a/deps/hiredis/async.c +++ b/deps/hiredis/async.c @@ -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; diff --git a/deps/hiredis/async.h b/deps/hiredis/async.h index 59cbf469..740555c2 100644 --- a/deps/hiredis/async.h +++ b/deps/hiredis/async.h @@ -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; diff --git a/deps/hiredis/fmacros.h b/deps/hiredis/fmacros.h index 9a56643d..3227faaf 100644 --- a/deps/hiredis/fmacros.h +++ b/deps/hiredis/fmacros.h @@ -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 -#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 diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 18bdfc99..bfbf483e 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -84,16 +84,14 @@ void freeReplyObject(void *reply) { case REDIS_REPLY_ARRAY: 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); + free(r->str); break; } free(r); @@ -432,11 +430,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 +575,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 +590,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 +606,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 +694,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 +704,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 +777,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 +995,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; } diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h index 423d5e50..1b0d5e65 100644 --- a/deps/hiredis/hiredis.h +++ b/deps/hiredis/hiredis.h @@ -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 @@ -158,6 +134,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); diff --git a/deps/hiredis/net.c b/deps/hiredis/net.c index 7d412098..a4b3abc6 100644 --- a/deps/hiredis/net.c +++ b/deps/hiredis/net.c @@ -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) { diff --git a/deps/hiredis/net.h b/deps/hiredis/net.h index 2f1a0bf8..a11594e6 100644 --- a/deps/hiredis/net.h +++ b/deps/hiredis/net.h @@ -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 diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index 50333b53..084d0b07 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -39,6 +39,7 @@ #include #include #include +#include #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) { @@ -220,10 +265,17 @@ 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 { /* Type will be error or status. */ if (r->fn && r->fn->createString) @@ -250,7 +302,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 +311,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 +362,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 +377,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 +405,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); @@ -387,6 +464,12 @@ static int processItem(redisReader *r) { case '*': cur->type = REDIS_REPLY_ARRAY; break; + case '%': + cur->type = REDIS_REPLY_MAP; + break; + case '~': + cur->type = REDIS_REPLY_SET; + break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; @@ -406,7 +489,9 @@ static int processItem(redisReader *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 +501,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 +518,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); } diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h index 2988aa45..84ee15cb 100644 --- a/deps/hiredis/read.h +++ b/deps/hiredis/read.h @@ -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. */ diff --git a/deps/hiredis/sds.c b/deps/hiredis/sds.c index 923ffd82..44777b10 100644 --- a/deps/hiredis/sds.c +++ b/deps/hiredis/sds.c @@ -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*)); diff --git a/deps/hiredis/test.c b/deps/hiredis/test.c index a23d6067..79cff430 100644 --- a/deps/hiredis/test.c +++ b/deps/hiredis/test.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -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); } From eb3c5a70d48f83c3222553fb0bf8c2c61395e886 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 5 Dec 2018 11:55:29 +0100 Subject: [PATCH 048/122] RESP3: hiredis: fix read.c assert for new types. --- deps/hiredis/hiredis.c | 4 +++- deps/hiredis/read.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index bfbf483e..0de477c3 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -123,7 +123,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; diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index 084d0b07..22143888 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -243,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 { From d4a4375d0510732e9578f9b54ab4cd7e85345393 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 5 Dec 2018 11:59:55 +0100 Subject: [PATCH 049/122] RESP3: hiredis: free map and set replies. --- deps/hiredis/hiredis.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 0de477c3..34a987b3 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -82,6 +82,8 @@ 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++) freeReplyObject(r->element[j]); @@ -134,7 +136,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; From 24a05e39f0e358d8332f82fb827d05c894462476 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 5 Dec 2018 12:00:47 +0100 Subject: [PATCH 050/122] RESP3: hiredis: fix hiredis.c assert for new types. --- deps/hiredis/hiredis.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 34a987b3..42d0f8dd 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -152,7 +152,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; @@ -169,7 +171,9 @@ 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; From 005915b5c3ed1fcb79e3e0ed6b5d41c057ef5a3b Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 5 Dec 2018 12:22:37 +0100 Subject: [PATCH 051/122] RESP3: hiredis: map and set display for TTY output. --- src/redis-cli.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index bfd245b4..bfecf1b7 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -820,8 +820,17 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) { out = sdscat(out,"(nil)\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 +840,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 +852,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); } From a2b2d88f384c4ad3e812cceccd4720a9c650207c Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 6 Dec 2018 11:23:23 +0100 Subject: [PATCH 052/122] RESP3: hiredis: initial double implementation. --- deps/hiredis/.travis.yml | 6 ++++++ deps/hiredis/hiredis.c | 21 +++++++++++++++++++++ deps/hiredis/hiredis.h | 1 + deps/hiredis/read.c | 36 +++++++++++++++++++++++++++++++++++- deps/hiredis/read.h | 1 + src/redis-cli.c | 3 +++ 6 files changed, 67 insertions(+), 1 deletion(-) diff --git a/deps/hiredis/.travis.yml b/deps/hiredis/.travis.yml index ad08076d..faf2ce68 100644 --- a/deps/hiredis/.travis.yml +++ b/deps/hiredis/.travis.yml @@ -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 diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 42d0f8dd..13b96187 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -47,6 +47,7 @@ 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); static void *createNilObject(const redisReadTask *task); /* Default set of functions to build the reply. Keep in mind that such a @@ -55,6 +56,7 @@ static redisReplyObjectFunctions defaultFunctions = { createStringObject, createArrayObject, createIntegerObject, + createDoubleObject, createNilObject, freeReplyObject }; @@ -179,6 +181,25 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { return r; } +static void *createDoubleObject(const redisReadTask *task, double value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_DOUBLE); + if (r == NULL) + return NULL; + + r->dval = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); + parent->element[task->idx] = r; + } + return r; +} + static void *createNilObject(const redisReadTask *task) { redisReply *r, *parent; diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h index 1b0d5e65..40719fe2 100644 --- a/deps/hiredis/hiredis.h +++ b/deps/hiredis/hiredis.h @@ -88,6 +88,7 @@ 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 */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index 22143888..d6a47974 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -29,7 +29,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ - #include "fmacros.h" #include #include @@ -40,6 +39,7 @@ #include #include #include +#include #include "read.h" #include "sds.h" @@ -278,6 +278,36 @@ static int processLineItem(redisReader *r) { } 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-1 >= sizeof(buf)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Double value is too large"); + return REDIS_ERR; + } + + memcpy(buf,p+1,len-1); + buf[len-1] = '\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 (eptr[0] != '\0' || isnan(d)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad double value"); + return REDIS_ERR; + } + } + obj = r->fn->createDouble(cur,d); + } else { + obj = (void*)REDIS_REPLY_DOUBLE; + } } else { /* Type will be error or status. */ if (r->fn && r->fn->createString) @@ -460,6 +490,9 @@ static int processItem(redisReader *r) { case ':': cur->type = REDIS_REPLY_INTEGER; break; + case ',': + cur->type = REDIS_REPLY_DOUBLE; + break; case '$': cur->type = REDIS_REPLY_STRING; break; @@ -487,6 +520,7 @@ static int processItem(redisReader *r) { case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_INTEGER: + case REDIS_REPLY_DOUBLE: return processLineItem(r); case REDIS_REPLY_STRING: return processBulkItem(r); diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h index 84ee15cb..e7c4bd30 100644 --- a/deps/hiredis/read.h +++ b/deps/hiredis/read.h @@ -81,6 +81,7 @@ 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); void *(*createNil)(const redisReadTask*); void (*freeObject)(void*); } redisReplyObjectFunctions; diff --git a/src/redis-cli.c b/src/redis-cli.c index bfecf1b7..15042b97 100644 --- a/src/redis-cli.c +++ b/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) %.17g\n",r->dval); + 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 */ From b8134dbfa734426c43741e8cd50bbdc5a7295dd1 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 6 Dec 2018 11:29:53 +0100 Subject: [PATCH 053/122] RESP3: hiredis: fix double implementation. --- deps/hiredis/read.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index d6a47974..2726d448 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -283,14 +283,14 @@ static int processLineItem(redisReader *r) { char buf[326], *eptr; double d; - if ((size_t)len-1 >= sizeof(buf)) { + if ((size_t)len >= sizeof(buf)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Double value is too large"); return REDIS_ERR; } - memcpy(buf,p+1,len-1); - buf[len-1] = '\0'; + memcpy(buf,p,len); + buf[len] = '\0'; if (strcasecmp(buf,",inf") == 0) { d = 1.0/0.0; /* Positive infinite. */ @@ -298,7 +298,7 @@ static int processLineItem(redisReader *r) { d = -1.0/0.0; /* Nevative infinite. */ } else { d = strtod((char*)buf,&eptr); - if (eptr[0] != '\0' || isnan(d)) { + if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad double value"); return REDIS_ERR; From ee4c355a3e5f46ed9ce5749228f9c15e2b1be427 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 6 Dec 2018 11:33:58 +0100 Subject: [PATCH 054/122] RESP3: hiredis: implement null type. --- deps/hiredis/read.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index 2726d448..32f1c922 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -308,6 +308,11 @@ static int processLineItem(redisReader *r) { } 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 { /* Type will be error or status. */ if (r->fn && r->fn->createString) @@ -493,6 +498,9 @@ static int processItem(redisReader *r) { case ',': cur->type = REDIS_REPLY_DOUBLE; break; + case '_': + cur->type = REDIS_REPLY_NIL; + break; case '$': cur->type = REDIS_REPLY_STRING; break; @@ -521,6 +529,7 @@ static int processItem(redisReader *r) { case REDIS_REPLY_STATUS: case REDIS_REPLY_INTEGER: case REDIS_REPLY_DOUBLE: + case REDIS_REPLY_NIL: return processLineItem(r); case REDIS_REPLY_STRING: return processBulkItem(r); From 045b1f633977926e37a8c77ccd311734abb56749 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 6 Dec 2018 11:43:11 +0100 Subject: [PATCH 055/122] RESP3: hiredis: save the original double string. --- deps/hiredis/hiredis.c | 18 ++++++++++++++++-- deps/hiredis/hiredis.h | 3 ++- deps/hiredis/read.c | 2 +- deps/hiredis/read.h | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 13b96187..193168a0 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -47,7 +47,7 @@ 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); +static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); static void *createNilObject(const redisReadTask *task); /* Default set of functions to build the reply. Keep in mind that such a @@ -95,6 +95,7 @@ void freeReplyObject(void *reply) { case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: + case REDIS_REPLY_DOUBLE: free(r->str); break; } @@ -181,7 +182,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { return r; } -static void *createDoubleObject(const redisReadTask *task, double value) { +static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_DOUBLE); @@ -189,6 +190,19 @@ static void *createDoubleObject(const redisReadTask *task, double value) { 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; diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h index 40719fe2..47d7982e 100644 --- a/deps/hiredis/hiredis.h +++ b/deps/hiredis/hiredis.h @@ -90,7 +90,8 @@ typedef struct redisReply { 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; diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index 32f1c922..511503cb 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -304,7 +304,7 @@ static int processLineItem(redisReader *r) { return REDIS_ERR; } } - obj = r->fn->createDouble(cur,d); + obj = r->fn->createDouble(cur,d,buf,len); } else { obj = (void*)REDIS_REPLY_DOUBLE; } diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h index e7c4bd30..db267f81 100644 --- a/deps/hiredis/read.h +++ b/deps/hiredis/read.h @@ -81,7 +81,7 @@ 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); + void *(*createDouble)(const redisReadTask*, double, char*, size_t); void *(*createNil)(const redisReadTask*); void (*freeObject)(void*); } redisReplyObjectFunctions; From 62b2642c518bf3cc19b2578598f79598b54309e8 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 6 Dec 2018 11:44:17 +0100 Subject: [PATCH 056/122] RESP3: redis-cli: show the double as received from Redis. --- src/redis-cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index 15042b97..b8934eda 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -811,7 +811,7 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) { out = sdscatprintf(out,"(integer) %lld\n",r->integer); break; case REDIS_REPLY_DOUBLE: - out = sdscatprintf(out,"(double) %.17g\n",r->dval); + out = sdscatprintf(out,"(double) %s\n",r->str); break; case REDIS_REPLY_STRING: /* If you are producing output for the standard output we want From c3bf646ef34e7fca0c58b87ef308ed975e53e7e6 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 7 Dec 2018 16:42:49 +0100 Subject: [PATCH 057/122] RESP3: fix DEBUG DIGEST-VALUE with new API. --- src/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug.c b/src/debug.c index 1ec7c497..2391f737 100644 --- a/src/debug.c +++ b/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 */ From 795ad670f9f3e2c09f8fb4c26b11a080def4420e Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 12:27:18 +0100 Subject: [PATCH 058/122] RESP3: DEBUG PROTOCOL command. Only types already supported by API. --- src/debug.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/debug.c b/src/debug.c index 2391f737..c9435e72 100644 --- a/src/debug.c +++ b/src/debug.c @@ -529,6 +529,49 @@ 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")) { + addReplyString(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); + } 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 { + 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; From 4e2dd54df0a0fc305024a0b0df9d8a604ab7f6e1 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 12:31:10 +0100 Subject: [PATCH 059/122] RESP3: DEBUG PROTOCOL: fix strcasecmp() check. --- src/debug.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/debug.c b/src/debug.c index c9435e72..d43dcc97 100644 --- a/src/debug.c +++ b/src/debug.c @@ -533,26 +533,26 @@ NULL /* 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")) { + if (!strcasecmp(name,"string")) { addReplyBulkCString(c,"Hello World"); - } else if (strcasecmp(name,"integer")) { + } else if (!strcasecmp(name,"integer")) { addReplyLongLong(c,12345); - } else if (strcasecmp(name,"double")) { + } else if (!strcasecmp(name,"double")) { addReplyDouble(c,3.14159265359); - } else if (strcasecmp(name,"bignum")) { + } else if (!strcasecmp(name,"bignum")) { addReplyString(c,"(1234567999999999999999999999999999999\r\n",40); - } else if (strcasecmp(name,"null")) { + } else if (!strcasecmp(name,"null")) { addReplyNull(c); - } else if (strcasecmp(name,"array")) { + } else if (!strcasecmp(name,"array")) { addReplyArrayLen(c,3); for (int j = 0; j < 3; j++) addReplyLongLong(c,j); - } else if (strcasecmp(name,"set")) { + } else if (!strcasecmp(name,"set")) { addReplySetLen(c,3); for (int j = 0; j < 3; j++) addReplyLongLong(c,j); - } else if (strcasecmp(name,"map")) { + } else if (!strcasecmp(name,"map")) { addReplyMapLen(c,3); for (int j = 0; j < 3; j++) addReplyLongLong(c,j); - } else if (strcasecmp(name,"attrib")) { + } else if (!strcasecmp(name,"attrib")) { addReplyAttributeLen(c,1); addReplyBulkCString(c,"key-popularity"); addReplyArrayLen(c,2); @@ -561,7 +561,7 @@ NULL /* 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")) { + } else if (!strcasecmp(name,"push")) { addReplyPushLen(c,2); addReplyBulkCString(c,"server-cpu-usage"); addReplyLongLong(c,42); From 8042afb246daaaaeb5a76614b2791c082ecb6654 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 12:35:28 +0100 Subject: [PATCH 060/122] RESP3: Fix addReplyBool() RESP2/3 output. --- src/debug.c | 5 ++++- src/networking.c | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/debug.c b/src/debug.c index d43dcc97..9029871e 100644 --- a/src/debug.c +++ b/src/debug.c @@ -551,7 +551,10 @@ NULL 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); + 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"); diff --git a/src/networking.c b/src/networking.c index 789ea822..75312a1f 100644 --- a/src/networking.c +++ b/src/networking.c @@ -612,7 +612,7 @@ void addReplyNull(client *c) { } void addReplyBool(client *c, int b) { - if (c->resp == 3) { + if (c->resp == 2) { addReply(c, b ? shared.cone : shared.czero); } else { addReplyString(c, b ? "#t\r\n" : "#f\r\n",4); From afba21129769f10604559a7a62fc39ac711faf27 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 16:39:27 +0100 Subject: [PATCH 061/122] RESP3: DEBUG PROTOCOL: boolean types. --- src/debug.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/debug.c b/src/debug.c index 9029871e..d1824dfd 100644 --- a/src/debug.c +++ b/src/debug.c @@ -572,6 +572,10 @@ NULL * 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 { 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"); } From e2911703855cd499e3323fcb750eac3847d1e18f Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 16:55:20 +0100 Subject: [PATCH 062/122] RESP3: verbatim reply API + DEBUG PROTOCOL support. --- src/debug.c | 2 ++ src/networking.c | 29 +++++++++++++++++++++++++++++ src/server.h | 1 + 3 files changed, 32 insertions(+) diff --git a/src/debug.c b/src/debug.c index d1824dfd..6cc734c3 100644 --- a/src/debug.c +++ b/src/debug.c @@ -576,6 +576,8 @@ NULL 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"); } diff --git a/src/networking.c b/src/networking.c index 75312a1f..6b13db8e 100644 --- a/src/networking.c +++ b/src/networking.c @@ -696,6 +696,35 @@ 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++; + } + } + addReplyString(c,buf,preflen); + addReplyString(c,s,len); + addReplyString(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 diff --git a/src/server.h b/src/server.h index 0362c03a..8c1e6a77 100644 --- a/src/server.h +++ b/src/server.h @@ -1440,6 +1440,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); 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 addReplyString(client *c, const char *s, size_t len); void addReplyBulk(client *c, robj *obj); void addReplyBulkCString(client *c, const char *s); From dfa9d2c74ca8b4bc66390d49d1d52cb6b325e16e Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 17:10:11 +0100 Subject: [PATCH 063/122] RESP3: hiredis: implement bool type. --- deps/hiredis/hiredis.c | 25 ++++++++++++++++++++++++- deps/hiredis/read.c | 10 ++++++++++ deps/hiredis/read.h | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c index 193168a0..0947d1ed 100644 --- a/deps/hiredis/hiredis.c +++ b/deps/hiredis/hiredis.c @@ -49,6 +49,7 @@ 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. */ @@ -58,6 +59,7 @@ static redisReplyObjectFunctions defaultFunctions = { createIntegerObject, createDoubleObject, createNilObject, + createBoolObject, freeReplyObject }; @@ -223,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; diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c index 511503cb..c75c3435 100644 --- a/deps/hiredis/read.c +++ b/deps/hiredis/read.c @@ -313,6 +313,12 @@ static int processLineItem(redisReader *r) { 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) @@ -513,6 +519,9 @@ static int processItem(redisReader *r) { case '~': cur->type = REDIS_REPLY_SET; break; + case '#': + cur->type = REDIS_REPLY_BOOL; + break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; @@ -530,6 +539,7 @@ static int processItem(redisReader *r) { 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); diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h index db267f81..f3d07584 100644 --- a/deps/hiredis/read.h +++ b/deps/hiredis/read.h @@ -83,6 +83,7 @@ typedef struct redisReplyObjectFunctions { 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; From 7d4b600f5df045eb08f49bb33af9a4a75cb480b2 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 10 Dec 2018 17:11:42 +0100 Subject: [PATCH 064/122] RESP3: redis-cli support for boolean in TTY output. --- src/redis-cli.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/redis-cli.c b/src/redis-cli.c index b8934eda..a74493ef 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -822,6 +822,9 @@ 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: From 709a6612eb07a8d9fd78c3421baff5fb8e8d698c Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 17 Dec 2018 16:59:19 +0100 Subject: [PATCH 065/122] RESP3: addReplyString() -> addReplyProto(). The function naming was totally nuts. Let's fix it as we break PRs anyway with RESP3 refactoring and changes. --- src/debug.c | 2 +- src/module.c | 4 ++-- src/networking.c | 58 +++++++++++++++++++++++------------------------ src/replication.c | 2 +- src/server.h | 2 +- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/debug.c b/src/debug.c index 6cc734c3..a98bc61a 100644 --- a/src/debug.c +++ b/src/debug.c @@ -540,7 +540,7 @@ NULL } else if (!strcasecmp(name,"double")) { addReplyDouble(c,3.14159265359); } else if (!strcasecmp(name,"bignum")) { - addReplyString(c,"(1234567999999999999999999999999999999\r\n",40); + addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40); } else if (!strcasecmp(name,"null")) { addReplyNull(c); } else if (!strcasecmp(name,"array")) { diff --git a/src/module.c b/src/module.c index ac935d5c..8954fcdf 100644 --- a/src/module.c +++ b/src/module.c @@ -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; diff --git a/src/networking.c b/src/networking.c index 6b13db8e..0a32f311 100644 --- a/src/networking.c +++ b/src/networking.c @@ -253,7 +253,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); @@ -300,7 +300,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 @@ -308,7 +308,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()"); } @@ -323,7 +323,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); } @@ -333,12 +333,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. @@ -352,9 +352,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 @@ -397,9 +397,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) { @@ -502,7 +502,7 @@ void addReplyDouble(client *c, double d) { if (c->resp == 2) { addReplyBulkCString(c, d > 0 ? "inf" : "-inf"); } else { - addReplyString(c, d > 0 ? ",inf\r\n" : "-inf\r\n", + addReplyProto(c, d > 0 ? ",inf\r\n" : "-inf\r\n", d > 0 ? 6 : 7); } } else { @@ -512,10 +512,10 @@ void addReplyDouble(client *c, double d) { if (c->resp == 2) { dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d); slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf); - addReplyString(c,sbuf,slen); + addReplyProto(c,sbuf,slen); } else { dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d); - addReplyString(c,dbuf,dlen); + addReplyProto(c,dbuf,dlen); } } } @@ -531,9 +531,9 @@ void addReplyHumanLongDouble(client *c, long double d) { } else { char buf[MAX_LONG_DOUBLE_CHARS]; int len = ld2string(buf,sizeof(buf),d,1); - addReplyString(c,",",1); - addReplyString(c,buf,len); - addReplyString(c,"\r\n",2); + addReplyProto(c,",",1); + addReplyProto(c,buf,len); + addReplyProto(c,"\r\n",2); } } @@ -558,7 +558,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) { @@ -605,9 +605,9 @@ void addReplyPushLen(client *c, long length) { void addReplyNull(client *c) { if (c->resp == 2) { - addReplyString(c,"$-1\r\n",5); + addReplyProto(c,"$-1\r\n",5); } else { - addReplyString(c,"_\r\n",3); + addReplyProto(c,"_\r\n",3); } } @@ -615,7 +615,7 @@ void addReplyBool(client *c, int b) { if (c->resp == 2) { addReply(c, b ? shared.cone : shared.czero); } else { - addReplyString(c, b ? "#t\r\n" : "#f\r\n",4); + addReplyProto(c, b ? "#t\r\n" : "#f\r\n",4); } } @@ -625,9 +625,9 @@ void addReplyBool(client *c, int b) { * Null type "_\r\n". */ void addReplyNullArray(client *c) { if (c->resp == 2) { - addReplyString(c,"*-1\r\n",5); + addReplyProto(c,"*-1\r\n",5); } else { - addReplyString(c,"_\r\n",3); + addReplyProto(c,"_\r\n",3); } } @@ -667,7 +667,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); } @@ -719,9 +719,9 @@ void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext) { p[i] = *ext++; } } - addReplyString(c,buf,preflen); - addReplyString(c,s,len); - addReplyString(c,"\r\n",2); + addReplyProto(c,buf,preflen); + addReplyProto(c,s,len); + addReplyProto(c,"\r\n",2); } } diff --git a/src/replication.c b/src/replication.c index 9508528d..1ca8641a 100644 --- a/src/replication.c +++ b/src/replication.c @@ -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); } } diff --git a/src/server.h b/src/server.h index 8c1e6a77..ed6f1a7d 100644 --- a/src/server.h +++ b/src/server.h @@ -1441,7 +1441,7 @@ 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 addReplyString(client *c, const char *s, size_t len); +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); From 798a32919291968d9ea44e6266ba5f8d505f4263 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 18 Dec 2018 12:19:24 +0100 Subject: [PATCH 066/122] RESP3: extract code to send pubsub messages into functions. --- src/pubsub.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/pubsub.c b/src/pubsub.c index 335c52d9..12b252cf 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -221,6 +221,25 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) { return count; } +/* Send a pubsub message of type "message" to the client. */ +void addReplyPubsubMessage(client *c, robj *channel, robj *msg) { + addReply(c,shared.mbulkhdr[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) { + addReply(c,shared.mbulkhdr[4]); + addReply(c,shared.pmessagebulk); + addReplyBulk(c,pat); + addReplyBulk(c,channel); + addReplyBulk(c,msg); +} + /* Publish a message */ int pubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; @@ -238,11 +257,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 +271,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++; } } From bc75a94e2d00ff0cccde475de0980a89bbc641e9 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 18 Dec 2018 12:33:51 +0100 Subject: [PATCH 067/122] RESP3: pubsub messages API completely refactored. --- src/pubsub.c | 132 +++++++++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 57 deletions(-) diff --git a/src/pubsub.c b/src/pubsub.c index 12b252cf..0bf615eb 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -29,6 +29,75 @@ #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) { + addReply(c,shared.mbulkhdr[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) { + addReply(c,shared.mbulkhdr[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) { + addReply(c,shared.mbulkhdr[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) { + addReply(c,shared.mbulkhdr[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) { + addReply(c,shared.mbulkhdr[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) { + addReply(c,shared.mbulkhdr[3]); + addReply(c,shared.punsubscribebulk); + if (pattern) + addReplyBulk(c,pattern); + else + addReplyNull(c); + addReplyLongLong(c,clientSubscriptionsCount(c)); +} + /*----------------------------------------------------------------------------- * Pubsub low level API *----------------------------------------------------------------------------*/ @@ -76,10 +145,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 +177,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 +197,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 +218,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 +236,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); - addReplyNull(c); - addReplyLongLong(c,dictSize(c->pubsub_channels)+ - listLength(c->pubsub_patterns)); - } + if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL); dictReleaseIterator(di); return count; } @@ -210,36 +254,10 @@ 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); - addReplyNull(c); - addReplyLongLong(c,dictSize(c->pubsub_channels)+ - listLength(c->pubsub_patterns)); - } + if (notify && count == 0) addReplyPubsubPatUnsubscribed(c,NULL); return count; } -/* Send a pubsub message of type "message" to the client. */ -void addReplyPubsubMessage(client *c, robj *channel, robj *msg) { - addReply(c,shared.mbulkhdr[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) { - addReply(c,shared.mbulkhdr[4]); - addReply(c,shared.pmessagebulk); - addReplyBulk(c,pat); - addReplyBulk(c,channel); - addReplyBulk(c,msg); -} - /* Publish a message */ int pubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; From eaaac0889223406bef4e7ee34d75616ac6599b36 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 19 Dec 2018 17:41:15 +0100 Subject: [PATCH 068/122] RESP3: Pubsub messages in new push format if client is in RESP3 mode. --- src/pubsub.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/pubsub.c b/src/pubsub.c index 0bf615eb..994dd973 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -37,7 +37,10 @@ int clientSubscriptionsCount(client *c); /* Send a pubsub message of type "message" to the client. */ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) { - addReply(c,shared.mbulkhdr[3]); + if (c->resp == 2) + addReply(c,shared.mbulkhdr[3]); + else + addReplyPushLen(c,3); addReply(c,shared.messagebulk); addReplyBulk(c,channel); addReplyBulk(c,msg); @@ -47,7 +50,10 @@ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) { * 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) { - addReply(c,shared.mbulkhdr[4]); + if (c->resp == 2) + addReply(c,shared.mbulkhdr[4]); + else + addReplyPushLen(c,4); addReply(c,shared.pmessagebulk); addReplyBulk(c,pat); addReplyBulk(c,channel); @@ -56,7 +62,10 @@ void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) { /* Send the pubsub subscription notification to the client. */ void addReplyPubsubSubscribed(client *c, robj *channel) { - addReply(c,shared.mbulkhdr[3]); + if (c->resp == 2) + addReply(c,shared.mbulkhdr[3]); + else + addReplyPushLen(c,3); addReply(c,shared.subscribebulk); addReplyBulk(c,channel); addReplyLongLong(c,clientSubscriptionsCount(c)); @@ -67,7 +76,10 @@ void addReplyPubsubSubscribed(client *c, robj *channel) { * unsubscribe command but there are no channels to unsubscribe from: we * still send a notification. */ void addReplyPubsubUnsubscribed(client *c, robj *channel) { - addReply(c,shared.mbulkhdr[3]); + if (c->resp == 2) + addReply(c,shared.mbulkhdr[3]); + else + addReplyPushLen(c,3); addReply(c,shared.unsubscribebulk); if (channel) addReplyBulk(c,channel); @@ -78,7 +90,10 @@ void addReplyPubsubUnsubscribed(client *c, robj *channel) { /* Send the pubsub pattern subscription notification to the client. */ void addReplyPubsubPatSubscribed(client *c, robj *pattern) { - addReply(c,shared.mbulkhdr[3]); + if (c->resp == 2) + addReply(c,shared.mbulkhdr[3]); + else + addReplyPushLen(c,3); addReply(c,shared.psubscribebulk); addReplyBulk(c,pattern); addReplyLongLong(c,clientSubscriptionsCount(c)); @@ -89,7 +104,10 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) { * punsubscribe command but there are no pattern to unsubscribe from: we * still send a notification. */ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) { - addReply(c,shared.mbulkhdr[3]); + if (c->resp == 2) + addReply(c,shared.mbulkhdr[3]); + else + addReplyPushLen(c,3); addReply(c,shared.punsubscribebulk); if (pattern) addReplyBulk(c,pattern); From a4f8f4a8246965c28a2ed3906a0567fbbfe09440 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 19 Dec 2018 17:55:07 +0100 Subject: [PATCH 069/122] RESP3: PING should reply normally in RESP3 Pub/Sub mode. Because in RESP3 commands can be sent in the Pub/Sub connection without problems, so it's better if in such mode there is no exception about PING. --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 8cdbdad7..c5b66788 100644 --- a/src/server.c +++ b/src/server.c @@ -2924,7 +2924,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) From 9018388c3f2d183f5110b57649d3239bd699291e Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 19 Dec 2018 17:56:50 +0100 Subject: [PATCH 070/122] RESP3: Allow any command in RESP3 Pub/Sub mode. --- src/server.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server.c b/src/server.c index c5b66788..d85e10d9 100644 --- a/src/server.c +++ b/src/server.c @@ -2681,8 +2681,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 && From 4d80b0e9657bed6db68debdc5aeab62121d27835 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 21 Dec 2018 17:11:52 +0100 Subject: [PATCH 071/122] RESP3: allow HELLO during busy script and not authenticated states. --- src/server.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index d85e10d9..6c7692ed 100644 --- a/src/server.c +++ b/src/server.c @@ -2582,7 +2582,9 @@ int processCommand(client *c) { } /* Check if the user is authenticated */ - if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) + if (server.requirepass && + !c->authenticated && + (c->cmd->proc != authCommand || c->cmd->proc == helloCommand)) { flagTransaction(c); addReply(c,shared.noautherr); @@ -2715,6 +2717,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 && From b43d70df568d85ba0fb79c9e0129d4654ef471ee Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 21 Dec 2018 17:16:22 +0100 Subject: [PATCH 072/122] ACL: refactoring of the original authentication code. --- src/Makefile | 2 +- src/acl.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/server.c | 44 +----------------------- src/server.h | 3 ++ 4 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 src/acl.c diff --git a/src/Makefile b/src/Makefile index 9edbb458..adf32d55 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/acl.c b/src/acl.c new file mode 100644 index 00000000..5155f947 --- /dev/null +++ b/src/acl.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "server.h" + +/* 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. */ +} + +/* 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) { + /* For now only the "default" user is allowed. When the RCP1 ACLs + * will be implemented multiple usernames will be supproted. */ + if (username != NULL && strcmp(username->ptr,"default")) { + errno = ENOENT; + return C_ERR; + } + + /* For now we just compare the password with the system wide one. */ + if (!time_independent_strcmp(password->ptr, server.requirepass)) { + return C_OK; + } else { + errno = EINVAL; + return C_ERR; + } +} diff --git a/src/server.c b/src/server.c index 6c7692ed..7a813404 100644 --- a/src/server.c +++ b/src/server.c @@ -2864,52 +2864,10 @@ 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. - * - * 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. */ -} - 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)) { + } else if (ACLCheckUserCredentials(NULL,c->argv[1]->ptr)) { c->authenticated = 1; addReply(c,shared.ok); } else { diff --git a/src/server.h b/src/server.h index ed6f1a7d..909e6c3d 100644 --- a/src/server.h +++ b/src/server.h @@ -1648,6 +1648,9 @@ void closeChildInfoPipe(void); void sendChildInfo(int process_type); void receiveChildInfo(void); +/* acl.c -- Authentication related prototypes. */ +int ACLCheckUserCredentials(robj *username, robj *password); + /* Sorted sets data type */ /* Input flags. */ From f5d918b2bb1bae3c84dee635ac13526779e9294c Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 21 Dec 2018 17:24:14 +0100 Subject: [PATCH 073/122] ACL: HELLO should stop if the user is not authenticated. --- src/networking.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/networking.c b/src/networking.c index 0a32f311..5657a764 100644 --- a/src/networking.c +++ b/src/networking.c @@ -2053,6 +2053,15 @@ void helloCommand(client *c) { 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 " + "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); From 42271cff2dcd0a30ef337e7284e134053c91f852 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 9 Jan 2019 17:09:30 +0100 Subject: [PATCH 074/122] ACL: fix ACLCheckUserCredentials() usage in AUTH. --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 7a813404..f19a240c 100644 --- a/src/server.c +++ b/src/server.c @@ -2867,7 +2867,7 @@ int writeCommandsDeniedByDiskError(void) { void authCommand(client *c) { if (!server.requirepass) { addReplyError(c,"Client sent AUTH, but no password is set"); - } else if (ACLCheckUserCredentials(NULL,c->argv[1]->ptr)) { + } else if (ACLCheckUserCredentials(NULL,c->argv[1]) == C_OK) { c->authenticated = 1; addReply(c,shared.ok); } else { From 91f1d8026b6697ee0649c7d2a8955be563e31863 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 9 Jan 2019 17:20:47 +0100 Subject: [PATCH 075/122] ACL: introduce the concept of command ID. --- src/server.c | 403 ++++++++++++++++++++++++++------------------------- src/server.h | 5 + 2 files changed, 207 insertions(+), 201 deletions(-) diff --git a/src/server.c b/src/server.c index f19a240c..5c15d223 100644 --- a/src/server.c +++ b/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. @@ -125,207 +126,207 @@ 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}, - {"hello",helloCommand,-2,"sF",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} }; /*============================ Utility functions ============================ */ diff --git a/src/server.h b/src/server.h index 909e6c3d..a004acb2 100644 --- a/src/server.h +++ b/src/server.h @@ -1305,6 +1305,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 { From 010b24f864134bc8f52228e6bbf6f8c52cecd5b6 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 9 Jan 2019 17:23:23 +0100 Subject: [PATCH 076/122] ACL: set the command ID while populating the commands table. --- src/server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server.c b/src/server.c index 5c15d223..df948887 100644 --- a/src/server.c +++ b/src/server.c @@ -2202,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; @@ -2222,6 +2224,8 @@ void populateCommandTable(void) { f++; } + c->id = j; /* Sequential ID for each command. Used for ACLs. */ + retval1 = dictAdd(server.commands, sdsnew(c->name), c); /* Populate an additional dictionary that will be unaffected * by rename-command statements in redis.conf. */ From 7fc882c57854e952c866a7925e574dbf5db162bd Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 9 Jan 2019 21:31:29 +0100 Subject: [PATCH 077/122] ACL: use a fixed table for command IDs. --- src/acl.c | 17 +++++++++++++++++ src/server.c | 3 +-- src/server.h | 9 +++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/acl.c b/src/acl.c index 5155f947..dd21e2f0 100644 --- a/src/acl.c +++ b/src/acl.c @@ -93,3 +93,20 @@ int ACLCheckUserCredentials(robj *username, robj *password) { 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; + 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++; +} diff --git a/src/server.c b/src/server.c index df948887..c297e912 100644 --- a/src/server.c +++ b/src/server.c @@ -2224,8 +2224,7 @@ void populateCommandTable(void) { f++; } - c->id = j; /* Sequential ID for each command. Used for ACLs. */ - + 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. */ diff --git a/src/server.h b/src/server.h index a004acb2..1eb66e01 100644 --- a/src/server.h +++ b/src/server.h @@ -1352,6 +1352,14 @@ typedef struct { dictIterator *di; } setTypeIterator; +/* 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 +typedef struct user { + uint64_t allowed_commands[USER_MAX_COMMAND_BIT/64]; +} user; + /* Structure to hold hash iteration abstraction. Note that iteration over * hashes involves both fields and values. Because it is possible that * not both are required, store pointers in the iterator to avoid @@ -1655,6 +1663,7 @@ void receiveChildInfo(void); /* acl.c -- Authentication related prototypes. */ int ACLCheckUserCredentials(robj *username, robj *password); +unsigned long ACLGetCommandID(const char *cmdname); /* Sorted sets data type */ From 45ff9f33d663c9d2db6a76f739d8c8f507808182 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 9 Jan 2019 21:47:43 +0100 Subject: [PATCH 078/122] ACL: ACLCheckUserCredentials() next id should be static. --- src/acl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index dd21e2f0..8ce84c6d 100644 --- a/src/acl.c +++ b/src/acl.c @@ -102,7 +102,7 @@ int ACLCheckUserCredentials(robj *username, robj *password) { * are unloaded and later reloaded. */ unsigned long ACLGetCommandID(const char *cmdname) { static rax *map = NULL; - unsigned long nextid = 0; + static unsigned long nextid = 0; if (map == NULL) map = raxNew(); void *id = raxFind(map,(unsigned char*)cmdname,strlen(cmdname)); From 4729f71495b008788e356476ed12b1a0ddfa11ca Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 10 Jan 2019 12:47:52 +0100 Subject: [PATCH 079/122] ACL: improved version of the user structure. --- src/server.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/server.h b/src/server.h index 1eb66e01..0ad37862 100644 --- a/src/server.h +++ b/src/server.h @@ -1356,8 +1356,27 @@ typedef struct { * 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 +#define USER_FLAG_ENABLED (1<<0) /* The user is active. */ typedef struct user { + 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. */ } user; /* Structure to hold hash iteration abstraction. Note that iteration over From 4278104acc054d09623ca2389107b46a79a7a336 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 10 Jan 2019 16:33:48 +0100 Subject: [PATCH 080/122] ACL: add a reference to the user in each client. --- src/acl.c | 5 +++++ src/networking.c | 1 + src/server.h | 56 +++++++++++++++++++++++++----------------------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/acl.c b/src/acl.c index 8ce84c6d..b624ff3b 100644 --- a/src/acl.c +++ b/src/acl.c @@ -110,3 +110,8 @@ unsigned long ACLGetCommandID(const char *cmdname) { 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) { + return NULL; +} diff --git a/src/networking.c b/src/networking.c index 5657a764..07851a36 100644 --- a/src/networking.c +++ b/src/networking.c @@ -119,6 +119,7 @@ client *createClient(int fd) { c->argc = 0; c->argv = NULL; c->cmd = c->lastcmd = NULL; + c->user = ACLGetUserByName("default",7); c->multibulklen = 0; c->bulklen = -1; c->sentlen = 0; diff --git a/src/server.h b/src/server.h index 0ad37862..22224f29 100644 --- a/src/server.h +++ b/src/server.h @@ -707,6 +707,33 @@ 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 +#define USER_FLAG_ENABLED (1<<0) /* The user is active. */ +typedef struct user { + 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. */ +} user; + /* With multiplexing we need to take per-client state. * Clients are taken in a linked list. */ typedef struct client { @@ -725,6 +752,7 @@ 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. */ 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. */ @@ -1352,33 +1380,6 @@ typedef struct { dictIterator *di; } setTypeIterator; -/* 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 -#define USER_FLAG_ENABLED (1<<0) /* The user is active. */ -typedef struct user { - 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. */ -} user; - /* Structure to hold hash iteration abstraction. Note that iteration over * hashes involves both fields and values. Because it is possible that * not both are required, store pointers in the iterator to avoid @@ -1683,6 +1684,7 @@ void receiveChildInfo(void); /* acl.c -- Authentication related prototypes. */ int ACLCheckUserCredentials(robj *username, robj *password); unsigned long ACLGetCommandID(const char *cmdname); +user *ACLGetUserByName(const char *name, size_t namelen); /* Sorted sets data type */ From e9a902a95802367462cf42d9f950498ccf00b74b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 10 Jan 2019 16:35:55 +0100 Subject: [PATCH 081/122] ACL: split acl.c into clear sections. --- src/acl.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/acl.c b/src/acl.c index b624ff3b..2202f293 100644 --- a/src/acl.c +++ b/src/acl.c @@ -29,6 +29,10 @@ #include "server.h" +/* ============================================================================= + * 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 @@ -71,6 +75,10 @@ int time_independent_strcmp(char *a, char *b) { return diff; /* If zero strings are the same. */ } +/* ============================================================================= + * Low level ACL API + * ==========================================================================*/ + /* Check the username and password pair and return C_OK if they are valid, * otherwise C_ERR is returned and errno is set to: * @@ -115,3 +123,7 @@ unsigned long ACLGetCommandID(const char *cmdname) { user *ACLGetUserByName(const char *name, size_t namelen) { return NULL; } + +/* ============================================================================= + * ACL related commands + * ==========================================================================*/ From 29c88a9ce593639599076bd1f564306e38ea7428 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 10 Jan 2019 16:39:32 +0100 Subject: [PATCH 082/122] ACL: initialization function. --- src/acl.c | 11 +++++++++++ src/server.c | 1 + src/server.h | 1 + 3 files changed, 13 insertions(+) diff --git a/src/acl.c b/src/acl.c index 2202f293..269e4b40 100644 --- a/src/acl.c +++ b/src/acl.c @@ -29,6 +29,12 @@ #include "server.h" +/* ============================================================================= + * Global state for ACLs + * ==========================================================================*/ + +rax *Users; /* Table mapping usernames to user structures. */ + /* ============================================================================= * Helper functions for the rest of the ACL implementation * ==========================================================================*/ @@ -79,6 +85,11 @@ int time_independent_strcmp(char *a, char *b) { * Low level ACL API * ==========================================================================*/ +/* Initialization of the ACL subsystem. */ +void ACLInit(void) { + Users = raxNew(); +} + /* Check the username and password pair and return C_OK if they are valid, * otherwise C_ERR is returned and errno is set to: * diff --git a/src/server.c b/src/server.c index c297e912..48b4cdb4 100644 --- a/src/server.c +++ b/src/server.c @@ -2185,6 +2185,7 @@ void initServer(void) { if (server.cluster_enabled) clusterInit(); replicationScriptCacheInit(); scriptingInit(1); + ACLInit(); slowlogInit(); latencyMonitorInit(); bioInit(); diff --git a/src/server.h b/src/server.h index 22224f29..e120e989 100644 --- a/src/server.h +++ b/src/server.h @@ -1682,6 +1682,7 @@ void sendChildInfo(int process_type); void receiveChildInfo(void); /* acl.c -- Authentication related prototypes. */ +void ACLInit(void); int ACLCheckUserCredentials(robj *username, robj *password); unsigned long ACLGetCommandID(const char *cmdname); user *ACLGetUserByName(const char *name, size_t namelen); From e4846b028d77b6ee3ee2bdc386104eeba8ba7c85 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 10 Jan 2019 16:40:45 +0100 Subject: [PATCH 083/122] ACL: implement ACLGetUserByName(). --- src/acl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 269e4b40..7550b22b 100644 --- a/src/acl.c +++ b/src/acl.c @@ -132,7 +132,9 @@ unsigned long ACLGetCommandID(const char *cmdname) { /* Return an username by its name, or NULL if the user does not exist. */ user *ACLGetUserByName(const char *name, size_t namelen) { - return NULL; + void *myuser = raxFind(Users,(unsigned char*)name,namelen); + if (myuser == raxNotFound) return NULL; + return myuser; } /* ============================================================================= From 6bb6a6d3a8fb017e6b2e981c76d44ebe56b25b65 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 10 Jan 2019 17:01:12 +0100 Subject: [PATCH 084/122] ACL: implement ACLCreateUser(). --- src/acl.c | 20 ++++++++++++++++++++ src/server.h | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 7550b22b..2dafcbea 100644 --- a/src/acl.c +++ b/src/acl.c @@ -85,6 +85,26 @@ int time_independent_strcmp(char *a, char *b) { * Low level ACL API * ==========================================================================*/ +/* 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->flags = 0; + u->allowed_subcommands = NULL; + u->passwords = listCreate(); + u->patterns = NULL; /* Just created users cannot access to any key, however + if the "~*" directive was enabled to match all the + keys, the user will be flagged with the ALLKEYS + flag. */ + memset(u->allowed_commands,0,sizeof(u->allowed_commands)); + raxInsert(Users,(unsigned char*)name,namelen,u,NULL); + return u; +} + /* Initialization of the ACL subsystem. */ void ACLInit(void) { Users = raxNew(); diff --git a/src/server.h b/src/server.h index e120e989..c0901939 100644 --- a/src/server.h +++ b/src/server.h @@ -712,6 +712,7 @@ typedef struct readyList { * If there is no associated user, the connection uses the default user. */ #define USER_MAX_COMMAND_BIT 1024 #define USER_FLAG_ENABLED (1<<0) /* The user is active. */ +#define USER_FLAG_ALLKEYS (1<<1) /* The user can mention any key. */ typedef struct user { uint64_t flags; /* See USER_FLAG_* */ @@ -731,7 +732,9 @@ typedef struct user { * 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. */ + 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. From dc4f7ad106b69cd6208becd39f35d263b4d1e61b Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 11 Jan 2019 11:02:55 +0100 Subject: [PATCH 085/122] ACL: create the default user. --- src/acl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 2dafcbea..75ffe044 100644 --- a/src/acl.c +++ b/src/acl.c @@ -34,6 +34,10 @@ * ==========================================================================*/ 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 @@ -90,7 +94,7 @@ int time_independent_strcmp(char *a, char *b) { * the structure representing the user. * * If the user with such name already exists NULL is returned. */ -user *ACLcreateUser(const char *name, size_t namelen) { +user *ACLCreateUser(const char *name, size_t namelen) { if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL; user *u = zmalloc(sizeof(*u)); u->flags = 0; @@ -108,6 +112,7 @@ user *ACLcreateUser(const char *name, size_t namelen) { /* Initialization of the ACL subsystem. */ void ACLInit(void) { Users = raxNew(); + DefaultUser = ACLCreateUser("default",7); } /* Check the username and password pair and return C_OK if they are valid, From 45952df700c57d8a0062317959845120a66acc38 Mon Sep 17 00:00:00 2001 From: charsyam Date: Fri, 11 Jan 2019 19:12:06 +0900 Subject: [PATCH 086/122] fix segmentfault when server start --- src/server.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 48b4cdb4..17d47974 100644 --- a/src/server.c +++ b/src/server.c @@ -2185,7 +2185,6 @@ void initServer(void) { if (server.cluster_enabled) clusterInit(); replicationScriptCacheInit(); scriptingInit(1); - ACLInit(); slowlogInit(); latencyMonitorInit(); bioInit(); @@ -4023,6 +4022,9 @@ int main(int argc, char **argv) { dictSetHashFunctionSeed((uint8_t*)hashseed); server.sentinel_mode = checkForSentinelMode(argc,argv); initServerConfig(); + + /* ACLInit should run before calling moduleInitModulesSystem */ + ACLInit(); moduleInitModulesSystem(); /* Store the executable path and arguments in a safe place in order From 4b72d087e9b3cbbe831d90d6612dfcba5922f1d2 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 11 Jan 2019 11:25:55 +0100 Subject: [PATCH 087/122] ACL: ACLSetUser(), initial ideas in comments. --- src/acl.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/acl.c b/src/acl.c index 75ffe044..ea708db7 100644 --- a/src/acl.c +++ b/src/acl.c @@ -109,10 +109,41 @@ user *ACLCreateUser(const char *name, size_t namelen) { 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 + * off Disable the user + * + Allow the execution of that command + * - Disallow the execution of that command + * +@ 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. + * +|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 "+". + * ~ Set 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. + * > Add this passowrd to the list of valid password for the user. + * For example >mypass will add "mypass" to the list. + * < Remove this password from the list of valid passwords. + * resetpass Flush the list of allowed passwords. + */ +void ACLSetUser(user *u, const char *op) { +} + /* Initialization of the ACL subsystem. */ void ACLInit(void) { Users = raxNew(); DefaultUser = ACLCreateUser("default",7); + ACLSetUser(DefaultUser,"+@all"); + ACLSetUser(DefaultUser,"on"); } /* Check the username and password pair and return C_OK if they are valid, From 7f8314760a764a1cfb7405b414e71fc6eba79d90 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 11 Jan 2019 11:30:09 +0100 Subject: [PATCH 088/122] ACL: modify comment from PR. --- src/server.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/server.c b/src/server.c index 17d47974..a63b7522 100644 --- a/src/server.c +++ b/src/server.c @@ -4022,9 +4022,8 @@ int main(int argc, char **argv) { dictSetHashFunctionSeed((uint8_t*)hashseed); server.sentinel_mode = checkForSentinelMode(argc,argv); initServerConfig(); - - /* ACLInit should run before calling moduleInitModulesSystem */ - ACLInit(); + 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 From aced0328e3fb532496afa1a30eb4227316aef3bd Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 11 Jan 2019 11:32:41 +0100 Subject: [PATCH 089/122] ACL: avoid a radix tree lookup for the default user. --- src/networking.c | 2 +- src/server.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/networking.c b/src/networking.c index 07851a36..754f222d 100644 --- a/src/networking.c +++ b/src/networking.c @@ -119,7 +119,7 @@ client *createClient(int fd) { c->argc = 0; c->argv = NULL; c->cmd = c->lastcmd = NULL; - c->user = ACLGetUserByName("default",7); + c->user = DefaultUser; c->multibulklen = 0; c->bulklen = -1; c->sentlen = 0; diff --git a/src/server.h b/src/server.h index c0901939..a15e6cd9 100644 --- a/src/server.h +++ b/src/server.h @@ -1685,6 +1685,7 @@ void sendChildInfo(int process_type); void receiveChildInfo(void); /* acl.c -- Authentication related prototypes. */ +extern user *DefaultUser; void ACLInit(void); int ACLCheckUserCredentials(robj *username, robj *password); unsigned long ACLGetCommandID(const char *cmdname); From e7d15e4820f660e412974ae46b6b9ea61e59ace3 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 11 Jan 2019 13:03:50 +0100 Subject: [PATCH 090/122] ACL: implement to first trivial opcodes in ACLSetUser(). --- src/acl.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/acl.c b/src/acl.c index ea708db7..59540d2a 100644 --- a/src/acl.c +++ b/src/acl.c @@ -127,15 +127,38 @@ user *ACLCreateUser(const char *name, size_t namelen) { * disabled command. Note that this form is not * allowed as negative like -DEBUG|SEGFAULT, but * only additive starting with "+". - * ~ Set a pattern of keys that can be mentioned as part of + * ~ 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. * > Add this passowrd to the list of valid password for the user. * For example >mypass will add "mypass" to the list. * < Remove this password from the list of valid passwords. + * allkeys Alias for ~* * resetpass Flush the list of allowed passwords. + * 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 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. */ -void ACLSetUser(user *u, const char *op) { +int ACLSetUser(user *u, const char *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,"~*")) + { + memset(u->allowed_subcommands,255,sizeof(u->allowed_commands)); + u->flags |= USER_FLAG_ALLKEYS; + } else { + return C_ERR; + } + return C_OK; } /* Initialization of the ACL subsystem. */ From 67754ae021d5cb29dc9555a59847b652833a6e20 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 13:18:12 +0100 Subject: [PATCH 091/122] ACL: ACLSetUser(), fix flag and add allcommands +@all opcode. --- src/acl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 59540d2a..fdc0744a 100644 --- a/src/acl.c +++ b/src/acl.c @@ -153,8 +153,13 @@ int ACLSetUser(user *u, const char *op) { } else if (!strcasecmp(op,"allkeys") || !strcasecmp(op,"~*")) { - memset(u->allowed_subcommands,255,sizeof(u->allowed_commands)); u->flags |= USER_FLAG_ALLKEYS; + if (u->patterns) listEmpty(u->patterns); + } else if (!strcasecmp(op,"allcommands") || + !strcasecmp(op,"+@all")) + { + memset(u->allowed_subcommands,255,sizeof(u->allowed_commands)); + u->flags |= USER_FLAG_ALLCOMMANDS; } else { return C_ERR; } From 4376575d833cfb66a904c0035b5ca69dffad5179 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 13:19:42 +0100 Subject: [PATCH 092/122] ACL: ACLSetUser(), add allcommands in comment. --- src/acl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/acl.c b/src/acl.c index fdc0744a..30e19564 100644 --- a/src/acl.c +++ b/src/acl.c @@ -134,6 +134,7 @@ user *ACLCreateUser(const char *name, size_t namelen) { * > Add this passowrd to the list of valid password for the user. * For example >mypass will add "mypass" to the list. * < Remove this password from the list of valid passwords. + * allcommands Alias for +@all * allkeys Alias for ~* * resetpass Flush the list of allowed passwords. * resetkeys Flush the list of allowed keys patterns. From f95152d4c840ae4540224ad788f2f8e4a782b677 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 13:19:50 +0100 Subject: [PATCH 093/122] ACL: Add skeleton for function checking ability to execute a command. --- src/acl.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/acl.c b/src/acl.c index 30e19564..2da7f50e 100644 --- a/src/acl.c +++ b/src/acl.c @@ -222,6 +222,29 @@ user *ACLGetUserByName(const char *name, size_t namelen) { 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 C_OK is returned, otherwise + * C_ERR is returned. */ +int ACLCheckCommandPerm(client *c) { + /* If there is no associated user, the connection can run anything. */ + if (c->user == NULL) return C_OK; + + /* Check if the user can execute this command. */ + if (!(c->user->flags & USER_FLAG_ALLCOMMANDS)) { + } + + /* Check if the user can execute touch this keys. */ + if (!(c->user->flags & USER_FLAG_ALLKEYS)) { + } + + /* If we survived all the above checks, the user can execute the + * command. */ + return C_OK; +} + /* ============================================================================= * ACL related commands * ==========================================================================*/ From 648411eb7dcd5a16cfbd146823cff8aeeb269369 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 13:20:45 +0100 Subject: [PATCH 094/122] ACL: Add hook in processCommand() to check the ACLs before call(). --- src/server.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server.c b/src/server.c index a63b7522..49f62f37 100644 --- a/src/server.c +++ b/src/server.c @@ -2687,6 +2687,12 @@ int processCommand(client *c) { return C_OK; } + /* Check if the user can run this command according to the current + * ACLs. */ + if (ACLCheckCommandPerm(c) == C_ERR) { + addReplyErrorFormat(c,"-NOPERM this user has no permissions to run the %s command", cmd->name); + } + /* 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) && From 2da2e452ab49e7a7d7f45229e1d9c3a8ce02811a Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 13:21:21 +0100 Subject: [PATCH 095/122] ACL: ACLLCOMMAND flags. --- src/server.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server.h b/src/server.h index a15e6cd9..973d3631 100644 --- a/src/server.h +++ b/src/server.h @@ -713,6 +713,7 @@ typedef struct readyList { #define USER_MAX_COMMAND_BIT 1024 #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. */ typedef struct user { uint64_t flags; /* See USER_FLAG_* */ @@ -755,7 +756,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. */ + 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. */ From a0a4fb85ff33cddd2e71799f240ff3eda3cec01f Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 13:22:56 +0100 Subject: [PATCH 096/122] ACL: Fix compilation by adding prototype and c->cmd fix. --- src/server.c | 2 +- src/server.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 49f62f37..4027b9ba 100644 --- a/src/server.c +++ b/src/server.c @@ -2690,7 +2690,7 @@ int processCommand(client *c) { /* Check if the user can run this command according to the current * ACLs. */ if (ACLCheckCommandPerm(c) == C_ERR) { - addReplyErrorFormat(c,"-NOPERM this user has no permissions to run the %s command", cmd->name); + addReplyErrorFormat(c,"-NOPERM this user has no permissions to run the %s command", c->cmd->name); } /* Only allow a subset of commands in the context of Pub/Sub if the diff --git a/src/server.h b/src/server.h index 973d3631..e2204231 100644 --- a/src/server.h +++ b/src/server.h @@ -1693,6 +1693,7 @@ void ACLInit(void); int ACLCheckUserCredentials(robj *username, robj *password); unsigned long ACLGetCommandID(const char *cmdname); user *ACLGetUserByName(const char *name, size_t namelen); +int ACLCheckCommandPerm(client *c); /* Sorted sets data type */ From 09391369b871eb9def03a61bafa40c7cfedda6a7 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 16:09:29 +0100 Subject: [PATCH 097/122] ACL: fix field name typo causing segfault. --- src/acl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 2da7f50e..eb551958 100644 --- a/src/acl.c +++ b/src/acl.c @@ -159,7 +159,7 @@ int ACLSetUser(user *u, const char *op) { } else if (!strcasecmp(op,"allcommands") || !strcasecmp(op,"+@all")) { - memset(u->allowed_subcommands,255,sizeof(u->allowed_commands)); + memset(u->allowed_commands,255,sizeof(u->allowed_commands)); u->flags |= USER_FLAG_ALLCOMMANDS; } else { return C_ERR; From 733438fe23f67559d4da922c749664ed5db5dfc9 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 17:01:49 +0100 Subject: [PATCH 098/122] RESP3: Populate new fields for the AOF fake client. However we should remove this fake client ad-hoc creation, and replace it with the proper call to createClient(-1), and then adjust the fake client as we like. --- src/aof.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aof.c b/src/aof.c index 9723fc33..7d76c6f3 100644 --- a/src/aof.c +++ b/src/aof.c @@ -645,6 +645,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); From a2e376ba5293e2b1f67c856add6eb61fbd1e7692 Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 14 Jan 2019 18:35:21 +0100 Subject: [PATCH 099/122] ACL: ACLCheckCommandPerm() implementation WIP. --- src/acl.c | 34 ++++++++++++++++++++++++++++++---- src/server.h | 3 ++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/acl.c b/src/acl.c index eb551958..742860e6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -229,15 +229,41 @@ user *ACLGetUserByName(const char *name, size_t namelen) { * If the user can execute the command C_OK is returned, otherwise * C_ERR is returned. */ 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 (c->user == NULL) return C_OK; + if (u == NULL) return C_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 C_ERR; /* Check if the user can execute this command. */ - if (!(c->user->flags & USER_FLAG_ALLCOMMANDS)) { + if (!(u->flags & USER_FLAG_ALLCOMMANDS)) { + 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 C_ERR; + long subid = 0; + while (1) { + if (u->allowed_subcommands[id][subid] == NULL) return C_ERR; + if (!strcasecmp(c->argv[1]->ptr, + u->allowed_subcommands[id][subid])) + break; /* Subcommand match found. Stop here. */ + subid++; + } + } } - /* Check if the user can execute touch this keys. */ - if (!(c->user->flags & USER_FLAG_ALLKEYS)) { + /* 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)) + { } /* If we survived all the above checks, the user can execute the diff --git a/src/server.h b/src/server.h index e2204231..70cb0040 100644 --- a/src/server.h +++ b/src/server.h @@ -710,7 +710,8 @@ typedef struct 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 +#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. */ From 7aea02fa87b3b900d65102a5029a52871994ac15 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 09:36:12 +0100 Subject: [PATCH 100/122] ACL: initial implementation of the ACL command. --- src/acl.c | 36 ++++++++++++++++++++++++++++++++++++ src/server.c | 5 +++-- src/server.h | 1 + 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/acl.c b/src/acl.c index 742860e6..2c92cbfb 100644 --- a/src/acl.c +++ b/src/acl.c @@ -274,3 +274,39 @@ int ACLCheckCommandPerm(client *c) { /* ============================================================================= * ACL related commands * ==========================================================================*/ + +/* ACL -- show and modify the configuration of ACL users. + * ACL help + * ACL list + * ACL setuser ... user attribs ... + * ACL deluser + * ACL getuser + */ +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) != C_OK) { + addReplyErrorFormat(c,"Syntax error in ACL SETUSER modifier '%s'", + c->argv[j]->ptr); + return; + } + } + addReply(c,shared.ok); + } else if (!strcasecmp(sub,"help")) { + const char *help[] = { +"LIST -- List all the registered users.", +"SETUSER [attribs ...] -- Create or modify a user.", +"DELUSER -- Delete a user.", +"GETUSER -- Get the user details.", +NULL + }; + addReplyHelp(c,help); + } else { + addReplySubcommandSyntaxError(c); + } +} diff --git a/src/server.c b/src/server.c index 4027b9ba..3955c63c 100644 --- a/src/server.c +++ b/src/server.c @@ -115,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 @@ -326,7 +326,8 @@ struct redisCommand redisCommandTable[] = { {"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} + {"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 ============================ */ diff --git a/src/server.h b/src/server.h index 70cb0040..d8d45fcf 100644 --- a/src/server.h +++ b/src/server.h @@ -2186,6 +2186,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)); From 4f7ff85b8871a1048044c94338cf56b30c17dbcf Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 12:58:54 +0100 Subject: [PATCH 101/122] ACL: ability to set/remove user passwords. --- src/acl.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/acl.c b/src/acl.c index 2c92cbfb..95a4549f 100644 --- a/src/acl.c +++ b/src/acl.c @@ -89,6 +89,12 @@ int time_independent_strcmp(char *a, char *b) { * 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. @@ -100,6 +106,7 @@ user *ACLCreateUser(const char *name, size_t namelen) { u->flags = 0; u->allowed_subcommands = NULL; u->passwords = listCreate(); + listSetMatchMethod(u->passwords,ACLListMatchSds); u->patterns = NULL; /* Just created users cannot access to any key, however if the "~*" directive was enabled to match all the keys, the user will be flagged with the ALLKEYS @@ -142,11 +149,18 @@ user *ACLCreateUser(const char *name, size_t namelen) { * -@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) { +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")) { @@ -161,6 +175,16 @@ int ACLSetUser(user *u, const char *op) { { memset(u->allowed_commands,255,sizeof(u->allowed_commands)); u->flags |= USER_FLAG_ALLCOMMANDS; + } 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); + } 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 { return C_ERR; } @@ -171,8 +195,8 @@ int ACLSetUser(user *u, const char *op) { void ACLInit(void) { Users = raxNew(); DefaultUser = ACLCreateUser("default",7); - ACLSetUser(DefaultUser,"+@all"); - ACLSetUser(DefaultUser,"on"); + ACLSetUser(DefaultUser,"+@all",-1); + ACLSetUser(DefaultUser,"on",-1); } /* Check the username and password pair and return C_OK if they are valid, @@ -290,8 +314,9 @@ void aclCommand(client *c) { 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) != C_OK) { - addReplyErrorFormat(c,"Syntax error in ACL SETUSER modifier '%s'", + 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; } From b39409bcf8ee67cab100c5ffc6664c058a6b3333 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 13:16:31 +0100 Subject: [PATCH 102/122] ACL: nopass user setting. This is needed in order to model the current behavior of authenticating the connection directly when no password is set. Now with ACLs this will be obtained by setting the default user as "nopass" user. Moreover this flag can be used in order to create other users that do not require any password but will work with "AUTH username ". --- src/acl.c | 16 +++++++++++++++- src/server.h | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 95a4549f..3c7ccd28 100644 --- a/src/acl.c +++ b/src/acl.c @@ -141,9 +141,19 @@ user *ACLCreateUser(const char *name, size_t namelen) { * > Add this passowrd to the list of valid password for the user. * For example >mypass will add "mypass" to the list. * < 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. + * 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 @@ -175,6 +185,9 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { { 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 (op[0] == '>') { sds newpass = sdsnewlen(op+1,oplen-1); listNode *ln = listSearchKey(u->passwords,newpass); @@ -197,6 +210,7 @@ void ACLInit(void) { DefaultUser = ACLCreateUser("default",7); ACLSetUser(DefaultUser,"+@all",-1); ACLSetUser(DefaultUser,"on",-1); + ACLSetUser(DefaultUser,"nopass",-1); } /* Check the username and password pair and return C_OK if they are valid, diff --git a/src/server.h b/src/server.h index d8d45fcf..30a0c6c4 100644 --- a/src/server.h +++ b/src/server.h @@ -715,6 +715,12 @@ typedef struct readyList { #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 { uint64_t flags; /* See USER_FLAG_* */ From 52e99229874d92507cb768a665ed16185c4fbffa Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 13:45:16 +0100 Subject: [PATCH 103/122] ACL: AUTH command new form, using the ACL subsystem. --- src/server.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/server.c b/src/server.c index 3955c63c..0516219d 100644 --- a/src/server.c +++ b/src/server.c @@ -237,7 +237,7 @@ struct redisCommand redisCommandTable[] = { {"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}, + {"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}, @@ -2875,16 +2875,40 @@ int writeCommandsDeniedByDiskError(void) { } } +/* AUTH + * AUTH (Redis >= 6.0 form) + * + * 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 (ACLCheckUserCredentials(NULL,c->argv[1]) == C_OK) { + /* 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) { + 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 { - c->authenticated = 0; - addReplyError(c,"invalid password"); + 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 From 35fe59935ef809956f593cd4973387665d2d072f Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 17:57:49 +0100 Subject: [PATCH 104/122] ACL: automatically authenticate the nopass default user. --- src/networking.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/networking.c b/src/networking.c index 754f222d..4aa19314 100644 --- a/src/networking.c +++ b/src/networking.c @@ -125,7 +125,9 @@ client *createClient(int fd) { 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; From cca64672f418aa793ed36fcb6da8977ea11b240a Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 18:16:20 +0100 Subject: [PATCH 105/122] ACL: AUTH uses users. ACL WHOAMI implemented. --- src/acl.c | 55 +++++++++++++++++++++++++++++++++++++++------------- src/server.h | 1 + 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/acl.c b/src/acl.c index 3c7ccd28..3191ebc6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -103,6 +103,7 @@ int ACLListMatchSds(void *a, void *b) { 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(); @@ -119,8 +120,10 @@ user *ACLCreateUser(const char *name, size_t namelen) { /* Set user properties according to the string "op". The following * is a description of what different strings will do: * - * on Enable the user - * off Disable the user + * 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. * + Allow the execution of that command * - Disallow the execution of that command * +@ Allow the execution of all the commands in such category @@ -140,6 +143,7 @@ user *ACLCreateUser(const char *name, size_t namelen) { * It is possible to specify multiple patterns. * > 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). * < 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 @@ -193,6 +197,7 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { 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); @@ -220,20 +225,35 @@ void ACLInit(void) { * ENONENT: if the specified user does not exist at all. */ int ACLCheckUserCredentials(robj *username, robj *password) { - /* For now only the "default" user is allowed. When the RCP1 ACLs - * will be implemented multiple usernames will be supproted. */ - if (username != NULL && strcmp(username->ptr,"default")) { + user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr)); + if (u == NULL) { errno = ENOENT; return C_ERR; } - /* For now we just compare the password with the system wide one. */ - if (!time_independent_strcmp(password->ptr, server.requirepass)) { - return C_OK; - } else { + /* 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 @@ -314,11 +334,11 @@ int ACLCheckCommandPerm(client *c) { * ==========================================================================*/ /* ACL -- show and modify the configuration of ACL users. - * ACL help - * ACL list - * ACL setuser ... user attribs ... - * ACL deluser - * ACL getuser + * ACL HELP + * ACL LIST + * ACL SETUSER ... user attribs ... + * ACL DELUSER + * ACL GETUSER */ void aclCommand(client *c) { char *sub = c->argv[1]->ptr; @@ -336,12 +356,19 @@ void aclCommand(client *c) { } } 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,"help")) { const char *help[] = { "LIST -- List all the registered users.", "SETUSER [attribs ...] -- Create or modify a user.", "DELUSER -- Delete a user.", "GETUSER -- Get the user details.", +"WHOAMI -- Return the current username.", NULL }; addReplyHelp(c,help); diff --git a/src/server.h b/src/server.h index 30a0c6c4..95331c6d 100644 --- a/src/server.h +++ b/src/server.h @@ -722,6 +722,7 @@ typedef struct readyList { 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 From c79b01f4baf8948a22fdca96a73710466b4fcd41 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 18:26:44 +0100 Subject: [PATCH 106/122] ACL: the AUTH command can be always executed. --- src/acl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index 3191ebc6..38c4f89b 100644 --- a/src/acl.c +++ b/src/acl.c @@ -298,7 +298,9 @@ int ACLCheckCommandPerm(client *c) { if (c->cmd->id >= USER_MAX_COMMAND_BIT) return C_ERR; /* Check if the user can execute this command. */ - if (!(u->flags & USER_FLAG_ALLCOMMANDS)) { + 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 From ff92c069475bd6f0ebbaca009ad3168c9d359851 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 15 Jan 2019 18:28:43 +0100 Subject: [PATCH 107/122] ACL: fix command exec check by returning. --- src/server.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/server.c b/src/server.c index 0516219d..edf16917 100644 --- a/src/server.c +++ b/src/server.c @@ -2596,6 +2596,14 @@ int processCommand(client *c) { return C_OK; } + /* Check if the user can run this command according to the current + * ACLs. */ + if (ACLCheckCommandPerm(c) == C_ERR) { + flagTransaction(c); + addReplyErrorFormat(c,"-NOPERM this user has no permissions to run the %s command", c->cmd->name); + 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. @@ -2688,12 +2696,6 @@ int processCommand(client *c) { return C_OK; } - /* Check if the user can run this command according to the current - * ACLs. */ - if (ACLCheckCommandPerm(c) == C_ERR) { - addReplyErrorFormat(c,"-NOPERM this user has no permissions to run the %s command", c->cmd->name); - } - /* 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) && From 0db42d4ba8148a1e493f2da208d4bcf509716870 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 16 Jan 2019 13:29:04 +0100 Subject: [PATCH 108/122] ACL: implement the key match opcode in ACLSetUser(). --- src/acl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/acl.c b/src/acl.c index 38c4f89b..e03c2a1b 100644 --- a/src/acl.c +++ b/src/acl.c @@ -203,6 +203,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { 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; } From f78b3ede2735397704fd452619efcbdac37254f6 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 16 Jan 2019 13:39:04 +0100 Subject: [PATCH 109/122] ACL: key matching implemented. --- src/acl.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/acl.c b/src/acl.c index e03c2a1b..edc75de6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -330,6 +330,29 @@ int ACLCheckCommandPerm(client *c) { 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. */ + 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 C_ERR; + } + getKeysFreeResult(keyidx); } /* If we survived all the above checks, the user can execute the From dbae371090780b81ccecfc0d24aa46715b873f36 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 16 Jan 2019 13:50:00 +0100 Subject: [PATCH 110/122] ACL: create the user pattern list ASAP. --- src/acl.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/acl.c b/src/acl.c index edc75de6..c656b3f6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -107,11 +107,9 @@ user *ACLCreateUser(const char *name, size_t namelen) { u->flags = 0; u->allowed_subcommands = NULL; u->passwords = listCreate(); + u->patterns = listCreate(); listSetMatchMethod(u->passwords,ACLListMatchSds); - u->patterns = NULL; /* Just created users cannot access to any key, however - if the "~*" directive was enabled to match all the - keys, the user will be flagged with the ALLKEYS - flag. */ + listSetMatchMethod(u->patterns,ACLListMatchSds); memset(u->allowed_commands,0,sizeof(u->allowed_commands)); raxInsert(Users,(unsigned char*)name,namelen,u,NULL); return u; @@ -338,7 +336,7 @@ int ACLCheckCommandPerm(client *c) { listRewind(u->passwords,&li); /* Test this key against every pattern. */ - match = 0; + int match = 0; while((ln = listNext(&li))) { sds pattern = listNodeValue(ln); size_t plen = sdslen(pattern); From 4a3419acfcaa178237a09e769aa34163d1391d09 Mon Sep 17 00:00:00 2001 From: antirez Date: Wed, 16 Jan 2019 18:31:05 +0100 Subject: [PATCH 111/122] ACL: fix and improve ACL key checking. --- src/acl.c | 22 ++++++++++++++-------- src/server.c | 12 ++++++++++-- src/server.h | 4 ++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/acl.c b/src/acl.c index c656b3f6..851439aa 100644 --- a/src/acl.c +++ b/src/acl.c @@ -218,6 +218,7 @@ void ACLInit(void) { Users = raxNew(); DefaultUser = ACLCreateUser("default",7); ACLSetUser(DefaultUser,"+@all",-1); + ACLSetUser(DefaultUser,"~*",-1); ACLSetUser(DefaultUser,"on",-1); ACLSetUser(DefaultUser,"nopass",-1); } @@ -288,18 +289,21 @@ user *ACLGetUserByName(const char *name, size_t namelen) { * 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 C_OK is returned, otherwise - * C_ERR is returned. */ + * 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 C_OK; + 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 C_ERR; + 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) && @@ -311,10 +315,12 @@ int ACLCheckCommandPerm(client *c) { * 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 C_ERR; + 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 C_ERR; + 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. */ @@ -348,14 +354,14 @@ int ACLCheckCommandPerm(client *c) { break; } } - if (!match) return C_ERR; + if (!match) return ACL_DENIED_KEY; } getKeysFreeResult(keyidx); } /* If we survived all the above checks, the user can execute the * command. */ - return C_OK; + return ACL_OK; } /* ============================================================================= diff --git a/src/server.c b/src/server.c index edf16917..a4cd5620 100644 --- a/src/server.c +++ b/src/server.c @@ -2598,9 +2598,17 @@ int processCommand(client *c) { /* Check if the user can run this command according to the current * ACLs. */ - if (ACLCheckCommandPerm(c) == C_ERR) { + int acl_retval = ACLCheckCommandPerm(c); + if (acl_retval != ACL_OK) { flagTransaction(c); - addReplyErrorFormat(c,"-NOPERM this user has no permissions to run the %s command", c->cmd->name); + 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; } diff --git a/src/server.h b/src/server.h index 95331c6d..58a4501b 100644 --- a/src/server.h +++ b/src/server.h @@ -1698,6 +1698,10 @@ 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); From 7b65605ab27e00cfdd58db048da398e27d783038 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 17 Jan 2019 18:05:43 +0100 Subject: [PATCH 112/122] ACL: reimplement requirepass option in term of ACLs. --- src/config.c | 49 ++++++++++++++++++++++++++++++++++++++++++++----- src/server.h | 1 + 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/config.c b/src/config.c index 7e6d9233..7354a4f0 100644 --- a/src/config.c +++ b/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; @@ -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,6 +1579,16 @@ void configGetCommand(client *c) { sdsfree(aux); matches++; } + if (stringmatch(pattern,"requirepass",1)) { + addReplyBulkCString(c,"requirepass"); + if (listLength(DefaultUser->passwords)) { + listNode *first = listFirst(DefaultUser->passwords); + sds password = listNodeValue(first); + addReplyBulkCBuffer(c,password,sdslen(password)); + } else { + addReplyBulkCString(c,""); + } + } setDeferredMapLen(c,replylen,matches); } @@ -1981,6 +1999,27 @@ 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; + + /* If there is no password set, we don't want the requirepass option + * to be present in the configuration at all. */ + if (listLength(DefaultUser->passwords) == 0) { + rewriteConfigMarkAsProcessed(state,option); + return; + } + + line = sdsnew(option); + line = sdscatlen(line, " ", 1); + listNode *first = listFirst(DefaultUser->passwords); + sds password = listNodeValue(first); + 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 +2200,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); diff --git a/src/server.h b/src/server.h index 58a4501b..3021633b 100644 --- a/src/server.h +++ b/src/server.h @@ -1706,6 +1706,7 @@ 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); /* Sorted sets data type */ From 0526d1538beaa36567e595becf8f890e92d109aa Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 17 Jan 2019 18:19:04 +0100 Subject: [PATCH 113/122] ACL: partial implementation of ACL GETUSER. --- src/acl.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/acl.c b/src/acl.c index 851439aa..aeda9f2f 100644 --- a/src/acl.c +++ b/src/acl.c @@ -397,6 +397,45 @@ void aclCommand(client *c) { } 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.", From 636424c0ce6d162e13c4b00b01ad43bba06c580b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 17 Jan 2019 18:22:22 +0100 Subject: [PATCH 114/122] ACL: change requirepass stop condition to use ACLs. --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index a4cd5620..541d4107 100644 --- a/src/server.c +++ b/src/server.c @@ -2587,7 +2587,7 @@ int processCommand(client *c) { } /* Check if the user is authenticated */ - if (server.requirepass && + if (!(DefaultUser->flags & USER_FLAG_NOPASS) && !c->authenticated && (c->cmd->proc != authCommand || c->cmd->proc == helloCommand)) { From b87815c1f800690c90bbc6c50bbe62878841d0b9 Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 17 Jan 2019 18:30:23 +0100 Subject: [PATCH 115/122] ACL: AUTH + no default user password raises an error. This way the behavior is very similar to the past one. This is useful in order to remember the user she probably failed to configure a password correctly. --- src/server.c | 17 +++++++++++++---- tests/unit/auth.tcl | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/server.c b/src/server.c index 541d4107..37757b21 100644 --- a/src/server.c +++ b/src/server.c @@ -2901,6 +2901,15 @@ void authCommand(client *c) { * 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 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 { @@ -2909,11 +2918,11 @@ void authCommand(client *c) { } if (ACLCheckUserCredentials(username,password) == C_OK) { - c->authenticated = 1; - c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr)); - addReply(c,shared.ok); + c->authenticated = 1; + c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr)); + addReply(c,shared.ok); } else { - addReplyError(c,"-WRONGPASS invalid username-password pair"); + addReplyError(c,"-WRONGPASS invalid username-password pair"); } /* Free the "default" string object we created for the two diff --git a/tests/unit/auth.tcl b/tests/unit/auth.tcl index 633cda95..9080d4bf 100644 --- a/tests/unit/auth.tcl +++ b/tests/unit/auth.tcl @@ -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 From 2c66c525f93539dab5e4fb71327fda2a9672cf3b Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 17 Jan 2019 18:33:36 +0100 Subject: [PATCH 116/122] ACL: configure the master connection without user. --- src/replication.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/replication.c b/src/replication.c index 1ca8641a..ab880a6b 100644 --- a/src/replication.c +++ b/src/replication.c @@ -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 From ac6e49de4884eb1c2508179b903b0dfadde0f0b9 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 18 Jan 2019 11:26:29 +0100 Subject: [PATCH 117/122] ACL: implement resetpass directive and adjust test. --- src/acl.c | 3 +++ tests/unit/dump.tcl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/acl.c b/src/acl.c index aeda9f2f..fc1edd87 100644 --- a/src/acl.c +++ b/src/acl.c @@ -190,6 +190,9 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { } 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); diff --git a/tests/unit/dump.tcl b/tests/unit/dump.tcl index 09768b80..062d803b 100644 --- a/tests/unit/dump.tcl +++ b/tests/unit/dump.tcl @@ -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 } } } From 7de6e30241127b60d4bf716a3399473320afe571 Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 18 Jan 2019 11:30:40 +0100 Subject: [PATCH 118/122] ACL: fix config get requirepass. --- src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.c b/src/config.c index 7354a4f0..de3cd012 100644 --- a/src/config.c +++ b/src/config.c @@ -1588,6 +1588,7 @@ void configGetCommand(client *c) { } else { addReplyBulkCString(c,""); } + matches++; } setDeferredMapLen(c,replylen,matches); } From c8391388c221b9255a7b6536c3f43438f36b8e2b Mon Sep 17 00:00:00 2001 From: antirez Date: Fri, 18 Jan 2019 11:49:30 +0100 Subject: [PATCH 119/122] ACL: remove server.requirepass + some refactoring. --- src/acl.c | 10 ++++++++++ src/config.c | 10 ++++------ src/networking.c | 2 +- src/sentinel.c | 2 +- src/server.c | 1 - src/server.h | 4 ++-- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/acl.c b/src/acl.c index fc1edd87..a2cb8784 100644 --- a/src/acl.c +++ b/src/acl.c @@ -216,6 +216,16 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { 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(); diff --git a/src/config.c b/src/config.c index de3cd012..ed3b6fec 100644 --- a/src/config.c +++ b/src/config.c @@ -1581,9 +1581,8 @@ void configGetCommand(client *c) { } if (stringmatch(pattern,"requirepass",1)) { addReplyBulkCString(c,"requirepass"); - if (listLength(DefaultUser->passwords)) { - listNode *first = listFirst(DefaultUser->passwords); - sds password = listNodeValue(first); + sds password = ACLDefaultUserFirstPassword(); + if (password) { addReplyBulkCBuffer(c,password,sdslen(password)); } else { addReplyBulkCString(c,""); @@ -2004,18 +2003,17 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) { 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 (listLength(DefaultUser->passwords) == 0) { + if (password == NULL) { rewriteConfigMarkAsProcessed(state,option); return; } line = sdsnew(option); line = sdscatlen(line, " ", 1); - listNode *first = listFirst(DefaultUser->passwords); - sds password = listNodeValue(first); line = sdscatsds(line, password); rewriteConfigRewriteLine(state,option,line,force); diff --git a/src/networking.c b/src/networking.c index 4aa19314..9e62cb6b 100644 --- a/src/networking.c +++ b/src/networking.c @@ -810,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) { diff --git a/src/sentinel.c b/src/sentinel.c index 1696b121..4d03c9c1 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -1961,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) { diff --git a/src/server.c b/src/server.c index 37757b21..41d6f454 100644 --- a/src/server.c +++ b/src/server.c @@ -1596,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; diff --git a/src/server.h b/src/server.h index 3021633b..956494aa 100644 --- a/src/server.h +++ b/src/server.h @@ -778,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. */ @@ -988,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 */ @@ -1707,6 +1706,7 @@ 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 */ From 3f0c2b1fa28870b15259b4d4fac92732e4c36976 Mon Sep 17 00:00:00 2001 From: WuYunlong Date: Mon, 21 Jan 2019 17:27:36 +0800 Subject: [PATCH 120/122] Update dict resize policy when aof rewrite process gets killed. --- src/aof.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aof.c b/src/aof.c index 9723fc33..500e0c93 100644 --- a/src/aof.c +++ b/src/aof.c @@ -221,6 +221,8 @@ static void killAppendOnlyChild(void) { server.aof_rewrite_time_start = -1; /* Close pipes used for IPC between the two processes. */ aofClosePipes(); + + updateDictResizePolicy(); } /* Called when the user switches from "appendonly yes" to "appendonly no" From f004a3e7ff01dfb8952fc424a68f780486c846ba Mon Sep 17 00:00:00 2001 From: WuYunlong Date: Mon, 21 Jan 2019 17:33:18 +0800 Subject: [PATCH 121/122] Update dict resize policy when rdb child process gets killed. --- src/db.c | 1 + src/replication.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/db.c b/src/db.c index 62c8aa13..0ed0cdd1 100644 --- a/src/db.c +++ b/src/db.c @@ -451,6 +451,7 @@ void flushallCommand(client *c) { if (server.rdb_child_pid != -1) { kill(server.rdb_child_pid,SIGUSR1); rdbRemoveTempFile(server.rdb_child_pid); + updateDictResizePolicy(); } if (server.saveparamslen > 0) { /* Normally rdbSave() will reset dirty, but we don't want this here diff --git a/src/replication.c b/src/replication.c index a3110661..7037ae24 100644 --- a/src/replication.c +++ b/src/replication.c @@ -1255,6 +1255,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { (long) server.rdb_child_pid); kill(server.rdb_child_pid,SIGUSR1); rdbRemoveTempFile(server.rdb_child_pid); + updateDictResizePolicy(); } if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) { From cfdc800a5ff5a2bb02ccd1e21c1c36e6cb5a474d Mon Sep 17 00:00:00 2001 From: antirez Date: Mon, 21 Jan 2019 11:15:43 +0100 Subject: [PATCH 122/122] Remove non semantical newline space from PR #5797. --- src/aof.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aof.c b/src/aof.c index 1bb4b7d3..4f7e9392 100644 --- a/src/aof.c +++ b/src/aof.c @@ -221,7 +221,6 @@ static void killAppendOnlyChild(void) { server.aof_rewrite_time_start = -1; /* Close pipes used for IPC between the two processes. */ aofClosePipes(); - updateDictResizePolicy(); }