From 0ba9e0f8dd3f5366c10683f699130a95b57788d9 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 b61dfac18..0e50c20a9 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 4ec37c3bd3e8db7fd897dd181d21b86d31250fb3 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 a93bd9b10..b0a12ebb9 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 1055c6b48423333d153976492e2d26f5753a8186 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 b0a12ebb9..705c7483f 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 853b97fd857f9463095127f37d59f945ab79931b 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 705c7483f..6fe93e660 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 39f4a23296b83b127aac54ecc7188ff3456a1b0f 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 6fe93e660..bfd245b4d 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 1f66431b4a66b886d6c289f7e38da5728ac2b36a 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 4b1a98326..2b4eeb19b 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 367ee1627..685dbb40d 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 fa4e6ba00db3c81520724c46521004b8e6098ca6 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 1ea671569..53629ef77 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 da3d2db500d33e89a5db26c62b8da92dd18aa398 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 74b857e04..51feefad4 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 1c82be419fec1ff81b3c41d914ccab83684dd2e3 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 51feefad4..37e947064 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 da4c6d45a..1ebccf469 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 33374b02bfdf21ead32737dc26a66d3cf2945905 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 9371b2bac..b686e2fca 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 1ebccf469..3ed559d60 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 7c9f682686fd56fd54ff313ea83b22800257026c 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 a3110661e..9508528d1 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 54ccb4df9ee5790eb4ad8490644655249ab3c24b 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 db6f7aa61..2dfb327f0 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 f10783e9fb1ead8d9380c6f0fcb8394ccfb7a321 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 84a61ca43..9246c37cd 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 c3d7abb00444456fe3d299515c31cf1272901038 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 9f51bba85..7e6d92336 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 f5a4f6bdf4b0d81928642fcf2c370dd6ba349b32 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 62c8aa131..49306311c 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 7fe7b38743df4843fe11c30032dd2578c1f57060 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 fa3a893a6..fe0e71fba 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 6fbf149112412170e554d8c794d6b10637cc128d 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 37e947064..f1ccdaf17 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 3ed559d60..603bbd2e3 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 76fa77f9d5f741ff701a35056eb067ee8134eae0 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 f1ccdaf17..533df2f22 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 603bbd2e3..4b8655006 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 342f3363987a3f11445dee8d7bbea037bc322f57 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 533df2f22..6f077be7a 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 4b8655006..272db6621 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 9ed8763002156acbfc2484e22177bc2a03e298ef 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 48ffa42b9..041af8b01 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 3aebf8ab293719f708466af901d37ec264ab390f 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 9246c37cd..f1b14018f 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 02c2309efc1a560cfa8b23f595c059603d1656f5 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 b253efe1f..fb25405fe 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 859eb46a3..c407cadf2 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 4b300d868..322f02726 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 c8f4703d5..dd4ec9537 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 0279914a416f894bdda81573e2746f98a24822fb 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 f67073fe6..e78957d9b 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 bc7b1c7b51b10c06d3937bf188ae96be6295421a 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 d7f7d4ae8..84a7bb86a 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 8e183fca4..1d715e39b 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 b715bb51248740134e6c1e01b32afd1f03cfdd7f 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 260b3679c..7aedc9deb 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 e02ab3c51e241220f312361d6681458c88b3fcd1 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 7aedc9deb..9b8d5fd30 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 ee2564e90e61e96f9d8b90200aa9ab633d06c78f 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 b686e2fca..e2534a4d1 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 272db6621..2bb7e3650 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 a039a1bc3cd7da44efecac40ec559454d647fcbc 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 e2534a4d1..7d84d39f8 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 2bb7e3650..456d11caa 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 dd4ec9537..60c8e9ab0 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 8413c6f1070d48c08083fefb7e3186ba0d259805 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 6f077be7a..bb51619f4 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 7d84d39f8..5b6a9c4a8 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 456d11caa..a483e1a61 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 7872d57d275b9943d005de20c1a1aadbfd2cd01c 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 fb25405fe..1a3a348b5 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 49306311c..74965f660 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 84a7bb86a..b84b75f74 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 bb51619f4..72f44f071 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 041af8b01..ec0bd02ee 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 c407cadf2..335c52d9b 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 9b8d5fd30..f6df38400 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 5b6a9c4a8..a736a1e59 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 a483e1a61..2958c635d 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 322f02726..8608cd8b3 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 fe0e71fba..a50da5796 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 60c8e9ab0..d6daad691 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 e78957d9b..61013dbcd 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 2dfb327f0..5800c5c0c 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 f1b14018f..1c943caea 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 20c202c219e8db3b37f6654bac5e748bbe1f8ccc 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 23f2266a7..8d03a7699 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 7ff88b2c7a2a9f383637636f30fb507af65e33d2 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 adff9d4fa..a94159f68 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 a97bf0c5496d879d8bdde7a0bb47aeca245556d4 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 2b43f2b75..1f22a20a6 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 0972c55994811b74a2dbb99dbc8369bd38259f40 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 c78fadfcf..d14f537bc 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 0dbdaab2a173d76466228bf4c998c005146cbf62 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 72f44f071..8be226292 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 2958c635d..d4558df5a 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 d266105a456b349b713ba7f9810573c4f7836983 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 a736a1e59..79df0282e 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 d4558df5a..c12dca596 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 92c9429d172b5562f89f54ed7057808336d13f6a 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 1f22a20a6..b8cf02c82 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 d14f537bc..91a0421f5 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 b84b75f74..71090d8ed 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 a94159f68..536edcdd5 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 c12dca596..0cc14f08c 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 d6daad691..451ffb4b5 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 1c943caea..2efdcd260 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 da155176f1d5b34e611225cb3078f21883e1ea67 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 ba3a3ab60..fc21ea006 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 38d02d0287f2bad82375a115143f0ab3e6cbcaaa 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 97e6a702e..33aa1245b 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 1916a890f70ea2daba1054fab2bf2f2ab7bd85a3 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 20d159d33..c79f1133d 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 cbb6c8f9789a8d7e0ea18b1b3452f79f038b93a9 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 b8cf02c82..f9e196626 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 f51f6c46b..1a5acac42 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 f7d9fd00595603b8454f9b1df345a87a45d7b450 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 2efdcd260..0427ee887 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 bb98fa90bb2ed285c1752b687074ae852a47edb2 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 a50da5796..70a7b2f47 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 d1c79701da3383b65dcc6786db33e2fff0fbde69 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 70a7b2f47..d8aee6572 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 75a6d12dd0b1a471b8b05e635f5797b1cc69a4a3 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 c79f1133d..ac935d5c2 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 8be226292..5e442e289 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 536edcdd5..1696b1217 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 79df0282e..8cdbdad76 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 0cc14f08c..6a398dc3e 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 e5241b6e58dd1b5a0643677e0885554ec64be7a0 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 5e442e289..789ea822d 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 6a398dc3e..0362c03a2 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 5fba9d160da7ca995d141b62b151b1d289b0ed30 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 f92bcb3c9..a7fe3ac11 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 9a4de8360..06ca99468 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 273d8b2dd..7d2bef180 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 ff08c25e1..39ef7cf5e 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 06bbef117..819efbd58 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 d955203f8..0cecd30d9 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 59cbf469b..740555c24 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 9a56643df..3227faafd 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 18bdfc99c..bfbf483e1 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 423d5e504..1b0d5e659 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 7d4120985..a4b3abc6d 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 2f1a0bf85..a11594e68 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 50333b534..084d0b078 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 2988aa453..84ee15cb6 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 923ffd82f..44777b10c 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 a23d60676..79cff4308 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 27b7e55379d0e34b18de212d003efa2b15cd2408 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 bfbf483e1..0de477c3e 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 084d0b078..221438883 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 99abef1e5db7a976e8671e0d325bc75a734dc97c 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 0de477c3e..34a987b31 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 1347b4eeef903abb4adf9893f3bec743d1505d3f 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 34a987b31..42d0f8dd1 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 86d2b8a65facec2c44f4f8527c8f07d5166151ba 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 bfd245b4d..bfecf1b79 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 d4b34743e194195e4a46e4e7bf86f42c501af815 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 ad08076d8..faf2ce684 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 42d0f8dd1..13b961871 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 1b0d5e659..40719fe2d 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 221438883..d6a479741 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 84ee15cb6..e7c4bd308 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 bfecf1b79..15042b979 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 d09a0f47d1b1259d8208b7c0c0b7625e45ce1119 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 d6a479741..2726d4481 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 59448be0979d44c4fe583e940c00b0dcb1202899 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 2726d4481..32f1c9220 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 380859ed3c59141c9a37b89741d3ad4036ad6a7e 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 13b961871..193168a0b 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 40719fe2d..47d7982e9 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 32f1c9220..511503cb5 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 e7c4bd308..db267f81f 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 092bb045feb610bb1100ada48b4c513dba31a17a 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 15042b979..b8934eda1 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 229ca5b2106e36463a10bb680d9bd1b813100b42 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 1ec7c4977..2391f7377 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 592508470d274eea9d53091d7de23ab32ac060bf 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 2391f7377..c9435e720 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 9089aadc9dcc080c797204c7206093cf39e9ec76 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 c9435e720..d43dcc97a 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 fd81b96935109ee10b4f509c2d114e5a0ea7a6c3 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 d43dcc97a..9029871ea 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 789ea822d..75312a1f2 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 a73887b0cb80503ce4d9c5d62907494f79b10779 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 9029871ea..d1824dfd7 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 e936aaf5fc4e477efd472b060ecb2b1433834bb4 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 d1824dfd7..6cc734c32 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 75312a1f2..6b13db8e3 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 0362c03a2..8c1e6a775 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 7894b02f3cf50f6d9d6b2b268d6f98df27d44047 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 193168a0b..0947d1ed7 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 511503cb5..c75c3435f 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 db267f81f..f3d075843 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 7db26f6aad53084c1b995c79fb6231d9e0a82e67 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 b8934eda1..a74493ef0 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 5190cbe08f3553d3c504c3b0575b381b411a56fa 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 6cc734c32..a98bc61ad 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 ac935d5c2..8954fcdf0 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 6b13db8e3..0a32f3113 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 9508528d1..1ca8641a9 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 8c1e6a775..ed6f1a7d0 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 67a6fe1dc7e72b5b458d0ca0c0ce87aa1d6dddb3 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 335c52d9b..12b252cfb 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 c4bf8bf6e8086ae120ee5a21d29e3a0d37c523ee 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 12b252cfb..0bf615eb1 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 a57ea371bdd19d4eeff93b6bfe3b5d16bcc4fafa 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 0bf615eb1..994dd9734 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 736d38cf88aecf09ad4efb4876c6ba331689f3b7 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 8cdbdad76..c5b667886 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 26007b980ac8b57e75a803065b1390e5fde9eff9 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 c5b667886..d85e10d95 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 79c3d1355d418cb358bff9b8ebd010ada73111ec 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 d85e10d95..6c7692ed7 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 106977aaf92d94dfca865795c59ef332948aaf3d 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 9edbb4581..adf32d557 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 000000000..5155f9471 --- /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 6c7692ed7..7a8134047 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 ed6f1a7d0..909e6c3da 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 cb7b4154cdc61c1d284f17d7fcf4d0b3a4861396 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 0a32f3113..5657a7643 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 50927b298eb2b538e68994be1e9951d32dc5889a 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 7a8134047..f19a240c2 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 a571c99587b004ea039a6814bc8d413f355e63c3 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 f19a240c2..5c15d223b 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 909e6c3da..a004acb25 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 647a157eac75f4b6b53a8fe91dd68a9134606ae5 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 5c15d223b..df9488873 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 cc5222bd7afb2946d896af42185c2ad55a98cd74 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 5155f9471..dd21e2f03 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 df9488873..c297e9122 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 a004acb25..1eb66e013 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 3a02107a18ec46e9a1063d473ebe38a750195e2a 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 dd21e2f03..8ce84c6da 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 9633476699305dc2cbe80c450c59f339b313fc6f 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 1eb66e013..0ad37862f 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 6771d472300ba556560163d90f28f798f8e51531 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 8ce84c6da..b624ff3bf 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 5657a7643..07851a36c 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 0ad37862f..22224f292 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 da1c642f831a6df15b63a2e843431f0879b6a488 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 b624ff3bf..2202f293c 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 e75a2b8500ea387c930a9eebf367c36d24b48213 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 2202f293c..269e4b40d 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 c297e9122..48b4cdb47 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 22224f292..e120e9894 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 f3ad8fc85e0db9e779217e18a28bfc09156ef717 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 269e4b40d..7550b22b5 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 487b8fcbaa3e59407b6c9117c8613fd6bf7b59e9 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 7550b22b5..2dafcbea9 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 e120e9894..c09019390 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 3c538d0595315d23b1712521d3f263f1c194bdbb 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 2dafcbea9..75ffe0446 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 e1ff6ef8e7388065382a072b39a4f3ac275a07f0 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 48b4cdb47..17d479744 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 efa8afedd7314f70d38a169e745296a3a5b5633d 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 75ffe0446..ea708db76 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 4890c2f5e9ac37bb2584a47b5089b5d46090239c 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 17d479744..a63b7522c 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 b2636ecdf51ff35e752807af68d5e88267e78e5f 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 07851a36c..754f222dd 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 c09019390..a15e6cd94 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 7822181e31969e35176c89f2397a4869f3de2b32 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 ea708db76..59540d2a1 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 31994ff3659e8db907867abdd6b69d2477e48b04 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 59540d2a1..fdc0744a5 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 79e57bb854f08b94a224ad6518167de8a5cbd9f4 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 fdc0744a5..30e195642 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 f4b216d32bef57b13a46ab16f12abccaf70b8a41 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 30e195642..2da7f50e4 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 b9e5d4f575ea0523db3092367afbccbc3ee840be 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 a63b7522c..49f62f37b 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 b497dbb48123849a093416364658dea2ed3aae87 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 a15e6cd94..973d3631c 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 53053f5f30f4b51efe62ddcd969dd92a5a8af222 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 49f62f37b..4027b9baf 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 973d3631c..e22042319 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 45346c6766095118d493cc401f47468dd324e547 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 2da7f50e4..eb5519586 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 fc85321233d967c00ea6d0e2df59593d0d416a96 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 9723fc333..7d76c6f32 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 532049a9bc52256e136b7768fe4317e1eb238828 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 eb5519586..742860e63 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 e22042319..70cb00401 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 4d8991fdf480452229210a7bd03cc9ba9bf28b39 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 742860e63..2c92cbfbe 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 4027b9baf..3955c63cd 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 70cb00401..d8d45fcf8 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 115adf0049b879c5f63b0ffa99ee820f3a53f705 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 2c92cbfbe..95a4549f1 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 1cb8998a1c86b50bf85a03cdd1110313fd6d04e8 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 95a4549f1..3c7ccd281 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 d8d45fcf8..30a0c6c47 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 57fa4a2f01e5b30809815999039b4bb6b57dc880 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 3955c63cd..0516219d1 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 cd421f85b8cf54fa3b052bdc922451ab0d2892e5 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 754f222dd..4aa193142 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 8cb696b95363b9ad30a58dd0efe07dacfa501868 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 3c7ccd281..3191ebc6b 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 30a0c6c47..95331c6d3 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 a2a8121ad891e66449b8d5aaf47bfc6743b3f179 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 3191ebc6b..38c4f89b3 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 2906e9c196733e7d7b462138172ca63206843b81 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 0516219d1..edf16917d 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 21808e3720b19de8eca9899f5cde74f459bd783d 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 38c4f89b3..e03c2a1b6 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 eb6cae0b724f82ee4aad08899888598ab24c45da 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 e03c2a1b6..edc75de6d 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 58848500b1fa977652c6ac4a5b2786f5d787634b 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 edc75de6d..c656b3f62 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 dabd1e3bbbfb976b33444de196a4977959f61715 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 c656b3f62..851439aa5 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 edf16917d..a4cd56208 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 95331c6d3..58a4501bf 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 03c4aa01ec719a9e98572cd728f1bc23d6d49bc6 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 7e6d92336..7354a4f0c 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 58a4501bf..3021633bd 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 b77577114b2fe9df9c9ec2e0cd2c6713c6d2f6fa 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 851439aa5..aeda9f2f0 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 a7ea1e7a2f597ac506a97836e198ea21121f720e 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 a4cd56208..541d41075 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 7e73a1fbf354cb345b1f5b4c3c036103643308b2 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 541d41075..37757b211 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 633cda95c..9080d4bf7 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 26be458e05409c2025ae02f8ba3fe1f0ee3d5f2c 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 1ca8641a9..ab880a6b6 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 f1e57a86f95b04e028fee470d5b264c8ca3ad28f 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 aeda9f2f0..fc1edd875 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 09768b80e..062d803b5 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 b3087f5d6a668610600f1ecc5720186b98d8126d 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 7354a4f0c..de3cd012e 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 4261b70f8de79d6dab7dc21ac0fd1b5a65472d71 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 fc1edd875..a2cb87848 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 de3cd012e..ed3b6fecd 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 4aa193142..9e62cb6be 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 1696b1217..4d03c9c12 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 37757b211..41d6f454c 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 3021633bd..956494aa3 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 38e83e61fcdeeb1398030ac5b8871b7608ff530d 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 9723fc333..500e0c936 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 9f3ebe6d5988f75a097f2145a3b96901f8665409 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 62c8aa131..0ed0cdd19 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 a3110661e..7037ae243 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 b3210c5a1109e4f77d10b8e4c7a9fd6f79370bb1 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 1bb4b7d3d..4f7e9392f 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(); }