Unverified Commit 49816941 authored by chendianqiang's avatar chendianqiang Committed by GitHub
Browse files

Merge pull request #2 from antirez/unstable

merge from redis
parents 68ceb466 f311a529
...@@ -11,4 +11,4 @@ then ...@@ -11,4 +11,4 @@ then
echo "You need tcl 8.5 or newer in order to run the Redis test" echo "You need tcl 8.5 or newer in order to run the Redis test"
exit 1 exit 1
fi fi
$TCLSH tests/test_helper.tcl $* $TCLSH tests/test_helper.tcl "${@}"
...@@ -20,6 +20,21 @@ ...@@ -20,6 +20,21 @@
# The port that this sentinel instance will run on # The port that this sentinel instance will run on
port 26379 port 26379
# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
# daemonized.
daemonize no
# When running daemonized, Redis Sentinel writes a pid file in
# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
# location here.
pidfile /var/run/redis-sentinel.pid
# Specify the log file name. Also the empty string can be used to force
# Sentinel to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""
# sentinel announce-ip <ip> # sentinel announce-ip <ip>
# sentinel announce-port <port> # sentinel announce-port <port>
# #
...@@ -58,11 +73,11 @@ dir /tmp ...@@ -58,11 +73,11 @@ dir /tmp
# be elected by the majority of the known Sentinels in order to # be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority. # start a failover, so no failover can be performed in minority.
# #
# Slaves are auto-discovered, so you don't need to specify slaves in # Replicas are auto-discovered, so you don't need to specify replicas in
# any way. Sentinel itself will rewrite this configuration file adding # any way. Sentinel itself will rewrite this configuration file adding
# the slaves using additional configuration options. # the replicas using additional configuration options.
# Also note that the configuration file is rewritten when a # Also note that the configuration file is rewritten when a
# slave is promoted to master. # replica is promoted to master.
# #
# Note: master name should not include special characters or spaces. # Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_". # The valid charset is A-z 0-9 and the three characters ".-_".
...@@ -70,11 +85,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2 ...@@ -70,11 +85,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass <master-name> <password> # sentinel auth-pass <master-name> <password>
# #
# Set the password to use to authenticate with the master and slaves. # Set the password to use to authenticate with the master and replicas.
# Useful if there is a password set in the Redis instances to monitor. # Useful if there is a password set in the Redis instances to monitor.
# #
# Note that the master password is also used for slaves, so it is not # Note that the master password is also used for replicas, so it is not
# possible to set a different password in masters and slaves instances # possible to set a different password in masters and replicas instances
# if you want to be able to monitor these instances with Sentinel. # if you want to be able to monitor these instances with Sentinel.
# #
# However you can have Redis instances without the authentication enabled # However you can have Redis instances without the authentication enabled
...@@ -89,7 +104,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2 ...@@ -89,7 +104,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel down-after-milliseconds <master-name> <milliseconds> # sentinel down-after-milliseconds <master-name> <milliseconds>
# #
# Number of milliseconds the master (or any attached slave or sentinel) should # Number of milliseconds the master (or any attached replica or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the # be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively # specified period) in order to consider it in S_DOWN state (Subjectively
# Down). # Down).
...@@ -97,11 +112,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2 ...@@ -97,11 +112,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# Default is 30 seconds. # Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000 sentinel down-after-milliseconds mymaster 30000
# sentinel parallel-syncs <master-name> <numslaves> # sentinel parallel-syncs <master-name> <numreplicas>
# #
# How many slaves we can reconfigure to point to the new slave simultaneously # How many replicas we can reconfigure to point to the new replica simultaneously
# during the failover. Use a low number if you use the slaves to serve query # during the failover. Use a low number if you use the replicas to serve query
# to avoid that all the slaves will be unreachable at about the same # to avoid that all the replicas will be unreachable at about the same
# time while performing the synchronization with the master. # time while performing the synchronization with the master.
sentinel parallel-syncs mymaster 1 sentinel parallel-syncs mymaster 1
...@@ -113,18 +128,18 @@ sentinel parallel-syncs mymaster 1 ...@@ -113,18 +128,18 @@ sentinel parallel-syncs mymaster 1
# already tried against the same master by a given Sentinel, is two # already tried against the same master by a given Sentinel, is two
# times the failover timeout. # times the failover timeout.
# #
# - The time needed for a slave replicating to a wrong master according # - The time needed for a replica replicating to a wrong master according
# to a Sentinel current configuration, to be forced to replicate # to a Sentinel current configuration, to be forced to replicate
# with the right master, is exactly the failover timeout (counting since # with the right master, is exactly the failover timeout (counting since
# the moment a Sentinel detected the misconfiguration). # the moment a Sentinel detected the misconfiguration).
# #
# - The time needed to cancel a failover that is already in progress but # - The time needed to cancel a failover that is already in progress but
# did not produced any configuration change (SLAVEOF NO ONE yet not # did not produced any configuration change (SLAVEOF NO ONE yet not
# acknowledged by the promoted slave). # acknowledged by the promoted replica).
# #
# - The maximum time a failover in progress waits for all the slaves to be # - The maximum time a failover in progress waits for all the replicas to be
# reconfigured as slaves of the new master. However even after this time # reconfigured as replicas of the new master. However even after this time
# the slaves will be reconfigured by the Sentinels anyway, but not with # the replicas will be reconfigured by the Sentinels anyway, but not with
# the exact parallel-syncs progression as specified. # the exact parallel-syncs progression as specified.
# #
# Default is 3 minutes. # Default is 3 minutes.
...@@ -185,7 +200,7 @@ sentinel failover-timeout mymaster 180000 ...@@ -185,7 +200,7 @@ sentinel failover-timeout mymaster 180000
# <role> is either "leader" or "observer" # <role> is either "leader" or "observer"
# #
# The arguments from-ip, from-port, to-ip, to-port are used to communicate # The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected slave # the old address of the master and the new address of the elected replica
# (now a master). # (now a master).
# #
# This script should be resistant to multiple invocations. # This script should be resistant to multiple invocations.
...@@ -213,12 +228,17 @@ sentinel deny-scripts-reconfig yes ...@@ -213,12 +228,17 @@ sentinel deny-scripts-reconfig yes
# #
# In such case it is possible to tell Sentinel to use different command names # In such case it is possible to tell Sentinel to use different command names
# instead of the normal ones. For example if the master "mymaster", and the # instead of the normal ones. For example if the master "mymaster", and the
# associated slaves, have "CONFIG" all renamed to "GUESSME", I could use: # associated replicas, have "CONFIG" all renamed to "GUESSME", I could use:
# #
# sentinel rename-command mymaster CONFIG GUESSME # SENTINEL rename-command mymaster CONFIG GUESSME
# #
# After such configuration is set, every time Sentinel would use CONFIG it will # After such configuration is set, every time Sentinel would use CONFIG it will
# use GUESSME instead. Note that there is no actual need to respect the command # use GUESSME instead. Note that there is no actual need to respect the command
# case, so writing "config guessme" is the same in the example above. # case, so writing "config guessme" is the same in the example above.
# #
# SENTINEL SET can also be used in order to perform this configuration at runtime. # SENTINEL SET can also be used in order to perform this configuration at runtime.
#
# In order to set a command back to its original name (undo the renaming), it
# is possible to just rename a command to itsef:
#
# SENTINEL rename-command mymaster CONFIG CONFIG
...@@ -21,6 +21,11 @@ NODEPS:=clean distclean ...@@ -21,6 +21,11 @@ NODEPS:=clean distclean
# Default settings # Default settings
STD=-std=c99 -pedantic -DREDIS_STATIC='' STD=-std=c99 -pedantic -DREDIS_STATIC=''
ifneq (,$(findstring clang,$(CC)))
ifneq (,$(findstring FreeBSD,$(uname_S)))
STD+=-Wno-c11-extensions
endif
endif
WARN=-Wall -W -Wno-missing-field-initializers WARN=-Wall -W -Wno-missing-field-initializers
OPT=$(OPTIMIZATION) OPT=$(OPTIMIZATION)
...@@ -41,6 +46,10 @@ endif ...@@ -41,6 +46,10 @@ endif
# To get ARM stack traces if Redis crashes we need a special C flag. # To get ARM stack traces if Redis crashes we need a special C flag.
ifneq (,$(filter aarch64 armv,$(uname_M))) ifneq (,$(filter aarch64 armv,$(uname_M)))
CFLAGS+=-funwind-tables CFLAGS+=-funwind-tables
else
ifneq (,$(findstring armv,$(uname_M)))
CFLAGS+=-funwind-tables
endif
endif endif
# Backwards compatibility for selecting an allocator # Backwards compatibility for selecting an allocator
...@@ -93,10 +102,20 @@ else ...@@ -93,10 +102,20 @@ else
ifeq ($(uname_S),OpenBSD) ifeq ($(uname_S),OpenBSD)
# OpenBSD # OpenBSD
FINAL_LIBS+= -lpthread FINAL_LIBS+= -lpthread
ifeq ($(USE_BACKTRACE),yes)
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
FINAL_LDFLAGS+= -L/usr/local/lib
FINAL_LIBS+= -lexecinfo
endif
else else
ifeq ($(uname_S),FreeBSD) ifeq ($(uname_S),FreeBSD)
# FreeBSD # FreeBSD
FINAL_LIBS+= -lpthread FINAL_LIBS+= -lpthread -lexecinfo
else
ifeq ($(uname_S),DragonFly)
# FreeBSD
FINAL_LIBS+= -lpthread -lexecinfo
else else
# All the other OSes (notably Linux) # All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic FINAL_LDFLAGS+= -rdynamic
...@@ -106,6 +125,7 @@ endif ...@@ -106,6 +125,7 @@ endif
endif endif
endif endif
endif endif
endif
# Include paths to dependencies # Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
...@@ -144,7 +164,7 @@ endif ...@@ -144,7 +164,7 @@ endif
REDIS_SERVER_NAME=redis-server REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel 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 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 gopher.o
REDIS_CLI_NAME=redis-cli 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_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 REDIS_BENCHMARK_NAME=redis-benchmark
......
/*
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include <fcntl.h>
/* =============================================================================
* Global state for ACLs
* ==========================================================================*/
rax *Users; /* Table mapping usernames to user structures. */
user *DefaultUser; /* Global reference to the default user.
Every new connection is associated to it, if no
AUTH or HELLO is used to authenticate with a
different user. */
list *UsersToLoad; /* This is a list of users found in the configuration file
that we'll need to load in the final stage of Redis
initialization, after all the modules are already
loaded. Every list element is a NULL terminated
array of SDS pointers: the first is the user name,
all the remaining pointers are ACL rules in the same
format as ACLSetUser(). */
struct ACLCategoryItem {
const char *name;
uint64_t flag;
} ACLCommandCategories[] = {
{"keyspace", CMD_CATEGORY_KEYSPACE},
{"read", CMD_CATEGORY_READ},
{"write", CMD_CATEGORY_WRITE},
{"set", CMD_CATEGORY_SET},
{"sortedset", CMD_CATEGORY_SORTEDSET},
{"list", CMD_CATEGORY_LIST},
{"hash", CMD_CATEGORY_HASH},
{"string", CMD_CATEGORY_STRING},
{"bitmap", CMD_CATEGORY_BITMAP},
{"hyperloglog", CMD_CATEGORY_HYPERLOGLOG},
{"geo", CMD_CATEGORY_GEO},
{"stream", CMD_CATEGORY_STREAM},
{"pubsub", CMD_CATEGORY_PUBSUB},
{"admin", CMD_CATEGORY_ADMIN},
{"fast", CMD_CATEGORY_FAST},
{"slow", CMD_CATEGORY_SLOW},
{"blocking", CMD_CATEGORY_BLOCKING},
{"dangerous", CMD_CATEGORY_DANGEROUS},
{"connection", CMD_CATEGORY_CONNECTION},
{"transaction", CMD_CATEGORY_TRANSACTION},
{"scripting", CMD_CATEGORY_SCRIPTING},
{NULL,0} /* Terminator. */
};
struct ACLUserFlag {
const char *name;
uint64_t flag;
} ACLUserFlags[] = {
{"on", USER_FLAG_ENABLED},
{"off", USER_FLAG_DISABLED},
{"allkeys", USER_FLAG_ALLKEYS},
{"allcommands", USER_FLAG_ALLCOMMANDS},
{"nopass", USER_FLAG_NOPASS},
{NULL,0} /* Terminator. */
};
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
void ACLResetSubcommands(user *u);
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
/* =============================================================================
* Helper functions for the rest of the ACL implementation
* ==========================================================================*/
/* Return zero if strings are the same, non-zero if they are not.
* The comparison is performed in a way that prevents an attacker to obtain
* information about the nature of the strings just monitoring the execution
* time of the function.
*
* Note that limiting the comparison length to strings up to 512 bytes we
* can avoid leaking any information about the password length and any
* possible branch misprediction related leak.
*/
int time_independent_strcmp(char *a, char *b) {
char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
/* The above two strlen perform len(a) + len(b) operations where either
* a or b are fixed (our password) length, and the difference is only
* relative to the length of the user provided string, so no information
* leak is possible in the following two lines of code. */
unsigned int alen = strlen(a);
unsigned int blen = strlen(b);
unsigned int j;
int diff = 0;
/* We can't compare strings longer than our static buffers.
* Note that this will never pass the first test in practical circumstances
* so there is no info leak. */
if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;
memset(bufa,0,sizeof(bufa)); /* Constant time. */
memset(bufb,0,sizeof(bufb)); /* Constant time. */
/* Again the time of the following two copies is proportional to
* len(a) + len(b) so no info is leaked. */
memcpy(bufa,a,alen);
memcpy(bufb,b,blen);
/* Always compare all the chars in the two buffers without
* conditional expressions. */
for (j = 0; j < sizeof(bufa); j++) {
diff |= (bufa[j] ^ bufb[j]);
}
/* Length must be equal as well. */
diff |= alen ^ blen;
return diff; /* If zero strings are the same. */
}
/* =============================================================================
* Low level ACL API
* ==========================================================================*/
/* Given the category name the command returns the corresponding flag, or
* zero if there is no match. */
uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
if (!strcasecmp(name,ACLCommandCategories[j].name)) {
return ACLCommandCategories[j].flag;
}
}
return 0; /* No match. */
}
/* 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;
}
/* Method to free list elements from ACL users password/ptterns lists. */
void ACLListFreeSds(void *item) {
sdsfree(item);
}
/* Method to duplicate list elements from ACL users password/ptterns lists. */
void *ACLListDupSds(void *item) {
return sdsdup(item);
}
/* Create a new user with the specified name, store it in the list
* of users (the Users global radix tree), and returns a reference to
* the structure representing the user.
*
* If the user with such name already exists NULL is returned. */
user *ACLCreateUser(const char *name, size_t namelen) {
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
user *u = zmalloc(sizeof(*u));
u->name = sdsnewlen(name,namelen);
u->flags = USER_FLAG_DISABLED;
u->allowed_subcommands = NULL;
u->passwords = listCreate();
u->patterns = listCreate();
listSetMatchMethod(u->passwords,ACLListMatchSds);
listSetFreeMethod(u->passwords,ACLListFreeSds);
listSetDupMethod(u->passwords,ACLListDupSds);
listSetMatchMethod(u->patterns,ACLListMatchSds);
listSetFreeMethod(u->patterns,ACLListFreeSds);
listSetDupMethod(u->patterns,ACLListDupSds);
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
return u;
}
/* This function should be called when we need an unlinked "fake" user
* we can use in order to validate ACL rules or for other similar reasons.
* The user will not get linked to the Users radix tree. The returned
* user should be released with ACLFreeUser() as usually. */
user *ACLCreateUnlinkedUser(void) {
char username[64];
for (int j = 0; ; j++) {
snprintf(username,sizeof(username),"__fakeuser:%d__",j);
user *fakeuser = ACLCreateUser(username,strlen(username));
if (fakeuser == NULL) continue;
int retval = raxRemove(Users,(unsigned char*) username,
strlen(username),NULL);
serverAssert(retval != 0);
return fakeuser;
}
}
/* Release the memory used by the user structure. Note that this function
* will not remove the user from the Users global radix tree. */
void ACLFreeUser(user *u) {
sdsfree(u->name);
listRelease(u->passwords);
listRelease(u->patterns);
ACLResetSubcommands(u);
zfree(u);
}
/* When a user is deleted we need to cycle the active
* connections in order to kill all the pending ones that
* are authenticated with such user. */
void ACLFreeUserAndKillClients(user *u) {
listIter li;
listNode *ln;
listRewind(server.clients,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);
if (c->user == u) {
/* We'll free the conenction asynchronously, so
* in theory to set a different user is not needed.
* However if there are bugs in Redis, soon or later
* this may result in some security hole: it's much
* more defensive to set the default user and put
* it in non authenticated mode. */
c->user = DefaultUser;
c->authenticated = 0;
freeClientAsync(c);
}
}
ACLFreeUser(u);
}
/* Copy the user ACL rules from the source user 'src' to the destination
* user 'dst' so that at the end of the process they'll have exactly the
* same rules (but the names will continue to be the original ones). */
void ACLCopyUser(user *dst, user *src) {
listRelease(dst->passwords);
listRelease(dst->patterns);
dst->passwords = listDup(src->passwords);
dst->patterns = listDup(src->patterns);
memcpy(dst->allowed_commands,src->allowed_commands,
sizeof(dst->allowed_commands));
dst->flags = src->flags;
ACLResetSubcommands(dst);
/* Copy the allowed subcommands array of array of SDS strings. */
if (src->allowed_subcommands) {
for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
if (src->allowed_subcommands[j]) {
for (int i = 0; src->allowed_subcommands[j][i]; i++)
{
ACLAddAllowedSubcommand(dst, j,
src->allowed_subcommands[j][i]);
}
}
}
}
}
/* Free all the users registered in the radix tree 'users' and free the
* radix tree itself. */
void ACLFreeUsersSet(rax *users) {
raxFreeWithCallback(users,(void(*)(void*))ACLFreeUserAndKillClients);
}
/* Given a command ID, this function set by reference 'word' and 'bit'
* so that user->allowed_commands[word] will address the right word
* where the corresponding bit for the provided ID is stored, and
* so that user->allowed_commands[word]&bit will identify that specific
* bit. The function returns C_ERR in case the specified ID overflows
* the bitmap in the user representation. */
int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
if (id >= USER_COMMAND_BITS_COUNT) return C_ERR;
*word = id / sizeof(uint64_t) / 8;
*bit = 1ULL << (id % (sizeof(uint64_t) * 8));
return C_OK;
}
/* Check if the specified command bit is set for the specified user.
* The function returns 1 is the bit is set or 0 if it is not.
* Note that this function does not check the ALLCOMMANDS flag of the user
* but just the lowlevel bitmask.
*
* If the bit overflows the user internal represetation, zero is returned
* in order to disallow the execution of the command in such edge case. */
int ACLGetUserCommandBit(user *u, unsigned long id) {
uint64_t word, bit;
if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return 0;
return (u->allowed_commands[word] & bit) != 0;
}
/* When +@all or allcommands is given, we set a reserved bit as well that we
* can later test, to see if the user has the right to execute "future commands",
* that is, commands loaded later via modules. */
int ACLUserCanExecuteFutureCommands(user *u) {
return ACLGetUserCommandBit(u,USER_COMMAND_BITS_COUNT-1);
}
/* Set the specified command bit for the specified user to 'value' (0 or 1).
* If the bit overflows the user internal represetation, no operation
* is performed. As a side effect of calling this function with a value of
* zero, the user flag ALLCOMMANDS is cleared since it is no longer possible
* to skip the command bit explicit test. */
void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
uint64_t word, bit;
if (value == 0) u->flags &= ~USER_FLAG_ALLCOMMANDS;
if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return;
if (value)
u->allowed_commands[word] |= bit;
else
u->allowed_commands[word] &= ~bit;
}
/* This is like ACLSetUserCommandBit(), but instead of setting the specified
* ID, it will check all the commands in the category specified as argument,
* and will set all the bits corresponding to such commands to the specified
* value. Since the category passed by the user may be non existing, the
* function returns C_ERR if the category was not found, or C_OK if it was
* found and the operation was performed. */
int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
if (!cflag) return C_ERR;
dictIterator *di = dictGetIterator(server.orig_commands);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */
if (cmd->flags & cflag) {
ACLSetUserCommandBit(u,cmd->id,value);
ACLResetSubcommandsForCommand(u,cmd->id);
}
}
dictReleaseIterator(di);
return C_OK;
}
/* Return the number of commands allowed (on) and denied (off) for the user 'u'
* in the subset of commands flagged with the specified category name.
* If the categoty name is not valid, C_ERR is returend, otherwise C_OK is
* returned and on and off are populated by reference. */
int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
const char *category)
{
uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
if (!cflag) return C_ERR;
*on = *off = 0;
dictIterator *di = dictGetIterator(server.orig_commands);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & cflag) {
if (ACLGetUserCommandBit(u,cmd->id))
(*on)++;
else
(*off)++;
}
}
dictReleaseIterator(di);
return C_OK;
}
/* This function returns an SDS string representing the specified user ACL
* rules related to command execution, in the same format you could set them
* back using ACL SETUSER. The function will return just the set of rules needed
* to recreate the user commands bitmap, without including other user flags such
* as on/off, passwords and so forth. The returned string always starts with
* the +@all or -@all rule, depending on the user bitmap, and is followed, if
* needed, by the other rules needed to narrow or extend what the user can do. */
sds ACLDescribeUserCommandRules(user *u) {
sds rules = sdsempty();
int additive; /* If true we start from -@all and add, otherwise if
false we start from +@all and remove. */
/* This code is based on a trick: as we generate the rules, we apply
* them to a fake user, so that as we go we still know what are the
* bit differences we should try to address by emitting more rules. */
user fu = {0};
user *fakeuser = &fu;
/* Here we want to understand if we should start with +@all and remove
* the commands corresponding to the bits that are not set in the user
* commands bitmap, or the contrary. Note that semantically the two are
* different. For instance starting with +@all and subtracting, the user
* will be able to execute future commands, while -@all and adding will just
* allow the user the run the selected commands and/or categories.
* How do we test for that? We use the trick of a reserved command ID bit
* that is set only by +@all (and its alias "allcommands"). */
if (ACLUserCanExecuteFutureCommands(u)) {
additive = 0;
rules = sdscat(rules,"+@all ");
ACLSetUser(fakeuser,"+@all",-1);
} else {
additive = 1;
rules = sdscat(rules,"-@all ");
ACLSetUser(fakeuser,"-@all",-1);
}
/* Try to add or subtract each category one after the other. Often a
* single category will not perfectly match the set of commands into
* it, so at the end we do a final pass adding/removing the single commands
* needed to make the bitmap exactly match. */
for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
unsigned long on, off;
ACLCountCategoryBitsForUser(u,&on,&off,ACLCommandCategories[j].name);
if ((additive && on > off) || (!additive && off > on)) {
sds op = sdsnewlen(additive ? "+@" : "-@", 2);
op = sdscat(op,ACLCommandCategories[j].name);
ACLSetUser(fakeuser,op,-1);
rules = sdscatsds(rules,op);
rules = sdscatlen(rules," ",1);
sdsfree(op);
}
}
/* Fix the final ACLs with single commands differences. */
dictIterator *di = dictGetIterator(server.orig_commands);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
int userbit = ACLGetUserCommandBit(u,cmd->id);
int fakebit = ACLGetUserCommandBit(fakeuser,cmd->id);
if (userbit != fakebit) {
rules = sdscatlen(rules, userbit ? "+" : "-", 1);
rules = sdscat(rules,cmd->name);
rules = sdscatlen(rules," ",1);
ACLSetUserCommandBit(fakeuser,cmd->id,userbit);
}
/* Emit the subcommands if there are any. */
if (userbit == 0 && u->allowed_subcommands &&
u->allowed_subcommands[cmd->id])
{
for (int j = 0; u->allowed_subcommands[cmd->id][j]; j++) {
rules = sdscatlen(rules,"+",1);
rules = sdscat(rules,cmd->name);
rules = sdscatlen(rules,"|",1);
rules = sdscatsds(rules,u->allowed_subcommands[cmd->id][j]);
rules = sdscatlen(rules," ",1);
}
}
}
dictReleaseIterator(di);
/* Trim the final useless space. */
sdsrange(rules,0,-2);
/* This is technically not needed, but we want to verify that now the
* predicted bitmap is exactly the same as the user bitmap, and abort
* otherwise, because aborting is better than a security risk in this
* code path. */
if (memcmp(fakeuser->allowed_commands,
u->allowed_commands,
sizeof(u->allowed_commands)) != 0)
{
serverLog(LL_WARNING,
"CRITICAL ERROR: User ACLs don't match final bitmap: '%s'",
rules);
serverPanic("No bitmap match in ACLDescribeUserCommandRules()");
}
return rules;
}
/* This is similar to ACLDescribeUserCommandRules(), however instead of
* describing just the user command rules, everything is described: user
* flags, keys, passwords and finally the command rules obtained via
* the ACLDescribeUserCommandRules() function. This is the function we call
* when we want to rewrite the configuration files describing ACLs and
* in order to show users with ACL LIST. */
sds ACLDescribeUser(user *u) {
sds res = sdsempty();
/* Flags. */
for (int j = 0; ACLUserFlags[j].flag; j++) {
/* Skip the allcommands and allkeys flags because they'll be emitted
* later as ~* and +@all. */
if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
if (u->flags & ACLUserFlags[j].flag) {
res = sdscat(res,ACLUserFlags[j].name);
res = sdscatlen(res," ",1);
}
}
/* Passwords. */
listIter li;
listNode *ln;
listRewind(u->passwords,&li);
while((ln = listNext(&li))) {
sds thispass = listNodeValue(ln);
res = sdscatlen(res,">",1);
res = sdscatsds(res,thispass);
res = sdscatlen(res," ",1);
}
/* Key patterns. */
if (u->flags & USER_FLAG_ALLKEYS) {
res = sdscatlen(res,"~* ",3);
} else {
listRewind(u->patterns,&li);
while((ln = listNext(&li))) {
sds thispat = listNodeValue(ln);
res = sdscatlen(res,"~",1);
res = sdscatsds(res,thispat);
res = sdscatlen(res," ",1);
}
}
/* Command rules. */
sds rules = ACLDescribeUserCommandRules(u);
res = sdscatsds(res,rules);
sdsfree(rules);
return res;
}
/* Get a command from the original command table, that is not affected
* by the command renaming operations: we base all the ACL work from that
* table, so that ACLs are valid regardless of command renaming. */
struct redisCommand *ACLLookupCommand(const char *name) {
struct redisCommand *cmd;
sds sdsname = sdsnew(name);
cmd = dictFetchValue(server.orig_commands, sdsname);
sdsfree(sdsname);
return cmd;
}
/* Flush the array of allowed subcommands for the specified user
* and command ID. */
void ACLResetSubcommandsForCommand(user *u, unsigned long id) {
if (u->allowed_subcommands && u->allowed_subcommands[id]) {
zfree(u->allowed_subcommands[id]);
u->allowed_subcommands[id] = NULL;
}
}
/* Flush the entire table of subcommands. This is useful on +@all, -@all
* or similar to return back to the minimal memory usage (and checks to do)
* for the user. */
void ACLResetSubcommands(user *u) {
if (u->allowed_subcommands == NULL) return;
for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
if (u->allowed_subcommands[j]) {
for (int i = 0; u->allowed_subcommands[j][i]; i++)
sdsfree(u->allowed_subcommands[j][i]);
zfree(u->allowed_subcommands[j]);
}
}
zfree(u->allowed_subcommands);
u->allowed_subcommands = NULL;
}
/* Add a subcommand to the list of subcommands for the user 'u' and
* the command id specified. */
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
/* If this is the first subcommand to be configured for
* this user, we have to allocate the subcommands array. */
if (u->allowed_subcommands == NULL) {
u->allowed_subcommands = zcalloc(USER_COMMAND_BITS_COUNT *
sizeof(sds*));
}
/* We also need to enlarge the allocation pointing to the
* null terminated SDS array, to make space for this one.
* To start check the current size, and while we are here
* make sure the subcommand is not already specified inside. */
long items = 0;
if (u->allowed_subcommands[id]) {
while(u->allowed_subcommands[id][items]) {
/* If it's already here do not add it again. */
if (!strcasecmp(u->allowed_subcommands[id][items],sub)) return;
items++;
}
}
/* Now we can make space for the new item (and the null term). */
items += 2;
u->allowed_subcommands[id] = zrealloc(u->allowed_subcommands[id],
sizeof(sds)*items);
u->allowed_subcommands[id][items-2] = sdsnew(sub);
u->allowed_subcommands[id][items-1] = NULL;
}
/* Set user properties according to the string "op". The following
* is a description of what different strings will do:
*
* on Enable the user: it is possible to authenticate as this user.
* off Disable the user: it's no longer possible to authenticate
* with this user, however the already authenticated connections
* will still work.
* +<command> Allow the execution of that command
* -<command> Disallow the execution of that command
* +@<category> Allow the execution of all the commands in such category
* with valid categories are like @admin, @set, @sortedset, ...
* and so forth, see the full list in the server.c file where
* the Redis command table is described and defined.
* The special category @all means all the commands, but currently
* present in the server, and that will be loaded in the future
* via modules.
* +<command>|subcommand Allow a specific subcommand of an otherwise
* disabled command. Note that this form is not
* allowed as negative like -DEBUG|SEGFAULT, but
* only additive starting with "+".
* allcommands Alias for +@all. Note that it implies the ability to execute
* all the future commands loaded via the modules system.
* nocommands Alias for -@all.
* ~<pattern> Add a pattern of keys that can be mentioned as part of
* commands. For instance ~* allows all the keys. The pattern
* is a glob-style pattern like the one of KEYS.
* It is possible to specify multiple patterns.
* allkeys Alias for ~*
* resetkeys Flush the list of allowed keys patterns.
* ><password> Add this passowrd to the list of valid password for the user.
* For example >mypass will add "mypass" to the list.
* This directive clears the "nopass" flag (see later).
* <<password> Remove this password from the list of valid passwords.
* nopass All the set passwords of the user are removed, and the user
* is flagged as requiring no password: it means that every
* password will work against this user. If this directive is
* used for the default user, every new connection will be
* immediately authenticated with the default user without
* any explicit AUTH command required. Note that the "resetpass"
* directive will clear this condition.
* 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).
* reset Performs the following actions: resetpass, resetkeys, off,
* -@all. The user returns to the same state it has immediately
* after its creation.
*
* The 'op' string must be null terminated. The 'oplen' argument should
* specify the length of the 'op' string in case the caller requires to pass
* binary data (for instance the >password form may use a binary password).
* Otherwise the field can be set to -1 and the function will use strlen()
* to determine the length.
*
* The function returns C_OK if the action to perform was understood because
* the 'op' string made sense. Otherwise C_ERR is returned if the operation
* is unknown or has some syntax error.
*
* When an error is returned, errno is set to the following values:
*
* EINVAL: The specified opcode is not understood.
* ENOENT: The command name or command category provided with + or - is not
* known.
* EBUSY: The subcommand you want to add is about a command that is currently
* fully added.
* EEXIST: You are adding a key pattern after "*" was already added. This is
* almost surely an error on the user side.
* ENODEV: The password you are trying to remove from the user does not exist.
*/
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;
u->flags &= ~USER_FLAG_DISABLED;
} else if (!strcasecmp(op,"off")) {
u->flags |= USER_FLAG_DISABLED;
u->flags &= ~USER_FLAG_ENABLED;
} else if (!strcasecmp(op,"allkeys") ||
!strcasecmp(op,"~*"))
{
u->flags |= USER_FLAG_ALLKEYS;
listEmpty(u->patterns);
} else if (!strcasecmp(op,"resetkeys")) {
u->flags &= ~USER_FLAG_ALLKEYS;
listEmpty(u->patterns);
} else if (!strcasecmp(op,"allcommands") ||
!strcasecmp(op,"+@all"))
{
memset(u->allowed_commands,255,sizeof(u->allowed_commands));
u->flags |= USER_FLAG_ALLCOMMANDS;
ACLResetSubcommands(u);
} else if (!strcasecmp(op,"nocommands") ||
!strcasecmp(op,"-@all"))
{
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
u->flags &= ~USER_FLAG_ALLCOMMANDS;
ACLResetSubcommands(u);
} else if (!strcasecmp(op,"nopass")) {
u->flags |= USER_FLAG_NOPASS;
listEmpty(u->passwords);
} else if (!strcasecmp(op,"resetpass")) {
u->flags &= ~USER_FLAG_NOPASS;
listEmpty(u->passwords);
} else if (op[0] == '>') {
sds newpass = sdsnewlen(op+1,oplen-1);
listNode *ln = listSearchKey(u->passwords,newpass);
/* Avoid re-adding the same password multiple times. */
if (ln == NULL) listAddNodeTail(u->passwords,newpass);
u->flags &= ~USER_FLAG_NOPASS;
} else if (op[0] == '<') {
sds delpass = sdsnewlen(op+1,oplen-1);
listNode *ln = listSearchKey(u->passwords,delpass);
sdsfree(delpass);
if (ln) {
listDelNode(u->passwords,ln);
} else {
errno = ENODEV;
return C_ERR;
}
} else if (op[0] == '~') {
if (u->flags & USER_FLAG_ALLKEYS) {
errno = EEXIST;
return C_ERR;
}
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 if (op[0] == '+' && op[1] != '@') {
if (strchr(op,'|') == NULL) {
if (ACLLookupCommand(op+1) == NULL) {
errno = ENOENT;
return C_ERR;
}
unsigned long id = ACLGetCommandID(op+1);
ACLSetUserCommandBit(u,id,1);
ACLResetSubcommandsForCommand(u,id);
} else {
/* Split the command and subcommand parts. */
char *copy = zstrdup(op+1);
char *sub = strchr(copy,'|');
sub[0] = '\0';
sub++;
/* Check if the command exists. We can't check the
* subcommand to see if it is valid. */
if (ACLLookupCommand(copy) == NULL) {
zfree(copy);
errno = ENOENT;
return C_ERR;
}
unsigned long id = ACLGetCommandID(copy);
/* The subcommand cannot be empty, so things like DEBUG|
* are syntax errors of course. */
if (strlen(sub) == 0) {
zfree(copy);
errno = EINVAL;
return C_ERR;
}
/* The command should not be set right now in the command
* bitmap, because adding a subcommand of a fully added
* command is probably an error on the user side. */
if (ACLGetUserCommandBit(u,id) == 1) {
zfree(copy);
errno = EBUSY;
return C_ERR;
}
/* Add the subcommand to the list of valid ones. */
ACLAddAllowedSubcommand(u,id,sub);
/* We have to clear the command bit so that we force the
* subcommand check. */
ACLSetUserCommandBit(u,id,0);
zfree(copy);
}
} else if (op[0] == '-' && op[1] != '@') {
if (ACLLookupCommand(op+1) == NULL) {
errno = ENOENT;
return C_ERR;
}
unsigned long id = ACLGetCommandID(op+1);
ACLSetUserCommandBit(u,id,0);
ACLResetSubcommandsForCommand(u,id);
} else if ((op[0] == '+' || op[0] == '-') && op[1] == '@') {
int bitval = op[0] == '+' ? 1 : 0;
if (ACLSetUserCommandBitsForCategory(u,op+2,bitval) == C_ERR) {
errno = ENOENT;
return C_ERR;
}
} else if (!strcasecmp(op,"reset")) {
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
serverAssert(ACLSetUser(u,"off",-1) == C_OK);
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
} else {
errno = EINVAL;
return C_ERR;
}
return C_OK;
}
/* Return a description of the error that occurred in ACLSetUser() according to
* the errno value set by the function on error. */
char *ACLSetUserStringError(void) {
char *errmsg = "Wrong format";
if (errno == ENOENT)
errmsg = "Unknown command or category name in ACL";
else if (errno == EINVAL)
errmsg = "Syntax error";
else if (errno == EBUSY)
errmsg = "Adding a subcommand of a command already fully "
"added is not allowed. Remove the command to start. "
"Example: -DEBUG +DEBUG|DIGEST";
else if (errno == EEXIST)
errmsg = "Adding a pattern after the * pattern (or the "
"'allkeys' flag) is not valid and does not have any "
"effect. Try 'resetkeys' to start with an empty "
"list of patterns";
else if (errno == ENODEV)
errmsg = "The password you are trying to remove from the user does "
"not exist";
return errmsg;
}
/* 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);
}
/* Initialize the default user, that will always exist for all the process
* lifetime. */
void ACLInitDefaultUser(void) {
DefaultUser = ACLCreateUser("default",7);
ACLSetUser(DefaultUser,"+@all",-1);
ACLSetUser(DefaultUser,"~*",-1);
ACLSetUser(DefaultUser,"on",-1);
ACLSetUser(DefaultUser,"nopass",-1);
}
/* Initialization of the ACL subsystem. */
void ACLInit(void) {
Users = raxNew();
UsersToLoad = listCreate();
ACLInitDefaultUser();
}
/* Check the username and password pair and return C_OK if they are valid,
* otherwise C_ERR is returned and errno is set to:
*
* EINVAL: if the username-password do not match.
* ENONENT: if the specified user does not exist at all.
*/
int ACLCheckUserCredentials(robj *username, robj *password) {
user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr));
if (u == NULL) {
errno = ENOENT;
return C_ERR;
}
/* Disabled users can't login. */
if (u->flags & USER_FLAG_DISABLED) {
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;
}
/* This is like ACLCheckUserCredentials(), however if the user/pass
* are correct, the connection is put in authenticated state and the
* connection user reference is populated.
*
* The return value is C_OK or C_ERR with the same meaning as
* ACLCheckUserCredentials(). */
int ACLAuthenticateUser(client *c, robj *username, robj *password) {
if (ACLCheckUserCredentials(username,password) == C_OK) {
c->authenticated = 1;
c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
return C_OK;
} else {
return C_ERR;
}
}
/* For ACL purposes, every user has a bitmap with the commands that such
* user is allowed to execute. In order to populate the bitmap, every command
* should have an assigned ID (that is used to index the bitmap). This function
* creates such an ID: it uses sequential IDs, reusing the same ID for the same
* command name, so that a command retains the same ID in case of modules that
* are unloaded and later reloaded. */
unsigned long ACLGetCommandID(const char *cmdname) {
static rax *map = NULL;
static unsigned long nextid = 0;
sds lowername = sdsnew(cmdname);
sdstolower(lowername);
if (map == NULL) map = raxNew();
void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername));
if (id != raxNotFound) {
sdsfree(lowername);
return (unsigned long)id;
}
raxInsert(map,(unsigned char*)lowername,strlen(lowername),
(void*)nextid,NULL);
sdsfree(lowername);
unsigned long thisid = nextid;
nextid++;
/* We never assign the last bit in the user commands bitmap structure,
* this way we can later check if this bit is set, understanding if the
* current ACL for the user was created starting with a +@all to add all
* the possible commands and just subtracting other single commands or
* categories, or if, instead, the ACL was created just adding commands
* and command categories from scratch, not allowing future commands by
* default (loaded via modules). This is useful when rewriting the ACLs
* with ACL SAVE. */
if (nextid == USER_COMMAND_BITS_COUNT-1) nextid++;
return thisid;
}
/* Return an username by its name, or NULL if the user does not exist. */
user *ACLGetUserByName(const char *name, size_t namelen) {
void *myuser = raxFind(Users,(unsigned char*)name,namelen);
if (myuser == raxNotFound) return NULL;
return myuser;
}
/* Check if the command ready to be excuted in the client 'c', and already
* referenced by c->cmd, can be executed by this client according to the
* ACls associated to the client user c->user.
*
* If the user can execute the command ACL_OK is returned, otherwise
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
* command cannot be executed because the user is not allowed to run such
* command, the second if the command is denied because the user is trying
* to access keys that are not among the specified patterns. */
int ACLCheckCommandPerm(client *c) {
user *u = c->user;
uint64_t id = c->cmd->id;
/* If there is no associated user, the connection can run anything. */
if (u == NULL) return ACL_OK;
/* Check if the user can execute this command. */
if (!(u->flags & USER_FLAG_ALLCOMMANDS) &&
c->cmd->proc != authCommand)
{
/* If the bit is not set we have to check further, in case the
* command is allowed just with that specific subcommand. */
if (ACLGetUserCommandBit(u,id) == 0) {
/* Check if the subcommand matches. */
if (c->argc < 2 ||
u->allowed_subcommands == NULL ||
u->allowed_subcommands[id] == NULL)
{
return ACL_DENIED_CMD;
}
long subid = 0;
while (1) {
if (u->allowed_subcommands[id][subid] == NULL)
return ACL_DENIED_CMD;
if (!strcasecmp(c->argv[1]->ptr,
u->allowed_subcommands[id][subid]))
break; /* Subcommand match found. Stop here. */
subid++;
}
}
}
/* Check if the user can execute commands explicitly touching the keys
* mentioned in the command arguments. */
if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
(c->cmd->getkeys_proc || c->cmd->firstkey))
{
int numkeys;
int *keyidx = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
for (int j = 0; j < numkeys; j++) {
listIter li;
listNode *ln;
listRewind(u->patterns,&li);
/* Test this key against every pattern. */
int match = 0;
while((ln = listNext(&li))) {
sds pattern = listNodeValue(ln);
size_t plen = sdslen(pattern);
int idx = keyidx[j];
if (stringmatchlen(pattern,plen,c->argv[idx]->ptr,
sdslen(c->argv[idx]->ptr),0))
{
match = 1;
break;
}
}
if (!match) {
getKeysFreeResult(keyidx);
return ACL_DENIED_KEY;
}
}
getKeysFreeResult(keyidx);
}
/* If we survived all the above checks, the user can execute the
* command. */
return ACL_OK;
}
/* =============================================================================
* ACL loading / saving functions
* ==========================================================================*/
/* Given an argument vector describing a user in the form:
*
* user <username> ... ACL rules and flags ...
*
* this function validates, and if the syntax is valid, appends
* the user definition to a list for later loading.
*
* The rules are tested for validity and if there obvious syntax errors
* the function returns C_ERR and does nothing, otherwise C_OK is returned
* and the user is appended to the list.
*
* Note that this function cannot stop in case of commands that are not found
* and, in that case, the error will be emitted later, because certain
* commands may be defined later once modules are loaded.
*
* When an error is detected and C_ERR is returned, the function populates
* by reference (if not set to NULL) the argc_err argument with the index
* of the argv vector that caused the error. */
int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
if (argc < 2 || strcasecmp(argv[0],"user")) {
if (argc_err) *argc_err = 0;
return C_ERR;
}
/* Try to apply the user rules in a fake user to see if they
* are actually valid. */
user *fakeuser = ACLCreateUnlinkedUser();
for (int j = 2; j < argc; j++) {
if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) == C_ERR) {
if (errno != ENOENT) {
ACLFreeUser(fakeuser);
if (argc_err) *argc_err = j;
return C_ERR;
}
}
}
/* Rules look valid, let's append the user to the list. */
sds *copy = zmalloc(sizeof(sds)*argc);
for (int j = 1; j < argc; j++) copy[j-1] = sdsdup(argv[j]);
copy[argc-1] = NULL;
listAddNodeTail(UsersToLoad,copy);
ACLFreeUser(fakeuser);
return C_OK;
}
/* This function will load the configured users appended to the server
* configuration via ACLAppendUserForLoading(). On loading errors it will
* log an error and return C_ERR, otherwise C_OK will be returned. */
int ACLLoadConfiguredUsers(void) {
listIter li;
listNode *ln;
listRewind(UsersToLoad,&li);
while ((ln = listNext(&li)) != NULL) {
sds *aclrules = listNodeValue(ln);
sds username = aclrules[0];
user *u = ACLCreateUser(username,sdslen(username));
if (!u) {
u = ACLGetUserByName(username,sdslen(username));
serverAssert(u != NULL);
ACLSetUser(u,"reset",-1);
}
/* Load every rule defined for this user. */
for (int j = 1; aclrules[j]; j++) {
if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) {
char *errmsg = ACLSetUserStringError();
serverLog(LL_WARNING,"Error loading ACL rule '%s' for "
"the user named '%s': %s",
aclrules[j],aclrules[0],errmsg);
return C_ERR;
}
}
/* Having a disabled user in the configuration may be an error,
* warn about it without returning any error to the caller. */
if (u->flags & USER_FLAG_DISABLED) {
serverLog(LL_NOTICE, "The user '%s' is disabled (there is no "
"'on' modifier in the user description). Make "
"sure this is not a configuration error.",
aclrules[0]);
}
}
return C_OK;
}
/* This function loads the ACL from the specified filename: every line
* is validated and shold be either empty or in the format used to specify
* users in the redis.conf configuration or in the ACL file, that is:
*
* user <username> ... rules ...
*
* Note that this function considers comments starting with '#' as errors
* because the ACL file is meant to be rewritten, and comments would be
* lost after the rewrite. Yet empty lines are allowed to avoid being too
* strict.
*
* One important part of implementing ACL LOAD, that uses this function, is
* to avoid ending with broken rules if the ACL file is invalid for some
* reason, so the function will attempt to validate the rules before loading
* each user. For every line that will be found broken the function will
* collect an error message.
*
* IMPORTANT: If there is at least a single error, nothing will be loaded
* and the rules will remain exactly as they were.
*
* At the end of the process, if no errors were found in the whole file then
* NULL is returned. Otherwise an SDS string describing in a single line
* a description of all the issues found is returned. */
sds ACLLoadFromFile(const char *filename) {
FILE *fp;
char buf[1024];
/* Open the ACL file. */
if ((fp = fopen(filename,"r")) == NULL) {
sds errors = sdscatprintf(sdsempty(),
"Error loading ACLs, opening file '%s': %s",
filename, strerror(errno));
return errors;
}
/* Load the whole file as a single string in memory. */
sds acls = sdsempty();
while(fgets(buf,sizeof(buf),fp) != NULL)
acls = sdscat(acls,buf);
fclose(fp);
/* Split the file into lines and attempt to load each line. */
int totlines;
sds *lines, errors = sdsempty();
lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines);
sdsfree(acls);
/* We need a fake user to validate the rules before making changes
* to the real user mentioned in the ACL line. */
user *fakeuser = ACLCreateUnlinkedUser();
/* We do all the loading in a fresh insteance of the Users radix tree,
* so if there are errors loading the ACL file we can rollback to the
* old version. */
rax *old_users = Users;
user *old_default_user = DefaultUser;
Users = raxNew();
ACLInitDefaultUser();
/* Load each line of the file. */
for (int i = 0; i < totlines; i++) {
sds *argv;
int argc;
int linenum = i+1;
lines[i] = sdstrim(lines[i]," \t\r\n");
/* Skip blank lines */
if (lines[i][0] == '\0') continue;
/* Split into arguments */
argv = sdssplitargs(lines[i],&argc);
if (argv == NULL) {
errors = sdscatprintf(errors,
"%s:%d: unbalanced quotes in acl line. ",
server.acl_filename, linenum);
continue;
}
/* Skip this line if the resulting command vector is empty. */
if (argc == 0) {
sdsfreesplitres(argv,argc);
continue;
}
/* The line should start with the "user" keyword. */
if (strcmp(argv[0],"user") || argc < 2) {
errors = sdscatprintf(errors,
"%s:%d should start with user keyword followed "
"by the username. ", server.acl_filename,
linenum);
sdsfreesplitres(argv,argc);
continue;
}
/* Try to process the line using the fake user to validate iif
* the rules are able to apply cleanly. */
ACLSetUser(fakeuser,"reset",-1);
int j;
for (j = 2; j < argc; j++) {
if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) {
char *errmsg = ACLSetUserStringError();
errors = sdscatprintf(errors,
"%s:%d: %s. ",
server.acl_filename, linenum, errmsg);
continue;
}
}
/* Apply the rule to the new users set only if so far there
* are no errors, otherwise it's useless since we are going
* to discard the new users set anyway. */
if (sdslen(errors) != 0) {
sdsfreesplitres(argv,argc);
continue;
}
/* We can finally lookup the user and apply the rule. If the
* user already exists we always reset it to start. */
user *u = ACLCreateUser(argv[1],sdslen(argv[1]));
if (!u) {
u = ACLGetUserByName(argv[1],sdslen(argv[1]));
serverAssert(u != NULL);
ACLSetUser(u,"reset",-1);
}
/* Note that the same rules already applied to the fake user, so
* we just assert that everything goess well: it should. */
for (j = 2; j < argc; j++)
serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);
sdsfreesplitres(argv,argc);
}
ACLFreeUser(fakeuser);
sdsfreesplitres(lines,totlines);
DefaultUser = old_default_user; /* This pointer must never change. */
/* Check if we found errors and react accordingly. */
if (sdslen(errors) == 0) {
/* The default user pointer is referenced in different places: instead
* of replacing such occurrences it is much simpler to copy the new
* default user configuration in the old one. */
user *new = ACLGetUserByName("default",7);
serverAssert(new != NULL);
ACLCopyUser(DefaultUser,new);
ACLFreeUser(new);
raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL);
raxRemove(old_users,(unsigned char*)"default",7,NULL);
ACLFreeUsersSet(old_users);
sdsfree(errors);
return NULL;
} else {
ACLFreeUsersSet(Users);
Users = old_users;
errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
return errors;
}
}
/* Generate a copy of the ACLs currently in memory in the specified filename.
* Returns C_OK on success or C_ERR if there was an error during the I/O.
* When C_ERR is returned a log is produced with hints about the issue. */
int ACLSaveToFile(const char *filename) {
sds acl = sdsempty();
int fd = -1;
sds tmpfilename = NULL;
int retval = C_ERR;
/* Let's generate an SDS string containing the new version of the
* ACL file. */
raxIterator ri;
raxStart(&ri,Users);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
user *u = ri.data;
/* Return information in the configuration file format. */
sds user = sdsnew("user ");
user = sdscatsds(user,u->name);
user = sdscatlen(user," ",1);
sds descr = ACLDescribeUser(u);
user = sdscatsds(user,descr);
sdsfree(descr);
acl = sdscatsds(acl,user);
acl = sdscatlen(acl,"\n",1);
sdsfree(user);
}
raxStop(&ri);
/* Create a temp file with the new content. */
tmpfilename = sdsnew(filename);
tmpfilename = sdscatfmt(tmpfilename,".tmp-%i-%I",
(int)getpid(),(int)mstime());
if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) {
serverLog(LL_WARNING,"Opening temp ACL file for ACL SAVE: %s",
strerror(errno));
goto cleanup;
}
/* Write it. */
if (write(fd,acl,sdslen(acl)) != (ssize_t)sdslen(acl)) {
serverLog(LL_WARNING,"Writing ACL file for ACL SAVE: %s",
strerror(errno));
goto cleanup;
}
close(fd); fd = -1;
/* Let's replace the new file with the old one. */
if (rename(tmpfilename,filename) == -1) {
serverLog(LL_WARNING,"Renaming ACL file for ACL SAVE: %s",
strerror(errno));
goto cleanup;
}
sdsfree(tmpfilename); tmpfilename = NULL;
retval = C_OK; /* If we reached this point, everything is fine. */
cleanup:
if (fd != -1) close(fd);
if (tmpfilename) unlink(tmpfilename);
sdsfree(tmpfilename);
sdsfree(acl);
return retval;
}
/* This function is called once the server is already running, modules are
* loaded, and we are ready to start, in order to load the ACLs either from
* the pending list of users defined in redis.conf, or from the ACL file.
* The function will just exit with an error if the user is trying to mix
* both the loading methods. */
void ACLLoadUsersAtStartup(void) {
if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
serverLog(LL_WARNING,
"Configuring Redis with users defined in redis.conf and at "
"the same setting an ACL file path is invalid. This setup "
"is very likely to lead to configuration errors and security "
"holes, please define either an ACL file or declare users "
"directly in your redis.conf, but not both.");
exit(1);
}
if (ACLLoadConfiguredUsers() == C_ERR) {
serverLog(LL_WARNING,
"Critical error while loading ACLs. Exiting.");
exit(1);
}
if (server.acl_filename[0] != '\0') {
sds errors = ACLLoadFromFile(server.acl_filename);
if (errors) {
serverLog(LL_WARNING,
"Aborting Redis startup because of ACL errors: %s", errors);
sdsfree(errors);
exit(1);
}
}
}
/* =============================================================================
* ACL related commands
* ==========================================================================*/
/* ACL -- show and modify the configuration of ACL users.
* ACL HELP
* ACL LOAD
* ACL LIST
* ACL USERS
* ACL CAT [<category>]
* ACL SETUSER <username> ... acl rules ...
* ACL DELUSER <username> [...]
* ACL GETUSER <username>
*/
void aclCommand(client *c) {
char *sub = c->argv[1]->ptr;
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
sds username = c->argv[2]->ptr;
/* Create a temporary user to validate and stage all changes against
* before applying to an existing user or creating a new user. If all
* arguments are valid the user parameters will all be applied together.
* If there are any errors then none of the changes will be applied. */
user *tempu = ACLCreateUnlinkedUser();
user *u = ACLGetUserByName(username,sdslen(username));
if (u) ACLCopyUser(tempu, u);
for (int j = 3; j < c->argc; j++) {
if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
char *errmsg = ACLSetUserStringError();
addReplyErrorFormat(c,
"Error in ACL SETUSER modifier '%s': %s",
(char*)c->argv[j]->ptr, errmsg);
ACLFreeUser(tempu);
return;
}
}
/* Overwrite the user with the temporary user we modified above. */
if (!u) u = ACLCreateUser(username,sdslen(username));
serverAssert(u != NULL);
ACLCopyUser(u, tempu);
ACLFreeUser(tempu);
addReply(c,shared.ok);
} else if (!strcasecmp(sub,"deluser") && c->argc >= 3) {
int deleted = 0;
for (int j = 2; j < c->argc; j++) {
sds username = c->argv[j]->ptr;
if (!strcmp(username,"default")) {
addReplyError(c,"The 'default' user cannot be removed");
return;
}
}
for (int j = 2; j < c->argc; j++) {
sds username = c->argv[j]->ptr;
user *u;
if (raxRemove(Users,(unsigned char*)username,
sdslen(username),
(void**)&u))
{
ACLFreeUserAndKillClients(u);
deleted++;
}
}
addReplyLongLong(c,deleted);
} else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
if (u == NULL) {
addReplyNull(c);
return;
}
addReplyMapLen(c,4);
/* Flags */
addReplyBulkCString(c,"flags");
void *deflen = addReplyDeferredLen(c);
int numflags = 0;
for (int j = 0; ACLUserFlags[j].flag; j++) {
if (u->flags & ACLUserFlags[j].flag) {
addReplyBulkCString(c,ACLUserFlags[j].name);
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));
}
/* Commands */
addReplyBulkCString(c,"commands");
sds cmddescr = ACLDescribeUserCommandRules(u);
addReplyBulkSds(c,cmddescr);
/* Key patterns */
addReplyBulkCString(c,"keys");
if (u->flags & USER_FLAG_ALLKEYS) {
addReplyArrayLen(c,1);
addReplyBulkCBuffer(c,"*",1);
} else {
addReplyArrayLen(c,listLength(u->patterns));
listIter li;
listNode *ln;
listRewind(u->patterns,&li);
while((ln = listNext(&li))) {
sds thispat = listNodeValue(ln);
addReplyBulkCBuffer(c,thispat,sdslen(thispat));
}
}
} else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&
c->argc == 2)
{
int justnames = !strcasecmp(sub,"users");
addReplyArrayLen(c,raxSize(Users));
raxIterator ri;
raxStart(&ri,Users);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
user *u = ri.data;
if (justnames) {
addReplyBulkCBuffer(c,u->name,sdslen(u->name));
} else {
/* Return information in the configuration file format. */
sds config = sdsnew("user ");
config = sdscatsds(config,u->name);
config = sdscatlen(config," ",1);
sds descr = ACLDescribeUser(u);
config = sdscatsds(config,descr);
sdsfree(descr);
addReplyBulkSds(c,config);
}
}
raxStop(&ri);
} else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
if (c->user != NULL) {
addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
} else {
addReplyNull(c);
}
} else if (server.acl_filename[0] == '\0' &&
(!strcasecmp(sub,"load") || !strcasecmp(sub,"save")))
{
addReplyError(c,"This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration.");
return;
} else if (!strcasecmp(sub,"load") && c->argc == 2) {
sds errors = ACLLoadFromFile(server.acl_filename);
if (errors == NULL) {
addReply(c,shared.ok);
} else {
addReplyError(c,errors);
sdsfree(errors);
}
} else if (!strcasecmp(sub,"save") && c->argc == 2) {
if (ACLSaveToFile(server.acl_filename) == C_OK) {
addReply(c,shared.ok);
} else {
addReplyError(c,"There was an error trying to save the ACLs. "
"Please check the server logs for more "
"information");
}
} else if (!strcasecmp(sub,"cat") && c->argc == 2) {
void *dl = addReplyDeferredLen(c);
int j;
for (j = 0; ACLCommandCategories[j].flag != 0; j++)
addReplyBulkCString(c,ACLCommandCategories[j].name);
setDeferredArrayLen(c,dl,j);
} else if (!strcasecmp(sub,"cat") && c->argc == 3) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr);
if (cflag == 0) {
addReplyErrorFormat(c, "Unknown category '%s'", (char*)c->argv[2]->ptr);
return;
}
int arraylen = 0;
void *dl = addReplyDeferredLen(c);
dictIterator *di = dictGetIterator(server.orig_commands);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
if (cmd->flags & CMD_MODULE) continue;
if (cmd->flags & cflag) {
addReplyBulkCString(c,cmd->name);
arraylen++;
}
}
dictReleaseIterator(di);
setDeferredArrayLen(c,dl,arraylen);
} else if (!strcasecmp(sub,"help")) {
const char *help[] = {
"LOAD -- Reload users from the ACL file.",
"LIST -- Show user details in config file format.",
"USERS -- List all the registered usernames.",
"SETUSER <username> [attribs ...] -- Create or modify a user.",
"GETUSER <username> -- Get the user details.",
"DELUSER <username> [...] -- Delete a list of users.",
"CAT -- List available categories.",
"CAT <category> -- List commands inside category.",
"WHOAMI -- Return the current connection username.",
NULL
};
addReplyHelp(c,help);
} else {
addReplySubcommandSyntaxError(c);
}
}
void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
int flagcount = 0;
void *flaglen = addReplyDeferredLen(c);
for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
if (cmd->flags & ACLCommandCategories[j].flag) {
addReplyStatusFormat(c, "@%s", ACLCommandCategories[j].name);
flagcount++;
}
}
setDeferredSetLen(c, flaglen, flagcount);
}
/* AUTH <passowrd>
* AUTH <username> <password> (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) {
/* Only two or three argument forms are allowed. */
if (c->argc > 3) {
addReply(c,shared.syntaxerr);
return;
}
/* Handle the two different forms here. The form with two arguments
* will just use "default" as username. */
robj *username, *password;
if (c->argc == 2) {
/* Mimic the old behavior of giving an error for the two commands
* from if no password is configured. */
if (DefaultUser->flags & USER_FLAG_NOPASS) {
addReplyError(c,"AUTH <password> called without any password "
"configured for the default user. Are you sure "
"your configuration is correct?");
return;
}
username = createStringObject("default",7);
password = c->argv[1];
} else {
username = c->argv[1];
password = c->argv[2];
}
if (ACLAuthenticateUser(c,username,password) == C_OK) {
addReply(c,shared.ok);
} else {
addReplyError(c,"-WRONGPASS invalid username-password pair");
}
/* Free the "default" string object we created for the two
* arguments form. */
if (c->argc == 2) decrRefCount(username);
}
...@@ -351,8 +351,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) { ...@@ -351,8 +351,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
* if flags has AE_FILE_EVENTS set, file events are processed. * 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_TIME_EVENTS set, time events are processed.
* if flags has AE_DONT_WAIT set the function returns ASAP until all * 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. * 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. */ * The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags) int aeProcessEvents(aeEventLoop *eventLoop, int flags)
......
...@@ -204,7 +204,7 @@ void aof_background_fsync(int fd) { ...@@ -204,7 +204,7 @@ void aof_background_fsync(int fd) {
} }
/* Kills an AOFRW child process if exists */ /* Kills an AOFRW child process if exists */
static void killAppendOnlyChild(void) { void killAppendOnlyChild(void) {
int statloc; int statloc;
/* No AOFRW child? return. */ /* No AOFRW child? return. */
if (server.aof_child_pid == -1) return; if (server.aof_child_pid == -1) return;
...@@ -221,6 +221,8 @@ static void killAppendOnlyChild(void) { ...@@ -221,6 +221,8 @@ static void killAppendOnlyChild(void) {
server.aof_rewrite_time_start = -1; server.aof_rewrite_time_start = -1;
/* Close pipes used for IPC between the two processes. */ /* Close pipes used for IPC between the two processes. */
aofClosePipes(); aofClosePipes();
closeChildInfoPipe();
updateDictResizePolicy();
} }
/* Called when the user switches from "appendonly yes" to "appendonly no" /* Called when the user switches from "appendonly yes" to "appendonly no"
...@@ -645,6 +647,8 @@ struct client *createFakeClient(void) { ...@@ -645,6 +647,8 @@ struct client *createFakeClient(void) {
c->obuf_soft_limit_reached_time = 0; c->obuf_soft_limit_reached_time = 0;
c->watched_keys = listCreate(); c->watched_keys = listCreate();
c->peerid = NULL; c->peerid = NULL;
c->resp = 2;
c->user = NULL;
listSetFreeMethod(c->reply,freeClientReplyValue); listSetFreeMethod(c->reply,freeClientReplyValue);
listSetDupMethod(c->reply,dupClientReplyValue); listSetDupMethod(c->reply,dupClientReplyValue);
initClientMultiState(c); initClientMultiState(c);
...@@ -677,6 +681,7 @@ int loadAppendOnlyFile(char *filename) { ...@@ -677,6 +681,7 @@ int loadAppendOnlyFile(char *filename) {
int old_aof_state = server.aof_state; int old_aof_state = server.aof_state;
long loops = 0; long loops = 0;
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */ off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
if (fp == NULL) { if (fp == NULL) {
serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno)); serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
...@@ -777,16 +782,28 @@ int loadAppendOnlyFile(char *filename) { ...@@ -777,16 +782,28 @@ int loadAppendOnlyFile(char *filename) {
/* Command lookup */ /* Command lookup */
cmd = lookupCommand(argv[0]->ptr); cmd = lookupCommand(argv[0]->ptr);
if (!cmd) { if (!cmd) {
serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr); serverLog(LL_WARNING,
"Unknown command '%s' reading the append only file",
(char*)argv[0]->ptr);
exit(1); exit(1);
} }
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
/* Run the command in the context of a fake client */ /* Run the command in the context of a fake client */
fakeClient->cmd = cmd; fakeClient->cmd = cmd;
if (fakeClient->flags & CLIENT_MULTI &&
fakeClient->cmd->proc != execCommand)
{
queueMultiCommand(fakeClient);
} else {
cmd->proc(fakeClient); cmd->proc(fakeClient);
}
/* The fake client should not have a reply */ /* The fake client should not have a reply */
serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0); serverAssert(fakeClient->bufpos == 0 &&
listLength(fakeClient->reply) == 0);
/* The fake client should never get blocked */ /* The fake client should never get blocked */
serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0); serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
...@@ -798,8 +815,15 @@ int loadAppendOnlyFile(char *filename) { ...@@ -798,8 +815,15 @@ int loadAppendOnlyFile(char *filename) {
} }
/* This point can only be reached when EOF is reached without errors. /* This point can only be reached when EOF is reached without errors.
* If the client is in the middle of a MULTI/EXEC, log error and quit. */ * If the client is in the middle of a MULTI/EXEC, handle it as it was
if (fakeClient->flags & CLIENT_MULTI) goto uxeof; * a short read, even if technically the protocol is correct: we want
* to remove the unprocessed tail and continue. */
if (fakeClient->flags & CLIENT_MULTI) {
serverLog(LL_WARNING,
"Revert incomplete MULTI/EXEC transaction in AOF file");
valid_up_to = valid_before_multi;
goto uxeof;
}
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
fclose(fp); fclose(fp);
...@@ -1119,6 +1143,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1119,6 +1143,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
streamID id; streamID id;
int64_t numfields; int64_t numfields;
if (s->length) {
/* Reconstruct the stream data using XADD commands. */ /* Reconstruct the stream data using XADD commands. */
while(streamIteratorGetID(&si,&id,&numfields)) { while(streamIteratorGetID(&si,&id,&numfields)) {
/* Emit a two elements array for each item. The first is /* Emit a two elements array for each item. The first is
...@@ -1137,6 +1162,27 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) { ...@@ -1137,6 +1162,27 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0; if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
} }
} }
} else {
/* Use the XADD MAXLEN 0 trick to generate an empty stream if
* the key we are serializing is an empty string, which is possible
* for the Stream type. */
if (rioWriteBulkCount(r,'*',7) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
if (rioWriteBulkString(r,"0",1) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
if (rioWriteBulkString(r,"x",1) == 0) return 0;
if (rioWriteBulkString(r,"y",1) == 0) return 0;
}
/* Append XSETID after XADD, make sure lastid is correct,
* in case of XDEL lastid. */
if (rioWriteBulkCount(r,'*',3) == 0) return 0;
if (rioWriteBulkString(r,"XSETID",6) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
/* Create all the stream consumer groups. */ /* Create all the stream consumer groups. */
if (s->cgroups) { if (s->cgroups) {
......
/* This file implements atomic counters using __atomic or __sync macros if /* This file implements atomic counters using __atomic or __sync macros if
* available, otherwise synchronizing different threads using a mutex. * available, otherwise synchronizing different threads using a mutex.
* *
* The exported interaface is composed of three macros: * The exported interface is composed of three macros:
* *
* atomicIncr(var,count) -- Increment the atomic counter * atomicIncr(var,count) -- Increment the atomic counter
* atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter * atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
* *
* The design is trivial, we have a structure representing a job to perform * The design is trivial, we have a structure representing a job to perform
* and a different thread and job queue for every job type. * and a different thread and job queue for every job type.
* Every thread wait for new jobs in its queue, and process every job * Every thread waits for new jobs in its queue, and process every job
* sequentially. * sequentially.
* *
* Jobs of the same type are guaranteed to be processed from the least * Jobs of the same type are guaranteed to be processed from the least
...@@ -204,14 +204,14 @@ void *bioProcessBackgroundJobs(void *arg) { ...@@ -204,14 +204,14 @@ void *bioProcessBackgroundJobs(void *arg) {
} }
zfree(job); zfree(job);
/* Unblock threads blocked on bioWaitStepOfType() if any. */
pthread_cond_broadcast(&bio_step_cond[type]);
/* Lock again before reiterating the loop, if there are no longer /* Lock again before reiterating the loop, if there are no longer
* jobs to process we'll block again in pthread_cond_wait(). */ * jobs to process we'll block again in pthread_cond_wait(). */
pthread_mutex_lock(&bio_mutex[type]); pthread_mutex_lock(&bio_mutex[type]);
listDelNode(bio_jobs[type],ln); listDelNode(bio_jobs[type],ln);
bio_pending[type]--; bio_pending[type]--;
/* Unblock threads blocked on bioWaitStepOfType() if any. */
pthread_cond_broadcast(&bio_step_cond[type]);
} }
} }
......
...@@ -1002,7 +1002,7 @@ void bitfieldCommand(client *c) { ...@@ -1002,7 +1002,7 @@ void bitfieldCommand(client *c) {
highest_write_offset)) == NULL) return; highest_write_offset)) == NULL) return;
} }
addReplyMultiBulkLen(c,numops); addReplyArrayLen(c,numops);
/* Actually process the operations. */ /* Actually process the operations. */
for (j = 0; j < numops; j++) { for (j = 0; j < numops; j++) {
...@@ -1047,7 +1047,7 @@ void bitfieldCommand(client *c) { ...@@ -1047,7 +1047,7 @@ void bitfieldCommand(client *c) {
setSignedBitfield(o->ptr,thisop->offset, setSignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval); thisop->bits,newval);
} else { } else {
addReply(c,shared.nullbulk); addReplyNull(c);
} }
} else { } else {
uint64_t oldval, newval, wrapped, retval; uint64_t oldval, newval, wrapped, retval;
...@@ -1076,7 +1076,7 @@ void bitfieldCommand(client *c) { ...@@ -1076,7 +1076,7 @@ void bitfieldCommand(client *c) {
setUnsignedBitfield(o->ptr,thisop->offset, setUnsignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval); thisop->bits,newval);
} else { } else {
addReply(c,shared.nullbulk); addReplyNull(c);
} }
} }
changes++; changes++;
......
...@@ -126,12 +126,37 @@ void processUnblockedClients(void) { ...@@ -126,12 +126,37 @@ void processUnblockedClients(void) {
* the code is conceptually more correct this way. */ * the code is conceptually more correct this way. */
if (!(c->flags & CLIENT_BLOCKED)) { if (!(c->flags & CLIENT_BLOCKED)) {
if (c->querybuf && sdslen(c->querybuf) > 0) { if (c->querybuf && sdslen(c->querybuf) > 0) {
processInputBuffer(c); processInputBufferAndReplicate(c);
} }
} }
} }
} }
/* This function will schedule the client for reprocessing at a safe time.
*
* This is useful when a client was blocked for some reason (blocking opeation,
* CLIENT PAUSE, or whatever), because it may end with some accumulated query
* buffer that needs to be processed ASAP:
*
* 1. When a client is blocked, its readable handler is still active.
* 2. However in this case it only gets data into the query buffer, but the
* query is not parsed or executed once there is enough to proceed as
* usually (because the client is blocked... so we can't execute commands).
* 3. When the client is unblocked, without this function, the client would
* have to write some query in order for the readable handler to finally
* call processQueryBuffer*() on it.
* 4. With this function instead we can put the client in a queue that will
* process it for queries ready to be executed at a safe time.
*/
void queueClientForReprocessing(client *c) {
/* The client may already be into the unblocked list because of a previous
* blocking operation, don't add back it into the list multiple times. */
if (!(c->flags & CLIENT_UNBLOCKED)) {
c->flags |= CLIENT_UNBLOCKED;
listAddNodeTail(server.unblocked_clients,c);
}
}
/* Unblock a client calling the right function depending on the kind /* Unblock a client calling the right function depending on the kind
* of operation the client is blocking for. */ * of operation the client is blocking for. */
void unblockClient(client *c) { void unblockClient(client *c) {
...@@ -152,12 +177,7 @@ void unblockClient(client *c) { ...@@ -152,12 +177,7 @@ void unblockClient(client *c) {
server.blocked_clients_by_type[c->btype]--; server.blocked_clients_by_type[c->btype]--;
c->flags &= ~CLIENT_BLOCKED; c->flags &= ~CLIENT_BLOCKED;
c->btype = BLOCKED_NONE; c->btype = BLOCKED_NONE;
/* The client may already be into the unblocked list because of a previous queueClientForReprocessing(c);
* blocking operation, don't add back it into the list multiple times. */
if (!(c->flags & CLIENT_UNBLOCKED)) {
c->flags |= CLIENT_UNBLOCKED;
listAddNodeTail(server.unblocked_clients,c);
}
} }
/* This function gets called when a blocked client timed out in order to /* This function gets called when a blocked client timed out in order to
...@@ -167,7 +187,7 @@ void replyToBlockedClientTimedOut(client *c) { ...@@ -167,7 +187,7 @@ void replyToBlockedClientTimedOut(client *c) {
if (c->btype == BLOCKED_LIST || if (c->btype == BLOCKED_LIST ||
c->btype == BLOCKED_ZSET || c->btype == BLOCKED_ZSET ||
c->btype == BLOCKED_STREAM) { c->btype == BLOCKED_STREAM) {
addReply(c,shared.nullmultibulk); addReplyNullArray(c);
} else if (c->btype == BLOCKED_WAIT) { } else if (c->btype == BLOCKED_WAIT) {
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset)); addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
} else if (c->btype == BLOCKED_MODULE) { } else if (c->btype == BLOCKED_MODULE) {
...@@ -195,7 +215,7 @@ void disconnectAllBlockedClients(void) { ...@@ -195,7 +215,7 @@ void disconnectAllBlockedClients(void) {
if (c->flags & CLIENT_BLOCKED) { if (c->flags & CLIENT_BLOCKED) {
addReplySds(c,sdsnew( addReplySds(c,sdsnew(
"-UNBLOCKED force unblock from blocking operation, " "-UNBLOCKED force unblock from blocking operation, "
"instance state changed (master -> slave?)\r\n")); "instance state changed (master -> replica?)\r\n"));
unblockClient(c); unblockClient(c);
c->flags |= CLIENT_CLOSE_AFTER_REPLY; c->flags |= CLIENT_CLOSE_AFTER_REPLY;
} }
...@@ -416,8 +436,12 @@ void handleClientsBlockedOnKeys(void) { ...@@ -416,8 +436,12 @@ void handleClientsBlockedOnKeys(void) {
* the name of the stream and the data we * the name of the stream and the data we
* extracted from it. Wrapped in a single-item * extracted from it. Wrapped in a single-item
* array, since we have just one key. */ * array, since we have just one key. */
addReplyMultiBulkLen(receiver,1); if (receiver->resp == 2) {
addReplyMultiBulkLen(receiver,2); addReplyArrayLen(receiver,1);
addReplyArrayLen(receiver,2);
} else {
addReplyMapLen(receiver,1);
}
addReplyBulk(receiver,rl->key); addReplyBulk(receiver,rl->key);
streamPropInfo pi = { streamPropInfo pi = {
......
...@@ -1230,7 +1230,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) { ...@@ -1230,7 +1230,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Clear FAIL state for node %.40s: %s is reachable again.", "Clear FAIL state for node %.40s: %s is reachable again.",
node->name, node->name,
nodeIsSlave(node) ? "slave" : "master without slots"); nodeIsSlave(node) ? "replica" : "master without slots");
node->flags &= ~CLUSTER_NODE_FAIL; node->flags &= ~CLUSTER_NODE_FAIL;
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
} }
...@@ -1589,6 +1589,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc ...@@ -1589,6 +1589,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
} }
} }
/* After updating the slots configuration, don't do any actual change
* in the state of the server if a module disabled Redis Cluster
* keys redirections. */
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
return;
/* If at least one slot was reassigned from a node to another node /* If at least one slot was reassigned from a node to another node
* with a greater configEpoch, it is possible that: * with a greater configEpoch, it is possible that:
* 1) We are a master left without slots. This means that we were * 1) We are a master left without slots. This means that we were
...@@ -2059,7 +2065,7 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2059,7 +2065,7 @@ int clusterProcessPacket(clusterLink *link) {
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT; server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
server.cluster->mf_slave = sender; server.cluster->mf_slave = sender;
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2)); pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
serverLog(LL_WARNING,"Manual failover requested by slave %.40s.", serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
sender->name); sender->name);
} else if (type == CLUSTERMSG_TYPE_UPDATE) { } else if (type == CLUSTERMSG_TYPE_UPDATE) {
clusterNode *n; /* The node the update is about. */ clusterNode *n; /* The node the update is about. */
...@@ -2873,7 +2879,7 @@ void clusterLogCantFailover(int reason) { ...@@ -2873,7 +2879,7 @@ void clusterLogCantFailover(int reason) {
switch(reason) { switch(reason) {
case CLUSTER_CANT_FAILOVER_DATA_AGE: case CLUSTER_CANT_FAILOVER_DATA_AGE:
msg = "Disconnected from master for longer than allowed. " msg = "Disconnected from master for longer than allowed. "
"Please check the 'cluster-slave-validity-factor' configuration " "Please check the 'cluster-replica-validity-factor' configuration "
"option."; "option.";
break; break;
case CLUSTER_CANT_FAILOVER_WAITING_DELAY: case CLUSTER_CANT_FAILOVER_WAITING_DELAY:
...@@ -3054,7 +3060,7 @@ void clusterHandleSlaveFailover(void) { ...@@ -3054,7 +3060,7 @@ void clusterHandleSlaveFailover(void) {
server.cluster->failover_auth_time += added_delay; server.cluster->failover_auth_time += added_delay;
server.cluster->failover_auth_rank = newrank; server.cluster->failover_auth_rank = newrank;
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Slave rank updated to #%d, added %lld milliseconds of delay.", "Replica rank updated to #%d, added %lld milliseconds of delay.",
newrank, added_delay); newrank, added_delay);
} }
} }
...@@ -3210,7 +3216,8 @@ void clusterHandleSlaveMigration(int max_slaves) { ...@@ -3210,7 +3216,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
* the natural slaves of this instance to advertise their switch from * the natural slaves of this instance to advertise their switch from
* the old master to the new one. */ * the old master to the new one. */
if (target && candidate == myself && if (target && candidate == myself &&
(mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY) (mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY &&
!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
{ {
serverLog(LL_WARNING,"Migrating to orphaned master %.40s", serverLog(LL_WARNING,"Migrating to orphaned master %.40s",
target->name); target->name);
...@@ -3321,14 +3328,18 @@ void clusterCron(void) { ...@@ -3321,14 +3328,18 @@ void clusterCron(void) {
int changed = 0; int changed = 0;
if (prev_ip == NULL && curr_ip != NULL) changed = 1; if (prev_ip == NULL && curr_ip != NULL) changed = 1;
if (prev_ip != NULL && curr_ip == NULL) changed = 1; else if (prev_ip != NULL && curr_ip == NULL) changed = 1;
if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1; else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
if (changed) { if (changed) {
if (prev_ip) zfree(prev_ip);
prev_ip = curr_ip; prev_ip = curr_ip;
if (prev_ip) prev_ip = zstrdup(prev_ip);
if (curr_ip) { if (curr_ip) {
/* We always take a copy of the previous IP address, by
* duplicating the string. This way later we can check if
* the address really changed. */
prev_ip = zstrdup(prev_ip);
strncpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN); strncpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN);
myself->ip[NET_IP_STR_LEN-1] = '\0'; myself->ip[NET_IP_STR_LEN-1] = '\0';
} else { } else {
...@@ -3559,6 +3570,7 @@ void clusterCron(void) { ...@@ -3559,6 +3570,7 @@ void clusterCron(void) {
if (nodeIsSlave(myself)) { if (nodeIsSlave(myself)) {
clusterHandleManualFailover(); clusterHandleManualFailover();
if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
clusterHandleSlaveFailover(); clusterHandleSlaveFailover();
/* If there are orphaned slaves, and we are a slave among the masters /* If there are orphaned slaves, and we are a slave among the masters
* with the max number of non-failing slaves, consider migrating to * with the max number of non-failing slaves, consider migrating to
...@@ -3865,6 +3877,11 @@ int verifyClusterConfigWithData(void) { ...@@ -3865,6 +3877,11 @@ int verifyClusterConfigWithData(void) {
int j; int j;
int update_config = 0; int update_config = 0;
/* Return ASAP if a module disabled cluster redirections. In that case
* every master can store keys about every possible hash slot. */
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
return C_OK;
/* If this node is a slave, don't perform the check at all as we /* If this node is a slave, don't perform the check at all as we
* completely depend on the replication stream. */ * completely depend on the replication stream. */
if (nodeIsSlave(myself)) return C_OK; if (nodeIsSlave(myself)) return C_OK;
...@@ -4109,7 +4126,7 @@ void clusterReplyMultiBulkSlots(client *c) { ...@@ -4109,7 +4126,7 @@ void clusterReplyMultiBulkSlots(client *c) {
*/ */
int num_masters = 0; int num_masters = 0;
void *slot_replylen = addDeferredMultiBulkLength(c); void *slot_replylen = addReplyDeferredLen(c);
dictEntry *de; dictEntry *de;
dictIterator *di = dictGetSafeIterator(server.cluster->nodes); dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
...@@ -4129,7 +4146,7 @@ void clusterReplyMultiBulkSlots(client *c) { ...@@ -4129,7 +4146,7 @@ void clusterReplyMultiBulkSlots(client *c) {
} }
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) { if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
int nested_elements = 3; /* slots (2) + master addr (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++; if (bit && j == CLUSTER_SLOTS-1) j++;
...@@ -4145,7 +4162,7 @@ void clusterReplyMultiBulkSlots(client *c) { ...@@ -4145,7 +4162,7 @@ void clusterReplyMultiBulkSlots(client *c) {
start = -1; start = -1;
/* First node reply position is always the master */ /* First node reply position is always the master */
addReplyMultiBulkLen(c, 3); addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip); addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port); addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN); addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
...@@ -4155,19 +4172,19 @@ void clusterReplyMultiBulkSlots(client *c) { ...@@ -4155,19 +4172,19 @@ void clusterReplyMultiBulkSlots(client *c) {
/* This loop is copy/pasted from clusterGenNodeDescription() /* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */ * with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue; if (nodeFailed(node->slaves[i])) continue;
addReplyMultiBulkLen(c, 3); addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip); addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port); addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN); addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++; nested_elements++;
} }
setDeferredMultiBulkLength(c, nested_replylen, nested_elements); setDeferredArrayLen(c, nested_replylen, nested_elements);
num_masters++; num_masters++;
} }
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
setDeferredMultiBulkLength(c, slot_replylen, num_masters); setDeferredArrayLen(c, slot_replylen, num_masters);
} }
void clusterCommand(client *c) { void clusterCommand(client *c) {
...@@ -4183,7 +4200,7 @@ void clusterCommand(client *c) { ...@@ -4183,7 +4200,7 @@ void clusterCommand(client *c) {
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.", "COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.", "COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.", "DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
"FAILOVER [force|takeover] -- Promote current slave node to being a master.", "FAILOVER [force|takeover] -- Promote current replica node to being a master.",
"FORGET <node-id> -- Remove a node from the cluster.", "FORGET <node-id> -- Remove a node from the cluster.",
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.", "GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
"FLUSHSLOTS -- Delete current node own slots information.", "FLUSHSLOTS -- Delete current node own slots information.",
...@@ -4193,11 +4210,11 @@ void clusterCommand(client *c) { ...@@ -4193,11 +4210,11 @@ void clusterCommand(client *c) {
"MYID -- Return the node id.", "MYID -- Return the node id.",
"NODES -- Return cluster configuration seen by node. Output format:", "NODES -- Return cluster configuration seen by node. Output format:",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>", " <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
"REPLICATE <node-id> -- Configure current node as slave to <node-id>.", "REPLICATE <node-id> -- Configure current node as replica to <node-id>.",
"RESET [hard|soft] -- Reset current node (default: soft).", "RESET [hard|soft] -- Reset current node (default: soft).",
"SET-config-epoch <epoch> - Set config epoch of current node.", "SET-config-epoch <epoch> - Set config epoch of current node.",
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.", "SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
"SLAVES <node-id> -- Return <node-id> slaves.", "REPLICAS <node-id> -- Return <node-id> replicas.",
"SLOTS -- Return information about slots range mappings. Each range is made of:", "SLOTS -- Return information about slots range mappings. Each range is made of:",
" start, end, master and replicas IP addresses, ports and ids", " start, end, master and replicas IP addresses, ports and ids",
NULL NULL
...@@ -4531,7 +4548,7 @@ NULL ...@@ -4531,7 +4548,7 @@ NULL
keys = zmalloc(sizeof(robj*)*maxkeys); keys = zmalloc(sizeof(robj*)*maxkeys);
numkeys = getKeysInSlot(slot, keys, maxkeys); numkeys = getKeysInSlot(slot, keys, maxkeys);
addReplyMultiBulkLen(c,numkeys); addReplyArrayLen(c,numkeys);
for (j = 0; j < numkeys; j++) { for (j = 0; j < numkeys; j++) {
addReplyBulk(c,keys[j]); addReplyBulk(c,keys[j]);
decrRefCount(keys[j]); decrRefCount(keys[j]);
...@@ -4574,7 +4591,7 @@ NULL ...@@ -4574,7 +4591,7 @@ NULL
/* Can't replicate a slave. */ /* Can't replicate a slave. */
if (nodeIsSlave(n)) { if (nodeIsSlave(n)) {
addReplyError(c,"I can only replicate a master, not a slave."); addReplyError(c,"I can only replicate a master, not a replica.");
return; return;
} }
...@@ -4593,7 +4610,8 @@ NULL ...@@ -4593,7 +4610,8 @@ NULL
clusterSetMaster(n); clusterSetMaster(n);
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"slaves") && c->argc == 3) { } else if ((!strcasecmp(c->argv[1]->ptr,"slaves") ||
!strcasecmp(c->argv[1]->ptr,"replicas")) && c->argc == 3) {
/* CLUSTER SLAVES <NODE ID> */ /* CLUSTER SLAVES <NODE ID> */
clusterNode *n = clusterLookupNode(c->argv[2]->ptr); clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
int j; int j;
...@@ -4609,7 +4627,7 @@ NULL ...@@ -4609,7 +4627,7 @@ NULL
return; return;
} }
addReplyMultiBulkLen(c,n->numslaves); addReplyArrayLen(c,n->numslaves);
for (j = 0; j < n->numslaves; j++) { for (j = 0; j < n->numslaves; j++) {
sds ni = clusterGenNodeDescription(n->slaves[j]); sds ni = clusterGenNodeDescription(n->slaves[j]);
addReplyBulkCString(c,ni); addReplyBulkCString(c,ni);
...@@ -4647,10 +4665,10 @@ NULL ...@@ -4647,10 +4665,10 @@ NULL
/* Check preconditions. */ /* Check preconditions. */
if (nodeIsMaster(myself)) { if (nodeIsMaster(myself)) {
addReplyError(c,"You should send CLUSTER FAILOVER to a slave"); addReplyError(c,"You should send CLUSTER FAILOVER to a replica");
return; return;
} else if (myself->slaveof == NULL) { } else if (myself->slaveof == NULL) {
addReplyError(c,"I'm a slave but my master is unknown to me"); addReplyError(c,"I'm a replica but my master is unknown to me");
return; return;
} else if (!force && } else if (!force &&
(nodeFailed(myself->slaveof) || (nodeFailed(myself->slaveof) ||
...@@ -4818,7 +4836,7 @@ void dumpCommand(client *c) { ...@@ -4818,7 +4836,7 @@ void dumpCommand(client *c) {
/* Check if the key is here. */ /* Check if the key is here. */
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
addReply(c,shared.nullbulk); addReplyNull(c);
return; return;
} }
...@@ -5146,6 +5164,11 @@ try_again: ...@@ -5146,6 +5164,11 @@ try_again:
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid)); serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
} }
int non_expired = 0; /* Number of keys that we'll find non expired.
Note that serializing large keys may take some time
so certain keys that were found non expired by the
lookupKey() function, may be expired later. */
/* Create RESTORE payload and generate the protocol to call the command. */ /* Create RESTORE payload and generate the protocol to call the command. */
for (j = 0; j < num_keys; j++) { for (j = 0; j < num_keys; j++) {
long long ttl = 0; long long ttl = 0;
...@@ -5153,8 +5176,17 @@ try_again: ...@@ -5153,8 +5176,17 @@ try_again:
if (expireat != -1) { if (expireat != -1) {
ttl = expireat-mstime(); ttl = expireat-mstime();
if (ttl < 0) {
continue;
}
if (ttl < 1) ttl = 1; if (ttl < 1) ttl = 1;
} }
/* Relocate valid (non expired) keys into the array in successive
* positions to remove holes created by the keys that were present
* in the first lookup but are now expired after the second lookup. */
kv[non_expired++] = kv[j];
serverAssertWithInfo(c,NULL, serverAssertWithInfo(c,NULL,
rioWriteBulkCount(&cmd,'*',replace ? 5 : 4)); rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
...@@ -5182,6 +5214,9 @@ try_again: ...@@ -5182,6 +5214,9 @@ try_again:
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7)); serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7));
} }
/* Fix the actual number of keys we are migrating. */
num_keys = non_expired;
/* Transfer the query to the other node in 64K chunks. */ /* Transfer the query to the other node in 64K chunks. */
errno = 0; errno = 0;
{ {
...@@ -5217,6 +5252,10 @@ try_again: ...@@ -5217,6 +5252,10 @@ try_again:
int socket_error = 0; int socket_error = 0;
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */ int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
/* Allocate the new argument vector that will replace the current command,
* to propagate the MIGRATE as a DEL command (if no COPY option was given).
* We allocate num_keys+1 because the additional argument is for "DEL"
* command name itself. */
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1)); if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) { for (j = 0; j < num_keys; j++) {
...@@ -5417,9 +5456,17 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in ...@@ -5417,9 +5456,17 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
multiCmd mc; multiCmd mc;
int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0; int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
/* Allow any key to be set if a module disabled cluster redirections. */
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
return myself;
/* Set error code optimistically for the base case. */ /* Set error code optimistically for the base case. */
if (error_code) *error_code = CLUSTER_REDIR_NONE; if (error_code) *error_code = CLUSTER_REDIR_NONE;
/* Modules can turn off Redis Cluster redirection: this is useful
* when writing a module that implements a completely different
* distributed system. */
/* We handle all the cases as if they were EXEC commands, so we have /* We handle all the cases as if they were EXEC commands, so we have
* a common code path for everything */ * a common code path for everything */
if (cmd->proc == execCommand) { if (cmd->proc == execCommand) {
......
...@@ -100,6 +100,13 @@ typedef struct clusterLink { ...@@ -100,6 +100,13 @@ typedef struct clusterLink {
#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */ #define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
#define CLUSTERMSG_TYPE_COUNT 10 /* Total number of message types. */ #define CLUSTERMSG_TYPE_COUNT 10 /* Total number of message types. */
/* Flags that a module can set in order to prevent certain Redis Cluster
* features to be enabled. Useful when implementing a different distributed
* system on top of Redis Cluster message bus, using modules. */
#define CLUSTER_MODULE_FLAG_NONE 0
#define CLUSTER_MODULE_FLAG_NO_FAILOVER (1<<1)
#define CLUSTER_MODULE_FLAG_NO_REDIRECTION (1<<2)
/* This structure represent elements of node->fail_reports. */ /* This structure represent elements of node->fail_reports. */
typedef struct clusterNodeFailReport { typedef struct clusterNodeFailReport {
struct clusterNode *node; /* Node reporting the failure condition. */ struct clusterNode *node; /* Node reporting the failure condition. */
......
...@@ -120,7 +120,7 @@ const char *configEnumGetName(configEnum *ce, int val) { ...@@ -120,7 +120,7 @@ const char *configEnumGetName(configEnum *ce, int val) {
return NULL; return NULL;
} }
/* Wrapper for configEnumGetName() returning "unknown" insetad of NULL if /* Wrapper for configEnumGetName() returning "unknown" instead of NULL if
* there is no match. */ * there is no match. */
const char *configEnumGetNameOrUnknown(configEnum *ce, int val) { const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
const char *name = configEnumGetName(ce,val); const char *name = configEnumGetName(ce,val);
...@@ -216,6 +216,10 @@ void loadServerConfigFromString(char *config) { ...@@ -216,6 +216,10 @@ void loadServerConfigFromString(char *config) {
if ((server.protected_mode = yesnotoi(argv[1])) == -1) { if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"gopher-enabled") && argc == 2) {
if ((server.gopher_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"port") && argc == 2) { } else if (!strcasecmp(argv[0],"port") && argc == 2) {
server.port = atoi(argv[1]); server.port = atoi(argv[1]);
if (server.port < 0 || server.port > 65535) { if (server.port < 0 || server.port > 65535) {
...@@ -283,6 +287,9 @@ void loadServerConfigFromString(char *config) { ...@@ -283,6 +287,9 @@ void loadServerConfigFromString(char *config) {
} }
fclose(logfp); fclose(logfp);
} }
} else if (!strcasecmp(argv[0],"aclfile") && argc == 2) {
zfree(server.acl_filename);
server.acl_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) { } else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
if ((server.always_show_logo = yesnotoi(argv[1])) == -1) { if ((server.always_show_logo = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
...@@ -344,15 +351,19 @@ void loadServerConfigFromString(char *config) { ...@@ -344,15 +351,19 @@ void loadServerConfigFromString(char *config) {
err = "lfu-decay-time must be 0 or greater"; err = "lfu-decay-time must be 0 or greater";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) { } else if ((!strcasecmp(argv[0],"slaveof") ||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
slaveof_linenum = linenum; slaveof_linenum = linenum;
server.masterhost = sdsnew(argv[1]); server.masterhost = sdsnew(argv[1]);
server.masterport = atoi(argv[2]); server.masterport = atoi(argv[2]);
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
} else if (!strcasecmp(argv[0],"repl-ping-slave-period") && argc == 2) { } else if ((!strcasecmp(argv[0],"repl-ping-slave-period") ||
!strcasecmp(argv[0],"repl-ping-replica-period")) &&
argc == 2)
{
server.repl_ping_slave_period = atoi(argv[1]); server.repl_ping_slave_period = atoi(argv[1]);
if (server.repl_ping_slave_period <= 0) { if (server.repl_ping_slave_period <= 0) {
err = "repl-ping-slave-period must be 1 or greater"; err = "repl-ping-replica-period must be 1 or greater";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) { } else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) {
...@@ -388,17 +399,33 @@ void loadServerConfigFromString(char *config) { ...@@ -388,17 +399,33 @@ void loadServerConfigFromString(char *config) {
err = "repl-backlog-ttl can't be negative "; err = "repl-backlog-ttl can't be negative ";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"masteruser") && argc == 2) {
zfree(server.masteruser);
server.masteruser = argv[1][0] ? zstrdup(argv[1]) : NULL;
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) { } else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
zfree(server.masterauth); zfree(server.masterauth);
server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL; server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL;
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) { } else if ((!strcasecmp(argv[0],"slave-serve-stale-data") ||
!strcasecmp(argv[0],"replica-serve-stale-data"))
&& argc == 2)
{
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) { if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"slave-read-only") && argc == 2) { } else if ((!strcasecmp(argv[0],"slave-read-only") ||
!strcasecmp(argv[0],"replica-read-only"))
&& argc == 2)
{
if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) { if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if ((!strcasecmp(argv[0],"slave-ignore-maxmemory") ||
!strcasecmp(argv[0],"replica-ignore-maxmemory"))
&& argc == 2)
{
if ((server.repl_slave_ignore_maxmemory = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) { } else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) { if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
...@@ -423,7 +450,9 @@ void loadServerConfigFromString(char *config) { ...@@ -423,7 +450,9 @@ void loadServerConfigFromString(char *config) {
if ((server.lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) { if ((server.lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"slave-lazy-flush") && argc == 2) { } else if ((!strcasecmp(argv[0],"slave-lazy-flush") ||
!strcasecmp(argv[0],"replica-lazy-flush")) && argc == 2)
{
if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) { if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
...@@ -440,10 +469,14 @@ void loadServerConfigFromString(char *config) { ...@@ -440,10 +469,14 @@ void loadServerConfigFromString(char *config) {
if ((server.daemonize = yesnotoi(argv[1])) == -1) { if ((server.daemonize = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"dynamic-hz") && argc == 2) {
if ((server.dynamic_hz = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"hz") && argc == 2) { } else if (!strcasecmp(argv[0],"hz") && argc == 2) {
server.hz = atoi(argv[1]); server.config_hz = atoi(argv[1]);
if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ; if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ; if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) { } else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
int yes; int yes;
...@@ -508,7 +541,12 @@ void loadServerConfigFromString(char *config) { ...@@ -508,7 +541,12 @@ void loadServerConfigFromString(char *config) {
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN"; err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
goto loaderr; 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) { } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
zfree(server.pidfile); zfree(server.pidfile);
server.pidfile = zstrdup(argv[1]); server.pidfile = zstrdup(argv[1]);
...@@ -651,15 +689,17 @@ void loadServerConfigFromString(char *config) { ...@@ -651,15 +689,17 @@ void loadServerConfigFromString(char *config) {
err = "cluster migration barrier must zero or positive"; err = "cluster migration barrier must zero or positive";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"cluster-slave-validity-factor") } else if ((!strcasecmp(argv[0],"cluster-slave-validity-factor") ||
!strcasecmp(argv[0],"cluster-replica-validity-factor"))
&& argc == 2) && argc == 2)
{ {
server.cluster_slave_validity_factor = atoi(argv[1]); server.cluster_slave_validity_factor = atoi(argv[1]);
if (server.cluster_slave_validity_factor < 0) { if (server.cluster_slave_validity_factor < 0) {
err = "cluster slave validity factor must be zero or positive"; err = "cluster replica validity factor must be zero or positive";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"cluster-slave-no-failover") && } else if ((!strcasecmp(argv[0],"cluster-slave-no-failover") ||
!strcasecmp(argv[0],"cluster-replica-no-failover")) &&
argc == 2) argc == 2)
{ {
server.cluster_slave_no_failover = yesnotoi(argv[1]); server.cluster_slave_no_failover = yesnotoi(argv[1]);
...@@ -669,6 +709,8 @@ void loadServerConfigFromString(char *config) { ...@@ -669,6 +709,8 @@ void loadServerConfigFromString(char *config) {
} }
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) { } else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
server.lua_time_limit = strtoll(argv[1],NULL,10); server.lua_time_limit = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
server.lua_always_replicate_commands = yesnotoi(argv[1]);
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") && } else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
argc == 2) argc == 2)
{ {
...@@ -710,27 +752,37 @@ void loadServerConfigFromString(char *config) { ...@@ -710,27 +752,37 @@ void loadServerConfigFromString(char *config) {
if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) { if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) { } else if ((!strcasecmp(argv[0],"slave-priority") ||
!strcasecmp(argv[0],"replica-priority")) && argc == 2)
{
server.slave_priority = atoi(argv[1]); server.slave_priority = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"slave-announce-ip") && argc == 2) { } else if ((!strcasecmp(argv[0],"slave-announce-ip") ||
!strcasecmp(argv[0],"replica-announce-ip")) && argc == 2)
{
zfree(server.slave_announce_ip); zfree(server.slave_announce_ip);
server.slave_announce_ip = zstrdup(argv[1]); server.slave_announce_ip = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"slave-announce-port") && argc == 2) { } else if ((!strcasecmp(argv[0],"slave-announce-port") ||
!strcasecmp(argv[0],"replica-announce-port")) && argc == 2)
{
server.slave_announce_port = atoi(argv[1]); server.slave_announce_port = atoi(argv[1]);
if (server.slave_announce_port < 0 || if (server.slave_announce_port < 0 ||
server.slave_announce_port > 65535) server.slave_announce_port > 65535)
{ {
err = "Invalid port"; goto loaderr; err = "Invalid port"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) { } else if ((!strcasecmp(argv[0],"min-slaves-to-write") ||
!strcasecmp(argv[0],"min-replicas-to-write")) && argc == 2)
{
server.repl_min_slaves_to_write = atoi(argv[1]); server.repl_min_slaves_to_write = atoi(argv[1]);
if (server.repl_min_slaves_to_write < 0) { if (server.repl_min_slaves_to_write < 0) {
err = "Invalid value for min-slaves-to-write."; goto loaderr; err = "Invalid value for min-replicas-to-write."; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) { } else if ((!strcasecmp(argv[0],"min-slaves-max-lag") ||
!strcasecmp(argv[0],"min-replicas-max-lag")) && argc == 2)
{
server.repl_min_slaves_max_lag = atoi(argv[1]); server.repl_min_slaves_max_lag = atoi(argv[1]);
if (server.repl_min_slaves_max_lag < 0) { if (server.repl_min_slaves_max_lag < 0) {
err = "Invalid value for min-slaves-max-lag."; goto loaderr; err = "Invalid value for min-replicas-max-lag."; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) { } else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
int flags = keyspaceEventsStringToFlags(argv[1]); int flags = keyspaceEventsStringToFlags(argv[1]);
...@@ -749,6 +801,16 @@ void loadServerConfigFromString(char *config) { ...@@ -749,6 +801,16 @@ void loadServerConfigFromString(char *config) {
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'"; "Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"user") && argc >= 2) {
int argc_err;
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
char buf[1024];
char *errmsg = ACLSetUserStringError();
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
argv[argc_err],errmsg);
err = buf;
goto loaderr;
}
} else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) { } else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
queueLoadModule(argv[1],&argv[2],argc-2); queueLoadModule(argv[1],&argv[2],argc-2);
} else if (!strcasecmp(argv[0],"sentinel")) { } else if (!strcasecmp(argv[0],"sentinel")) {
...@@ -772,7 +834,7 @@ void loadServerConfigFromString(char *config) { ...@@ -772,7 +834,7 @@ void loadServerConfigFromString(char *config) {
if (server.cluster_enabled && server.masterhost) { if (server.cluster_enabled && server.masterhost) {
linenum = slaveof_linenum; linenum = slaveof_linenum;
i = linenum-1; i = linenum-1;
err = "slaveof directive not allowed in cluster mode"; err = "replicaof directive not allowed in cluster mode";
goto loaderr; goto loaderr;
} }
...@@ -856,6 +918,10 @@ void loadServerConfig(char *filename, char *options) { ...@@ -856,6 +918,10 @@ void loadServerConfig(char *filename, char *options) {
#define config_set_special_field(_name) \ #define config_set_special_field(_name) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { } else if (!strcasecmp(c->argv[2]->ptr,_name)) {
#define config_set_special_field_with_alias(_name1,_name2) \
} else if (!strcasecmp(c->argv[2]->ptr,_name1) || \
!strcasecmp(c->argv[2]->ptr,_name2)) {
#define config_set_else } else #define config_set_else } else
void configSetCommand(client *c) { void configSetCommand(client *c) {
...@@ -878,8 +944,15 @@ void configSetCommand(client *c) { ...@@ -878,8 +944,15 @@ void configSetCommand(client *c) {
server.rdb_filename = zstrdup(o->ptr); server.rdb_filename = zstrdup(o->ptr);
} config_set_special_field("requirepass") { } config_set_special_field("requirepass") {
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt; if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
zfree(server.requirepass); /* The old "requirepass" directive just translates to setting
server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL; * a password to the default user. */
ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
} config_set_special_field("masteruser") {
zfree(server.masteruser);
server.masteruser = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} config_set_special_field("masterauth") { } config_set_special_field("masterauth") {
zfree(server.masterauth); zfree(server.masterauth);
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL; server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
...@@ -1015,7 +1088,9 @@ void configSetCommand(client *c) { ...@@ -1015,7 +1088,9 @@ void configSetCommand(client *c) {
if (flags == -1) goto badfmt; if (flags == -1) goto badfmt;
server.notify_keyspace_events = flags; server.notify_keyspace_events = flags;
} config_set_special_field("slave-announce-ip") { } config_set_special_field_with_alias("slave-announce-ip",
"replica-announce-ip")
{
zfree(server.slave_announce_ip); zfree(server.slave_announce_ip);
server.slave_announce_ip = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL; server.slave_announce_ip = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
...@@ -1031,6 +1106,8 @@ void configSetCommand(client *c) { ...@@ -1031,6 +1106,8 @@ void configSetCommand(client *c) {
"cluster-require-full-coverage",server.cluster_require_full_coverage) { "cluster-require-full-coverage",server.cluster_require_full_coverage) {
} config_set_bool_field( } config_set_bool_field(
"cluster-slave-no-failover",server.cluster_slave_no_failover) { "cluster-slave-no-failover",server.cluster_slave_no_failover) {
} config_set_bool_field(
"cluster-replica-no-failover",server.cluster_slave_no_failover) {
} config_set_bool_field( } config_set_bool_field(
"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) { "aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
} config_set_bool_field( } config_set_bool_field(
...@@ -1041,8 +1118,16 @@ void configSetCommand(client *c) { ...@@ -1041,8 +1118,16 @@ void configSetCommand(client *c) {
"aof-use-rdb-preamble",server.aof_use_rdb_preamble) { "aof-use-rdb-preamble",server.aof_use_rdb_preamble) {
} config_set_bool_field( } config_set_bool_field(
"slave-serve-stale-data",server.repl_serve_stale_data) { "slave-serve-stale-data",server.repl_serve_stale_data) {
} config_set_bool_field(
"replica-serve-stale-data",server.repl_serve_stale_data) {
} config_set_bool_field( } config_set_bool_field(
"slave-read-only",server.repl_slave_ro) { "slave-read-only",server.repl_slave_ro) {
} config_set_bool_field(
"replica-read-only",server.repl_slave_ro) {
} config_set_bool_field(
"slave-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
} config_set_bool_field(
"replica-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
} config_set_bool_field( } config_set_bool_field(
"activerehashing",server.activerehashing) { "activerehashing",server.activerehashing) {
} config_set_bool_field( } config_set_bool_field(
...@@ -1060,6 +1145,8 @@ void configSetCommand(client *c) { ...@@ -1060,6 +1145,8 @@ void configSetCommand(client *c) {
#endif #endif
} config_set_bool_field( } config_set_bool_field(
"protected-mode",server.protected_mode) { "protected-mode",server.protected_mode) {
} config_set_bool_field(
"gopher-enabled",server.gopher_enabled) {
} config_set_bool_field( } config_set_bool_field(
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) { "stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
} config_set_bool_field( } config_set_bool_field(
...@@ -1070,8 +1157,12 @@ void configSetCommand(client *c) { ...@@ -1070,8 +1157,12 @@ void configSetCommand(client *c) {
"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del) { "lazyfree-lazy-server-del",server.lazyfree_lazy_server_del) {
} config_set_bool_field( } config_set_bool_field(
"slave-lazy-flush",server.repl_slave_lazy_flush) { "slave-lazy-flush",server.repl_slave_lazy_flush) {
} config_set_bool_field(
"replica-lazy-flush",server.repl_slave_lazy_flush) {
} config_set_bool_field( } config_set_bool_field(
"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) { "no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
} config_set_bool_field(
"dynamic-hz",server.dynamic_hz) {
/* Numerical fields. /* Numerical fields.
* config_set_numerical_field(name,var,min,max) */ * config_set_numerical_field(name,var,min,max) */
...@@ -1131,6 +1222,8 @@ void configSetCommand(client *c) { ...@@ -1131,6 +1222,8 @@ void configSetCommand(client *c) {
"latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){ "latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
} config_set_numerical_field( } config_set_numerical_field(
"repl-ping-slave-period",server.repl_ping_slave_period,1,INT_MAX) { "repl-ping-slave-period",server.repl_ping_slave_period,1,INT_MAX) {
} config_set_numerical_field(
"repl-ping-replica-period",server.repl_ping_slave_period,1,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"repl-timeout",server.repl_timeout,1,INT_MAX) { "repl-timeout",server.repl_timeout,1,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
...@@ -1139,14 +1232,24 @@ void configSetCommand(client *c) { ...@@ -1139,14 +1232,24 @@ void configSetCommand(client *c) {
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,INT_MAX) { "repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"slave-priority",server.slave_priority,0,INT_MAX) { "slave-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field(
"replica-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"slave-announce-port",server.slave_announce_port,0,65535) { "slave-announce-port",server.slave_announce_port,0,65535) {
} config_set_numerical_field(
"replica-announce-port",server.slave_announce_port,0,65535) {
} config_set_numerical_field( } config_set_numerical_field(
"min-slaves-to-write",server.repl_min_slaves_to_write,0,INT_MAX) { "min-slaves-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
refreshGoodSlavesCount(); refreshGoodSlavesCount();
} config_set_numerical_field(
"min-replicas-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
refreshGoodSlavesCount();
} config_set_numerical_field( } config_set_numerical_field(
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) { "min-slaves-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
refreshGoodSlavesCount(); refreshGoodSlavesCount();
} config_set_numerical_field(
"min-replicas-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
refreshGoodSlavesCount();
} config_set_numerical_field( } config_set_numerical_field(
"cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) { "cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
...@@ -1158,11 +1261,13 @@ void configSetCommand(client *c) { ...@@ -1158,11 +1261,13 @@ void configSetCommand(client *c) {
} config_set_numerical_field( } config_set_numerical_field(
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) { "cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"hz",server.hz,0,INT_MAX) { "cluster-replica-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
} config_set_numerical_field(
"hz",server.config_hz,0,INT_MAX) {
/* Hz is more an hint from the user, so we accept values out of range /* Hz is more an hint from the user, so we accept values out of range
* but cap them to reasonable values. */ * but cap them to reasonable values. */
if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ; if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ; if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
} config_set_numerical_field( } config_set_numerical_field(
"watchdog-period",ll,0,INT_MAX) { "watchdog-period",ll,0,INT_MAX) {
if (ll) if (ll)
...@@ -1175,9 +1280,9 @@ void configSetCommand(client *c) { ...@@ -1175,9 +1280,9 @@ void configSetCommand(client *c) {
} config_set_memory_field("maxmemory",server.maxmemory) { } config_set_memory_field("maxmemory",server.maxmemory) {
if (server.maxmemory) { if (server.maxmemory) {
if (server.maxmemory < zmalloc_used_memory()) { if (server.maxmemory < zmalloc_used_memory()) {
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy."); serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
} }
freeMemoryIfNeeded(); freeMemoryIfNeededAndSafe();
} }
} config_set_memory_field( } config_set_memory_field(
"proto-max-bulk-len",server.proto_max_bulk_len) { "proto-max-bulk-len",server.proto_max_bulk_len) {
...@@ -1253,7 +1358,7 @@ badfmt: /* Bad format errors */ ...@@ -1253,7 +1358,7 @@ badfmt: /* Bad format errors */
void configGetCommand(client *c) { void configGetCommand(client *c) {
robj *o = c->argv[2]; robj *o = c->argv[2];
void *replylen = addDeferredMultiBulkLength(c); void *replylen = addReplyDeferredLen(c);
char *pattern = o->ptr; char *pattern = o->ptr;
char buf[128]; char buf[128];
int matches = 0; int matches = 0;
...@@ -1261,13 +1366,15 @@ void configGetCommand(client *c) { ...@@ -1261,13 +1366,15 @@ void configGetCommand(client *c) {
/* String values */ /* String values */
config_get_string_field("dbfilename",server.rdb_filename); config_get_string_field("dbfilename",server.rdb_filename);
config_get_string_field("requirepass",server.requirepass); config_get_string_field("masteruser",server.masteruser);
config_get_string_field("masterauth",server.masterauth); config_get_string_field("masterauth",server.masterauth);
config_get_string_field("cluster-announce-ip",server.cluster_announce_ip); config_get_string_field("cluster-announce-ip",server.cluster_announce_ip);
config_get_string_field("unixsocket",server.unixsocket); config_get_string_field("unixsocket",server.unixsocket);
config_get_string_field("logfile",server.logfile); config_get_string_field("logfile",server.logfile);
config_get_string_field("aclfile",server.acl_filename);
config_get_string_field("pidfile",server.pidfile); config_get_string_field("pidfile",server.pidfile);
config_get_string_field("slave-announce-ip",server.slave_announce_ip); config_get_string_field("slave-announce-ip",server.slave_announce_ip);
config_get_string_field("replica-announce-ip",server.slave_announce_ip);
/* Numerical values */ /* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory); config_get_numerical_field("maxmemory",server.maxmemory);
...@@ -1320,19 +1427,25 @@ void configGetCommand(client *c) { ...@@ -1320,19 +1427,25 @@ void configGetCommand(client *c) {
config_get_numerical_field("tcp-backlog",server.tcp_backlog); config_get_numerical_field("tcp-backlog",server.tcp_backlog);
config_get_numerical_field("databases",server.dbnum); config_get_numerical_field("databases",server.dbnum);
config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period); config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
config_get_numerical_field("repl-ping-replica-period",server.repl_ping_slave_period);
config_get_numerical_field("repl-timeout",server.repl_timeout); config_get_numerical_field("repl-timeout",server.repl_timeout);
config_get_numerical_field("repl-backlog-size",server.repl_backlog_size); config_get_numerical_field("repl-backlog-size",server.repl_backlog_size);
config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit); config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit);
config_get_numerical_field("maxclients",server.maxclients); config_get_numerical_field("maxclients",server.maxclients);
config_get_numerical_field("watchdog-period",server.watchdog_period); config_get_numerical_field("watchdog-period",server.watchdog_period);
config_get_numerical_field("slave-priority",server.slave_priority); config_get_numerical_field("slave-priority",server.slave_priority);
config_get_numerical_field("replica-priority",server.slave_priority);
config_get_numerical_field("slave-announce-port",server.slave_announce_port); config_get_numerical_field("slave-announce-port",server.slave_announce_port);
config_get_numerical_field("replica-announce-port",server.slave_announce_port);
config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write); config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write);
config_get_numerical_field("min-replicas-to-write",server.repl_min_slaves_to_write);
config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag); config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag);
config_get_numerical_field("hz",server.hz); config_get_numerical_field("min-replicas-max-lag",server.repl_min_slaves_max_lag);
config_get_numerical_field("hz",server.config_hz);
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout); config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier); config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor); config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay); config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive); config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
...@@ -1341,12 +1454,22 @@ void configGetCommand(client *c) { ...@@ -1341,12 +1454,22 @@ void configGetCommand(client *c) {
server.cluster_require_full_coverage); server.cluster_require_full_coverage);
config_get_bool_field("cluster-slave-no-failover", config_get_bool_field("cluster-slave-no-failover",
server.cluster_slave_no_failover); server.cluster_slave_no_failover);
config_get_bool_field("cluster-replica-no-failover",
server.cluster_slave_no_failover);
config_get_bool_field("no-appendfsync-on-rewrite", config_get_bool_field("no-appendfsync-on-rewrite",
server.aof_no_fsync_on_rewrite); server.aof_no_fsync_on_rewrite);
config_get_bool_field("slave-serve-stale-data", config_get_bool_field("slave-serve-stale-data",
server.repl_serve_stale_data); server.repl_serve_stale_data);
config_get_bool_field("replica-serve-stale-data",
server.repl_serve_stale_data);
config_get_bool_field("slave-read-only", config_get_bool_field("slave-read-only",
server.repl_slave_ro); server.repl_slave_ro);
config_get_bool_field("replica-read-only",
server.repl_slave_ro);
config_get_bool_field("slave-ignore-maxmemory",
server.repl_slave_ignore_maxmemory);
config_get_bool_field("replica-ignore-maxmemory",
server.repl_slave_ignore_maxmemory);
config_get_bool_field("stop-writes-on-bgsave-error", config_get_bool_field("stop-writes-on-bgsave-error",
server.stop_writes_on_bgsave_err); server.stop_writes_on_bgsave_err);
config_get_bool_field("daemonize", server.daemonize); config_get_bool_field("daemonize", server.daemonize);
...@@ -1355,6 +1478,7 @@ void configGetCommand(client *c) { ...@@ -1355,6 +1478,7 @@ void configGetCommand(client *c) {
config_get_bool_field("activerehashing", server.activerehashing); config_get_bool_field("activerehashing", server.activerehashing);
config_get_bool_field("activedefrag", server.active_defrag_enabled); config_get_bool_field("activedefrag", server.active_defrag_enabled);
config_get_bool_field("protected-mode", server.protected_mode); config_get_bool_field("protected-mode", server.protected_mode);
config_get_bool_field("gopher-enabled", server.gopher_enabled);
config_get_bool_field("repl-disable-tcp-nodelay", config_get_bool_field("repl-disable-tcp-nodelay",
server.repl_disable_tcp_nodelay); server.repl_disable_tcp_nodelay);
config_get_bool_field("repl-diskless-sync", config_get_bool_field("repl-diskless-sync",
...@@ -1375,6 +1499,10 @@ void configGetCommand(client *c) { ...@@ -1375,6 +1499,10 @@ void configGetCommand(client *c) {
server.lazyfree_lazy_server_del); server.lazyfree_lazy_server_del);
config_get_bool_field("slave-lazy-flush", config_get_bool_field("slave-lazy-flush",
server.repl_slave_lazy_flush); server.repl_slave_lazy_flush);
config_get_bool_field("replica-lazy-flush",
server.repl_slave_lazy_flush);
config_get_bool_field("dynamic-hz",
server.dynamic_hz);
/* Enum values */ /* Enum values */
config_get_enum_field("maxmemory-policy", config_get_enum_field("maxmemory-policy",
...@@ -1446,10 +1574,14 @@ void configGetCommand(client *c) { ...@@ -1446,10 +1574,14 @@ void configGetCommand(client *c) {
addReplyBulkCString(c,buf); addReplyBulkCString(c,buf);
matches++; matches++;
} }
if (stringmatch(pattern,"slaveof",1)) { if (stringmatch(pattern,"slaveof",1) ||
stringmatch(pattern,"replicaof",1))
{
char *optname = stringmatch(pattern,"slaveof",1) ?
"slaveof" : "replicaof";
char buf[256]; char buf[256];
addReplyBulkCString(c,"slaveof"); addReplyBulkCString(c,optname);
if (server.masterhost) if (server.masterhost)
snprintf(buf,sizeof(buf),"%s %d", snprintf(buf,sizeof(buf),"%s %d",
server.masterhost, server.masterport); server.masterhost, server.masterport);
...@@ -1475,7 +1607,17 @@ void configGetCommand(client *c) { ...@@ -1475,7 +1607,17 @@ void configGetCommand(client *c) {
sdsfree(aux); sdsfree(aux);
matches++; matches++;
} }
setDeferredMultiBulkLength(c,replylen,matches*2); if (stringmatch(pattern,"requirepass",1)) {
addReplyBulkCString(c,"requirepass");
sds password = ACLDefaultUserFirstPassword();
if (password) {
addReplyBulkCBuffer(c,password,sdslen(password));
} else {
addReplyBulkCString(c,"");
}
matches++;
}
setDeferredMapLen(c,replylen,matches);
} }
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
...@@ -1605,8 +1747,20 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) { ...@@ -1605,8 +1747,20 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
/* Now we populate the state according to the content of this line. /* Now we populate the state according to the content of this line.
* Append the line and populate the option -> line numbers map. */ * Append the line and populate the option -> line numbers map. */
rewriteConfigAppendLine(state,line); rewriteConfigAppendLine(state,line);
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
/* Translate options using the word "slave" to the corresponding name
* "replica", before adding such option to the config name -> lines
* mapping. */
char *p = strstr(argv[0],"slave");
if (p) {
sds alt = sdsempty();
alt = sdscatlen(alt,argv[0],p-argv[0]);;
alt = sdscatlen(alt,"replica",7);
alt = sdscatlen(alt,p+5,strlen(p+5));
sdsfree(argv[0]);
argv[0] = alt;
}
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
sdsfreesplitres(argv,argc); sdsfreesplitres(argv,argc);
} }
fclose(fp); fclose(fp);
...@@ -1781,6 +1935,38 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) { ...@@ -1781,6 +1935,38 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
rewriteConfigMarkAsProcessed(state,"save"); rewriteConfigMarkAsProcessed(state,"save");
} }
/* Rewrite the user option. */
void rewriteConfigUserOption(struct rewriteConfigState *state) {
/* If there is a user file defined we just mark this configuration
* directive as processed, so that all the lines containing users
* inside the config file gets discarded. */
if (server.acl_filename[0] != '\0') {
rewriteConfigMarkAsProcessed(state,"user");
return;
}
/* Otherwise scan the list of users and rewrite every line. Note that
* in case the list here is empty, the effect will just be to comment
* all the users directive inside the config file. */
raxIterator ri;
raxStart(&ri,Users);
raxSeek(&ri,"^",NULL,0);
while(raxNext(&ri)) {
user *u = ri.data;
sds line = sdsnew("user ");
line = sdscatsds(line,u->name);
line = sdscatlen(line," ",1);
sds descr = ACLDescribeUser(u);
line = sdscatsds(line,descr);
sdsfree(descr);
rewriteConfigRewriteLine(state,"user",line,1);
}
raxStop(&ri);
/* Mark "user" as processed in case there are no defined users. */
rewriteConfigMarkAsProcessed(state,"user");
}
/* Rewrite the dir option, always using absolute paths.*/ /* Rewrite the dir option, always using absolute paths.*/
void rewriteConfigDirOption(struct rewriteConfigState *state) { void rewriteConfigDirOption(struct rewriteConfigState *state) {
char cwd[1024]; char cwd[1024];
...@@ -1793,15 +1979,14 @@ void rewriteConfigDirOption(struct rewriteConfigState *state) { ...@@ -1793,15 +1979,14 @@ void rewriteConfigDirOption(struct rewriteConfigState *state) {
} }
/* Rewrite the slaveof option. */ /* Rewrite the slaveof option. */
void rewriteConfigSlaveofOption(struct rewriteConfigState *state) { void rewriteConfigSlaveofOption(struct rewriteConfigState *state, char *option) {
char *option = "slaveof";
sds line; sds line;
/* If this is a master, we want all the slaveof config options /* If this is a master, we want all the slaveof config options
* in the file to be removed. Note that if this is a cluster instance * in the file to be removed. Note that if this is a cluster instance
* we don't want a slaveof directive inside redis.conf. */ * we don't want a slaveof directive inside redis.conf. */
if (server.cluster_enabled || server.masterhost == NULL) { if (server.cluster_enabled || server.masterhost == NULL) {
rewriteConfigMarkAsProcessed(state,"slaveof"); rewriteConfigMarkAsProcessed(state,option);
return; return;
} }
line = sdscatprintf(sdsempty(),"%s %s %d", option, line = sdscatprintf(sdsempty(),"%s %s %d", option,
...@@ -1843,8 +2028,10 @@ void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state ...@@ -1843,8 +2028,10 @@ void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state
rewriteConfigFormatMemory(soft,sizeof(soft), rewriteConfigFormatMemory(soft,sizeof(soft),
server.client_obuf_limits[j].soft_limit_bytes); server.client_obuf_limits[j].soft_limit_bytes);
char *typename = getClientTypeName(j);
if (!strcmp(typename,"slave")) typename = "replica";
line = sdscatprintf(sdsempty(),"%s %s %s %s %ld", line = sdscatprintf(sdsempty(),"%s %s %s %s %ld",
option, getClientTypeName(j), hard, soft, option, typename, hard, soft,
(long) server.client_obuf_limits[j].soft_limit_seconds); (long) server.client_obuf_limits[j].soft_limit_seconds);
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
...@@ -1872,6 +2059,26 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) { ...@@ -1872,6 +2059,26 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
/* Rewrite the requirepass option. */
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
int force = 1;
sds line;
sds password = ACLDefaultUserFirstPassword();
/* If there is no password set, we don't want the requirepass option
* to be present in the configuration at all. */
if (password == NULL) {
rewriteConfigMarkAsProcessed(state,option);
return;
}
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatsds(line, password);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Glue together the configuration lines in the current configuration /* Glue together the configuration lines in the current configuration
* rewrite state into a single string, stripping multiple empty lines. */ * rewrite state into a single string, stripping multiple empty lines. */
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) { sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
...@@ -2022,36 +2229,40 @@ int rewriteConfig(char *path) { ...@@ -2022,36 +2229,40 @@ int rewriteConfig(char *path) {
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM); rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM);
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,CONFIG_DEFAULT_CLIENT_TIMEOUT); rewriteConfigNumericalOption(state,"timeout",server.maxidletime,CONFIG_DEFAULT_CLIENT_TIMEOUT);
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,CONFIG_DEFAULT_TCP_KEEPALIVE); rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,CONFIG_DEFAULT_TCP_KEEPALIVE);
rewriteConfigNumericalOption(state,"slave-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT); rewriteConfigNumericalOption(state,"replica-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,CONFIG_DEFAULT_VERBOSITY); rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,CONFIG_DEFAULT_VERBOSITY);
rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE); rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE);
rewriteConfigStringOption(state,"aclfile",server.acl_filename,CONFIG_DEFAULT_ACL_FILENAME);
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,CONFIG_DEFAULT_SYSLOG_ENABLED); rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,CONFIG_DEFAULT_SYSLOG_ENABLED);
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,CONFIG_DEFAULT_SYSLOG_IDENT); rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,CONFIG_DEFAULT_SYSLOG_IDENT);
rewriteConfigSyslogfacilityOption(state); rewriteConfigSyslogfacilityOption(state);
rewriteConfigSaveOption(state); rewriteConfigSaveOption(state);
rewriteConfigUserOption(state);
rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM); rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM);
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR); rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION); rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION);
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM); rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM);
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME); rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME);
rewriteConfigDirOption(state); rewriteConfigDirOption(state);
rewriteConfigSlaveofOption(state); rewriteConfigSlaveofOption(state,"replicaof");
rewriteConfigStringOption(state,"slave-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP); rewriteConfigStringOption(state,"replica-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP);
rewriteConfigStringOption(state,"masteruser",server.masteruser,NULL);
rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL); rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL); rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL);
rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA); rewriteConfigYesNoOption(state,"replica-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY); rewriteConfigYesNoOption(state,"replica-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD); rewriteConfigYesNoOption(state,"replica-ignore-maxmemory",server.repl_slave_ignore_maxmemory,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY);
rewriteConfigNumericalOption(state,"repl-ping-replica-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT); rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE); rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT); rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY); rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY);
rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC); rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC);
rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY); rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY); rewriteConfigNumericalOption(state,"replica-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE); rewriteConfigNumericalOption(state,"min-replicas-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG); 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); rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY); 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); rewriteConfigBytesOption(state,"proto-max-bulk-len",server.proto_max_bulk_len,CONFIG_DEFAULT_PROTO_MAX_BULK_LEN);
...@@ -2076,10 +2287,10 @@ int rewriteConfig(char *path) { ...@@ -2076,10 +2287,10 @@ int rewriteConfig(char *path) {
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0); rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE); rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE); rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
rewriteConfigYesNoOption(state,"cluster-slave-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER); rewriteConfigYesNoOption(state,"cluster-replica-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT); rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER); rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY); rewriteConfigNumericalOption(state,"cluster-replica-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN); rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN);
rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD); rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD);
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN); rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN);
...@@ -2097,8 +2308,9 @@ int rewriteConfig(char *path) { ...@@ -2097,8 +2308,9 @@ int rewriteConfig(char *path) {
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING); rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG); rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE); rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
rewriteConfigYesNoOption(state,"gopher-enabled",server.gopher_enabled,CONFIG_DEFAULT_GOPHER_ENABLED);
rewriteConfigClientoutputbufferlimitOption(state); rewriteConfigClientoutputbufferlimitOption(state);
rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ); rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC); rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
rewriteConfigYesNoOption(state,"rdb-save-incremental-fsync",server.rdb_save_incremental_fsync,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC); rewriteConfigYesNoOption(state,"rdb-save-incremental-fsync",server.rdb_save_incremental_fsync,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC);
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED); rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED);
...@@ -2107,7 +2319,8 @@ int rewriteConfig(char *path) { ...@@ -2107,7 +2319,8 @@ int rewriteConfig(char *path) {
rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION); rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION);
rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE); rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL); rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
rewriteConfigYesNoOption(state,"slave-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH); rewriteConfigYesNoOption(state,"replica-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
rewriteConfigYesNoOption(state,"dynamic-hz",server.dynamic_hz,CONFIG_DEFAULT_DYNAMIC_HZ);
/* Rewrite Sentinel config if in Sentinel mode. */ /* Rewrite Sentinel config if in Sentinel mode. */
if (server.sentinel_mode) rewriteConfigSentinelOption(state); if (server.sentinel_mode) rewriteConfigSentinelOption(state);
......
...@@ -62,7 +62,9 @@ ...@@ -62,7 +62,9 @@
#endif #endif
/* Test for backtrace() */ /* Test for backtrace() */
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) #if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
defined(__FreeBSD__) || (defined(__OpenBSD__) && defined(USE_BACKTRACE))\
|| defined(__DragonFly__)
#define HAVE_BACKTRACE 1 #define HAVE_BACKTRACE 1
#endif #endif
......
...@@ -38,6 +38,8 @@ ...@@ -38,6 +38,8 @@
* C-level DB API * C-level DB API
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
int keyIsExpired(redisDb *db, robj *key);
/* Update LFU when an object is accessed. /* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached. * Firstly, decrement the counter if the decrement time is reached.
* Then logarithmically increment the counter, and update the access time. */ * Then logarithmically increment the counter, and update the access time. */
...@@ -102,7 +104,10 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { ...@@ -102,7 +104,10 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
/* Key expired. If we are in the context of a master, expireIfNeeded() /* Key expired. If we are in the context of a master, expireIfNeeded()
* returns 0 only when the key does not exist at all, so it's safe * returns 0 only when the key does not exist at all, so it's safe
* to return NULL ASAP. */ * to return NULL ASAP. */
if (server.masterhost == NULL) return NULL; if (server.masterhost == NULL) {
server.stat_keyspace_misses++;
return NULL;
}
/* However if we are in the context of a slave, expireIfNeeded() will /* However if we are in the context of a slave, expireIfNeeded() will
* not really try to expire the key, it only returns information * not really try to expire the key, it only returns information
...@@ -121,6 +126,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { ...@@ -121,6 +126,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
server.current_client->cmd && server.current_client->cmd &&
server.current_client->cmd->flags & CMD_READONLY) server.current_client->cmd->flags & CMD_READONLY)
{ {
server.stat_keyspace_misses++;
return NULL; return NULL;
} }
} }
...@@ -184,14 +190,19 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) { ...@@ -184,14 +190,19 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
dictEntry *de = dictFind(db->dict,key->ptr); dictEntry *de = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,de != NULL); serverAssertWithInfo(NULL,key,de != NULL);
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { dictEntry auxentry = *de;
robj *old = dictGetVal(de); robj *old = dictGetVal(de);
int saved_lru = old->lru; if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
dictReplace(db->dict, key->ptr, val); val->lru = old->lru;
val->lru = saved_lru;
} else {
dictReplace(db->dict, key->ptr, val);
} }
dictSetVal(db->dict, de, val);
if (server.lazyfree_lazy_server_del) {
freeObjAsync(old);
dictSetVal(db->dict, &auxentry, NULL);
}
dictFreeVal(db->dict, &auxentry);
} }
/* High level Set operation. This function can be used in order to set /* High level Set operation. This function can be used in order to set
...@@ -201,7 +212,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) { ...@@ -201,7 +212,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
* 2) clients WATCHing for the destination key notified. * 2) clients WATCHing for the destination key notified.
* 3) The expire time of the key is reset (the key is made persistent). * 3) The expire time of the key is reset (the key is made persistent).
* *
* All the new keys in the database should be craeted via this interface. */ * All the new keys in the database should be created via this interface. */
void setKey(redisDb *db, robj *key, robj *val) { void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db,key) == NULL) { if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val); dbAdd(db,key,val);
...@@ -230,7 +241,7 @@ robj *dbRandomKey(redisDb *db) { ...@@ -230,7 +241,7 @@ robj *dbRandomKey(redisDb *db) {
sds key; sds key;
robj *keyobj; robj *keyobj;
de = dictGetRandomKey(db->dict); de = dictGetFairRandomKey(db->dict);
if (de == NULL) return NULL; if (de == NULL) return NULL;
key = dictGetKey(de); key = dictGetKey(de);
...@@ -329,7 +340,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) { ...@@ -329,7 +340,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
* database(s). Otherwise -1 is returned in the specific case the * database(s). Otherwise -1 is returned in the specific case the
* DB number is out of range, and errno is set to EINVAL. */ * DB number is out of range, and errno is set to EINVAL. */
long long emptyDb(int dbnum, int flags, void(callback)(void*)) { long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
int j, async = (flags & EMPTYDB_ASYNC); int async = (flags & EMPTYDB_ASYNC);
long long removed = 0; long long removed = 0;
if (dbnum < -1 || dbnum >= server.dbnum) { if (dbnum < -1 || dbnum >= server.dbnum) {
...@@ -337,8 +348,15 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) { ...@@ -337,8 +348,15 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
return -1; return -1;
} }
for (j = 0; j < server.dbnum; j++) { int startdb, enddb;
if (dbnum != -1 && dbnum != j) continue; if (dbnum == -1) {
startdb = 0;
enddb = server.dbnum-1;
} else {
startdb = enddb = dbnum;
}
for (int j = startdb; j <= enddb; j++) {
removed += dictSize(server.db[j].dict); removed += dictSize(server.db[j].dict);
if (async) { if (async) {
emptyDbAsync(&server.db[j]); emptyDbAsync(&server.db[j]);
...@@ -430,10 +448,7 @@ void flushallCommand(client *c) { ...@@ -430,10 +448,7 @@ void flushallCommand(client *c) {
signalFlushedDb(-1); signalFlushedDb(-1);
server.dirty += emptyDb(-1,flags,NULL); server.dirty += emptyDb(-1,flags,NULL);
addReply(c,shared.ok); addReply(c,shared.ok);
if (server.rdb_child_pid != -1) { if (server.rdb_child_pid != -1) killRDBChild();
kill(server.rdb_child_pid,SIGUSR1);
rdbRemoveTempFile(server.rdb_child_pid);
}
if (server.saveparamslen > 0) { if (server.saveparamslen > 0) {
/* Normally rdbSave() will reset dirty, but we don't want this here /* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */ * as otherwise FLUSHALL will not be replicated nor put into the AOF. */
...@@ -507,7 +522,7 @@ void randomkeyCommand(client *c) { ...@@ -507,7 +522,7 @@ void randomkeyCommand(client *c) {
robj *key; robj *key;
if ((key = dbRandomKey(c->db)) == NULL) { if ((key = dbRandomKey(c->db)) == NULL) {
addReply(c,shared.nullbulk); addReplyNull(c);
return; return;
} }
...@@ -521,7 +536,7 @@ void keysCommand(client *c) { ...@@ -521,7 +536,7 @@ void keysCommand(client *c) {
sds pattern = c->argv[1]->ptr; sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern), allkeys; int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0; unsigned long numkeys = 0;
void *replylen = addDeferredMultiBulkLength(c); void *replylen = addReplyDeferredLen(c);
di = dictGetSafeIterator(c->db->dict); di = dictGetSafeIterator(c->db->dict);
allkeys = (pattern[0] == '*' && pattern[1] == '\0'); allkeys = (pattern[0] == '*' && pattern[1] == '\0');
...@@ -531,7 +546,7 @@ void keysCommand(client *c) { ...@@ -531,7 +546,7 @@ void keysCommand(client *c) {
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key)); keyobj = createStringObject(key,sdslen(key));
if (expireIfNeeded(c->db,keyobj) == 0) { if (!keyIsExpired(c->db,keyobj)) {
addReplyBulk(c,keyobj); addReplyBulk(c,keyobj);
numkeys++; numkeys++;
} }
...@@ -539,7 +554,7 @@ void keysCommand(client *c) { ...@@ -539,7 +554,7 @@ void keysCommand(client *c) {
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
setDeferredMultiBulkLength(c,replylen,numkeys); setDeferredArrayLen(c,replylen,numkeys);
} }
/* This callback is used by scanGenericCommand in order to collect elements /* This callback is used by scanGenericCommand in order to collect elements
...@@ -764,10 +779,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { ...@@ -764,10 +779,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
} }
/* Step 4: Reply to the client. */ /* Step 4: Reply to the client. */
addReplyMultiBulkLen(c, 2); addReplyArrayLen(c, 2);
addReplyBulkLongLong(c,cursor); addReplyBulkLongLong(c,cursor);
addReplyMultiBulkLen(c, listLength(keys)); addReplyArrayLen(c, listLength(keys));
while ((node = listFirst(keys)) != NULL) { while ((node = listFirst(keys)) != NULL) {
robj *kobj = listNodeValue(node); robj *kobj = listNodeValue(node);
addReplyBulk(c, kobj); addReplyBulk(c, kobj);
...@@ -1108,6 +1123,25 @@ void propagateExpire(redisDb *db, robj *key, int lazy) { ...@@ -1108,6 +1123,25 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
decrRefCount(argv[1]); decrRefCount(argv[1]);
} }
/* Check if the key is expired. */
int keyIsExpired(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
return now > when;
}
/* This function is called when we are going to perform some operation /* This function is called when we are going to perform some operation
* in a given key, but such key may be already logically expired even if * in a given key, but such key may be already logically expired even if
* it still exists in the database. The main way this function is called * it still exists in the database. The main way this function is called
...@@ -1128,32 +1162,17 @@ void propagateExpire(redisDb *db, robj *key, int lazy) { ...@@ -1128,32 +1162,17 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
* The return value of the function is 0 if the key is still valid, * The return value of the function is 0 if the key is still valid,
* otherwise the function returns 1 if the key is expired. */ * otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *key) { int expireIfNeeded(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key); if (!keyIsExpired(db,key)) return 0;
mstime_t now;
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */ /* If we are running in the context of a slave, instead of
if (server.loading) return 0; * evicting the expired key from the database, we return ASAP:
/* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will * the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys. * send us synthesized DEL operations for expired keys.
* *
* Still we try to return the right information to the caller, * Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if * that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */ * we think the key is expired at this time. */
if (server.masterhost != NULL) return now > when; if (server.masterhost != NULL) return 1;
/* Return when this key has not expired */
if (now <= when) return 0;
/* Delete the key */ /* Delete the key */
server.stat_expiredkeys++; server.stat_expiredkeys++;
......
...@@ -37,7 +37,11 @@ ...@@ -37,7 +37,11 @@
#ifdef HAVE_BACKTRACE #ifdef HAVE_BACKTRACE
#include <execinfo.h> #include <execinfo.h>
#ifndef __OpenBSD__
#include <ucontext.h> #include <ucontext.h>
#else
typedef ucontext_t sigcontext_t;
#endif
#include <fcntl.h> #include <fcntl.h>
#include "bio.h" #include "bio.h"
#include <unistd.h> #include <unistd.h>
...@@ -70,7 +74,7 @@ void xorDigest(unsigned char *digest, void *ptr, size_t len) { ...@@ -70,7 +74,7 @@ void xorDigest(unsigned char *digest, void *ptr, size_t len) {
digest[j] ^= hash[j]; digest[j] ^= hash[j];
} }
void xorObjectDigest(unsigned char *digest, robj *o) { void xorStringObjectDigest(unsigned char *digest, robj *o) {
o = getDecodedObject(o); o = getDecodedObject(o);
xorDigest(digest,o->ptr,sdslen(o->ptr)); xorDigest(digest,o->ptr,sdslen(o->ptr));
decrRefCount(o); decrRefCount(o);
...@@ -100,66 +104,35 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) { ...@@ -100,66 +104,35 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) {
SHA1Final(digest,&ctx); SHA1Final(digest,&ctx);
} }
void mixObjectDigest(unsigned char *digest, robj *o) { void mixStringObjectDigest(unsigned char *digest, robj *o) {
o = getDecodedObject(o); o = getDecodedObject(o);
mixDigest(digest,o->ptr,sdslen(o->ptr)); mixDigest(digest,o->ptr,sdslen(o->ptr));
decrRefCount(o); decrRefCount(o);
} }
/* Compute the dataset digest. Since keys, sets elements, hashes elements /* This function computes the digest of a data structure stored in the
* are not ordered, we use a trick: every aggregate digest is the xor * object 'o'. It is the core of the DEBUG DIGEST command: when taking the
* of the digests of their elements. This way the order will not change * digest of a whole dataset, we take the digest of the key and the value
* the result. For list instead we use a feedback entering the output digest * pair, and xor all those together.
* as input in order to ensure that a different ordered list will result in *
* a different digest. */ * Note that this function does not reset the initial 'digest' passed, it
void computeDatasetDigest(unsigned char *final) { * will continue mixing this object digest to anything that was already
unsigned char digest[20]; * present. */
char buf[128]; void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) {
dictIterator *di = NULL; uint32_t aux = htonl(o->type);
dictEntry *de;
int j;
uint32_t aux;
memset(final,0,20); /* Start with a clean result */
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
if (dictSize(db->dict) == 0) continue;
di = dictGetSafeIterator(db->dict);
/* hash the DB id, so the same dataset moved in a different
* DB will lead to a different digest */
aux = htonl(j);
mixDigest(final,&aux,sizeof(aux));
/* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) {
sds key;
robj *keyobj, *o;
long long expiretime;
memset(digest,0,20); /* This key-val digest */
key = dictGetKey(de);
keyobj = createStringObject(key,sdslen(key));
mixDigest(digest,key,sdslen(key));
o = dictGetVal(de);
aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux)); mixDigest(digest,&aux,sizeof(aux));
expiretime = getExpire(db,keyobj); long long expiretime = getExpire(db,keyobj);
char buf[128];
/* Save the key and associated value */ /* Save the key and associated value */
if (o->type == OBJ_STRING) { if (o->type == OBJ_STRING) {
mixObjectDigest(digest,o); mixStringObjectDigest(digest,o);
} else if (o->type == OBJ_LIST) { } else if (o->type == OBJ_LIST) {
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL); listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
listTypeEntry entry; listTypeEntry entry;
while(listTypeNext(li,&entry)) { while(listTypeNext(li,&entry)) {
robj *eleobj = listTypeGet(&entry); robj *eleobj = listTypeGet(&entry);
mixObjectDigest(digest,eleobj); mixStringObjectDigest(digest,eleobj);
decrRefCount(eleobj); decrRefCount(eleobj);
} }
listTypeReleaseIterator(li); listTypeReleaseIterator(li);
...@@ -274,6 +247,48 @@ void computeDatasetDigest(unsigned char *final) { ...@@ -274,6 +247,48 @@ void computeDatasetDigest(unsigned char *final) {
} }
/* If the key has an expire, add it to the mix */ /* If the key has an expire, add it to the mix */
if (expiretime != -1) xorDigest(digest,"!!expire!!",10); if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
}
/* Compute the dataset digest. Since keys, sets elements, hashes elements
* are not ordered, we use a trick: every aggregate digest is the xor
* of the digests of their elements. This way the order will not change
* the result. For list instead we use a feedback entering the output digest
* as input in order to ensure that a different ordered list will result in
* a different digest. */
void computeDatasetDigest(unsigned char *final) {
unsigned char digest[20];
dictIterator *di = NULL;
dictEntry *de;
int j;
uint32_t aux;
memset(final,0,20); /* Start with a clean result */
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
if (dictSize(db->dict) == 0) continue;
di = dictGetSafeIterator(db->dict);
/* hash the DB id, so the same dataset moved in a different
* DB will lead to a different digest */
aux = htonl(j);
mixDigest(final,&aux,sizeof(aux));
/* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) {
sds key;
robj *keyobj, *o;
memset(digest,0,20); /* This key-val digest */
key = dictGetKey(de);
keyobj = createStringObject(key,sdslen(key));
mixDigest(digest,key,sdslen(key));
o = dictGetVal(de);
xorObjectDigest(db,keyobj,digest,o);
/* We can finally xor the key-val digest to the final digest */ /* We can finally xor the key-val digest to the final digest */
xorDigest(final,digest,20); xorDigest(final,digest,20);
decrRefCount(keyobj); decrRefCount(keyobj);
...@@ -289,7 +304,9 @@ void debugCommand(client *c) { ...@@ -289,7 +304,9 @@ void debugCommand(client *c) {
"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.", "CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.", "CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
"DIGEST -- Output a hex signature representing the current DB content.", "DIGEST -- Output a hex signature representing the current DB content.",
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.", "ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
"LOG <message> -- write message to the server log.",
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.", "HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.", "HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.", "LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
...@@ -305,6 +322,7 @@ void debugCommand(client *c) { ...@@ -305,6 +322,7 @@ void debugCommand(client *c) {
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.", "SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
"STRUCTSIZE -- Return the size of different Redis core C structures.", "STRUCTSIZE -- Return the size of different Redis core C structures.",
"ZIPLIST <key> -- Show low level info about the ziplist encoding.", "ZIPLIST <key> -- Show low level info about the ziplist encoding.",
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -331,8 +349,10 @@ NULL ...@@ -331,8 +349,10 @@ NULL
zfree(ptr); zfree(ptr);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) { } else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
serverAssertWithInfo(c,c->argv[0],1 == 2); serverAssertWithInfo(c,c->argv[0],1 == 2);
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) { } else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
rdbSaveInfo rsi, *rsiptr; rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi); rsiptr = rdbPopulateSaveInfo(&rsi);
...@@ -341,7 +361,10 @@ NULL ...@@ -341,7 +361,10 @@ NULL
return; return;
} }
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL); emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
if (rdbLoad(server.rdb_filename,NULL) != C_OK) { protectClient(c);
int ret = rdbLoad(server.rdb_filename,NULL);
unprotectClient(c);
if (ret != C_OK) {
addReplyError(c,"Error trying to load the RDB dump"); addReplyError(c,"Error trying to load the RDB dump");
return; return;
} }
...@@ -350,7 +373,10 @@ NULL ...@@ -350,7 +373,10 @@ NULL
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
if (server.aof_state != AOF_OFF) flushAppendOnlyFile(1); if (server.aof_state != AOF_OFF) flushAppendOnlyFile(1);
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL); emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
if (loadAppendOnlyFile(server.aof_filename) != C_OK) { protectClient(c);
int ret = loadAppendOnlyFile(server.aof_filename);
unprotectClient(c);
if (ret != C_OK) {
addReply(c,shared.err); addReply(c,shared.err);
return; return;
} }
...@@ -481,15 +507,80 @@ NULL ...@@ -481,15 +507,80 @@ NULL
} }
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
/* DEBUG DIGEST (form without keys specified) */
unsigned char digest[20]; unsigned char digest[20];
sds d = sdsempty(); sds d = sdsempty();
int j;
computeDatasetDigest(digest); computeDatasetDigest(digest);
for (j = 0; j < 20; j++) for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
d = sdscatprintf(d, "%02x",digest[j]);
addReplyStatus(c,d); addReplyStatus(c,d);
sdsfree(d); sdsfree(d);
} else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
/* DEBUG DIGEST-VALUE key key key ... key. */
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 */
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
sds d = sdsempty();
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
addReplyStatus(c,d);
sdsfree(d);
}
} else if (!strcasecmp(c->argv[1]->ptr,"protocol") && c->argc == 3) {
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
* attrib|push|verbatim|true|false|state|err|bloberr] */
char *name = c->argv[2]->ptr;
if (!strcasecmp(name,"string")) {
addReplyBulkCString(c,"Hello World");
} else if (!strcasecmp(name,"integer")) {
addReplyLongLong(c,12345);
} else if (!strcasecmp(name,"double")) {
addReplyDouble(c,3.14159265359);
} else if (!strcasecmp(name,"bignum")) {
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
} else if (!strcasecmp(name,"null")) {
addReplyNull(c);
} else if (!strcasecmp(name,"array")) {
addReplyArrayLen(c,3);
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
} else if (!strcasecmp(name,"set")) {
addReplySetLen(c,3);
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
} else if (!strcasecmp(name,"map")) {
addReplyMapLen(c,3);
for (int j = 0; j < 3; j++) {
addReplyLongLong(c,j);
addReplyBool(c, j == 1);
}
} else if (!strcasecmp(name,"attrib")) {
addReplyAttributeLen(c,1);
addReplyBulkCString(c,"key-popularity");
addReplyArrayLen(c,2);
addReplyBulkCString(c,"key:123");
addReplyLongLong(c,90);
/* Attributes are not real replies, so a well formed reply should
* also have a normal reply type after the attribute. */
addReplyBulkCString(c,"Some real reply following the attribute");
} else if (!strcasecmp(name,"push")) {
addReplyPushLen(c,2);
addReplyBulkCString(c,"server-cpu-usage");
addReplyLongLong(c,42);
/* Push replies are not synchronous replies, so we emit also a
* normal reply in order for blocking clients just discarding the
* push reply, to actually consume the reply and continue. */
addReplyBulkCString(c,"Some real reply following the push reply");
} else if (!strcasecmp(name,"true")) {
addReplyBool(c,1);
} else if (!strcasecmp(name,"false")) {
addReplyBool(c,0);
} else if (!strcasecmp(name,"verbatim")) {
addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
} else {
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr");
}
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
double dtime = strtod(c->argv[2]->ptr,NULL); double dtime = strtod(c->argv[2]->ptr,NULL);
long long utime = dtime*1000000; long long utime = dtime*1000000;
...@@ -581,6 +672,10 @@ NULL ...@@ -581,6 +672,10 @@ NULL
changeReplicationId(); changeReplicationId();
clearReplicationId2(); clearReplicationId2();
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"stringmatch-test") && c->argc == 2)
{
stringmatchlen_fuzz_test();
addReplyStatus(c,"Apparently Redis did not crash: test passed");
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);
return; return;
...@@ -708,7 +803,7 @@ static void *getMcontextEip(ucontext_t *uc) { ...@@ -708,7 +803,7 @@ static void *getMcontextEip(ucontext_t *uc) {
#endif #endif
#elif defined(__linux__) #elif defined(__linux__)
/* Linux */ /* Linux */
#if defined(__i386__) #if defined(__i386__) || defined(__ILP32__)
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */ return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
#elif defined(__X86_64__) || defined(__x86_64__) #elif defined(__X86_64__) || defined(__x86_64__)
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */ return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
...@@ -719,6 +814,22 @@ static void *getMcontextEip(ucontext_t *uc) { ...@@ -719,6 +814,22 @@ static void *getMcontextEip(ucontext_t *uc) {
#elif defined(__aarch64__) /* Linux AArch64 */ #elif defined(__aarch64__) /* Linux AArch64 */
return (void*) uc->uc_mcontext.pc; return (void*) uc->uc_mcontext.pc;
#endif #endif
#elif defined(__FreeBSD__)
/* FreeBSD */
#if defined(__i386__)
return (void*) uc->uc_mcontext.mc_eip;
#elif defined(__x86_64__)
return (void*) uc->uc_mcontext.mc_rip;
#endif
#elif defined(__OpenBSD__)
/* OpenBSD */
#if defined(__i386__)
return (void*) uc->sc_eip;
#elif defined(__x86_64__)
return (void*) uc->sc_rip;
#endif
#elif defined(__DragonFly__)
return (void*) uc->uc_mcontext.mc_rip;
#else #else
return NULL; return NULL;
#endif #endif
...@@ -804,7 +915,7 @@ void logRegisters(ucontext_t *uc) { ...@@ -804,7 +915,7 @@ void logRegisters(ucontext_t *uc) {
/* Linux */ /* Linux */
#elif defined(__linux__) #elif defined(__linux__)
/* Linux x86 */ /* Linux x86 */
#if defined(__i386__) #if defined(__i386__) || defined(__ILP32__)
serverLog(LL_WARNING, serverLog(LL_WARNING,
"\n" "\n"
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n" "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
...@@ -860,6 +971,145 @@ void logRegisters(ucontext_t *uc) { ...@@ -860,6 +971,145 @@ void logRegisters(ucontext_t *uc) {
); );
logStackContent((void**)uc->uc_mcontext.gregs[15]); logStackContent((void**)uc->uc_mcontext.gregs[15]);
#endif #endif
#elif defined(__FreeBSD__)
#if defined(__x86_64__)
serverLog(LL_WARNING,
"\n"
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
(unsigned long) uc->uc_mcontext.mc_rax,
(unsigned long) uc->uc_mcontext.mc_rbx,
(unsigned long) uc->uc_mcontext.mc_rcx,
(unsigned long) uc->uc_mcontext.mc_rdx,
(unsigned long) uc->uc_mcontext.mc_rdi,
(unsigned long) uc->uc_mcontext.mc_rsi,
(unsigned long) uc->uc_mcontext.mc_rbp,
(unsigned long) uc->uc_mcontext.mc_rsp,
(unsigned long) uc->uc_mcontext.mc_r8,
(unsigned long) uc->uc_mcontext.mc_r9,
(unsigned long) uc->uc_mcontext.mc_r10,
(unsigned long) uc->uc_mcontext.mc_r11,
(unsigned long) uc->uc_mcontext.mc_r12,
(unsigned long) uc->uc_mcontext.mc_r13,
(unsigned long) uc->uc_mcontext.mc_r14,
(unsigned long) uc->uc_mcontext.mc_r15,
(unsigned long) uc->uc_mcontext.mc_rip,
(unsigned long) uc->uc_mcontext.mc_rflags,
(unsigned long) uc->uc_mcontext.mc_cs
);
logStackContent((void**)uc->uc_mcontext.mc_rsp);
#elif defined(__i386__)
serverLog(LL_WARNING,
"\n"
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
(unsigned long) uc->uc_mcontext.mc_eax,
(unsigned long) uc->uc_mcontext.mc_ebx,
(unsigned long) uc->uc_mcontext.mc_ebx,
(unsigned long) uc->uc_mcontext.mc_edx,
(unsigned long) uc->uc_mcontext.mc_edi,
(unsigned long) uc->uc_mcontext.mc_esi,
(unsigned long) uc->uc_mcontext.mc_ebp,
(unsigned long) uc->uc_mcontext.mc_esp,
(unsigned long) uc->uc_mcontext.mc_ss,
(unsigned long) uc->uc_mcontext.mc_eflags,
(unsigned long) uc->uc_mcontext.mc_eip,
(unsigned long) uc->uc_mcontext.mc_cs,
(unsigned long) uc->uc_mcontext.mc_es,
(unsigned long) uc->uc_mcontext.mc_fs,
(unsigned long) uc->uc_mcontext.mc_gs
);
logStackContent((void**)uc->uc_mcontext.mc_esp);
#endif
#elif defined(__OpenBSD__)
#if defined(__x86_64__)
serverLog(LL_WARNING,
"\n"
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
(unsigned long) uc->sc_rax,
(unsigned long) uc->sc_rbx,
(unsigned long) uc->sc_rcx,
(unsigned long) uc->sc_rdx,
(unsigned long) uc->sc_rdi,
(unsigned long) uc->sc_rsi,
(unsigned long) uc->sc_rbp,
(unsigned long) uc->sc_rsp,
(unsigned long) uc->sc_r8,
(unsigned long) uc->sc_r9,
(unsigned long) uc->sc_r10,
(unsigned long) uc->sc_r11,
(unsigned long) uc->sc_r12,
(unsigned long) uc->sc_r13,
(unsigned long) uc->sc_r14,
(unsigned long) uc->sc_r15,
(unsigned long) uc->sc_rip,
(unsigned long) uc->sc_rflags,
(unsigned long) uc->sc_cs
);
logStackContent((void**)uc->sc_rsp);
#elif defined(__i386__)
serverLog(LL_WARNING,
"\n"
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
(unsigned long) uc->sc_eax,
(unsigned long) uc->sc_ebx,
(unsigned long) uc->sc_ebx,
(unsigned long) uc->sc_edx,
(unsigned long) uc->sc_edi,
(unsigned long) uc->sc_esi,
(unsigned long) uc->sc_ebp,
(unsigned long) uc->sc_esp,
(unsigned long) uc->sc_ss,
(unsigned long) uc->sc_eflags,
(unsigned long) uc->sc_eip,
(unsigned long) uc->sc_cs,
(unsigned long) uc->sc_es,
(unsigned long) uc->sc_fs,
(unsigned long) uc->sc_gs
);
logStackContent((void**)uc->sc_esp);
#endif
#elif defined(__DragonFly__)
serverLog(LL_WARNING,
"\n"
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
(unsigned long) uc->uc_mcontext.mc_rax,
(unsigned long) uc->uc_mcontext.mc_rbx,
(unsigned long) uc->uc_mcontext.mc_rcx,
(unsigned long) uc->uc_mcontext.mc_rdx,
(unsigned long) uc->uc_mcontext.mc_rdi,
(unsigned long) uc->uc_mcontext.mc_rsi,
(unsigned long) uc->uc_mcontext.mc_rbp,
(unsigned long) uc->uc_mcontext.mc_rsp,
(unsigned long) uc->uc_mcontext.mc_r8,
(unsigned long) uc->uc_mcontext.mc_r9,
(unsigned long) uc->uc_mcontext.mc_r10,
(unsigned long) uc->uc_mcontext.mc_r11,
(unsigned long) uc->uc_mcontext.mc_r12,
(unsigned long) uc->uc_mcontext.mc_r13,
(unsigned long) uc->uc_mcontext.mc_r14,
(unsigned long) uc->uc_mcontext.mc_r15,
(unsigned long) uc->uc_mcontext.mc_rip,
(unsigned long) uc->uc_mcontext.mc_rflags,
(unsigned long) uc->uc_mcontext.mc_cs
);
logStackContent((void**)uc->uc_mcontext.mc_rsp);
#else #else
serverLog(LL_WARNING, serverLog(LL_WARNING,
" Dumping of registers not supported for this OS/arch"); " Dumping of registers not supported for this OS/arch");
...@@ -1179,6 +1429,8 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len) { ...@@ -1179,6 +1429,8 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len) {
void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) { void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
#ifdef HAVE_BACKTRACE #ifdef HAVE_BACKTRACE
ucontext_t *uc = (ucontext_t*) secret; ucontext_t *uc = (ucontext_t*) secret;
#else
(void)secret;
#endif #endif
UNUSED(info); UNUSED(info);
UNUSED(sig); UNUSED(sig);
......
...@@ -739,6 +739,30 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) { ...@@ -739,6 +739,30 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
return stored; return stored;
} }
/* This is like dictGetRandomKey() from the POV of the API, but will do more
* work to ensure a better distribution of the returned element.
*
* This function improves the distribution because the dictGetRandomKey()
* problem is that it selects a random bucket, then it selects a random
* element from the chain in the bucket. However elements being in different
* chain lengths will have different probabilities of being reported. With
* this function instead what we do is to consider a "linear" range of the table
* that may be constituted of N buckets with chains of different lengths
* appearing one after the other. Then we report a random element in the range.
* In this way we smooth away the problem of different chain lenghts. */
#define GETFAIR_NUM_ENTRIES 15
dictEntry *dictGetFairRandomKey(dict *d) {
dictEntry *entries[GETFAIR_NUM_ENTRIES];
unsigned int count = dictGetSomeKeys(d,entries,GETFAIR_NUM_ENTRIES);
/* Note that dictGetSomeKeys() may return zero elements in an unlucky
* run() even if there are actually elements inside the hash table. So
* when we get zero, we call the true dictGetRandomKey() that will always
* yeld the element if the hash table has at least one. */
if (count == 0) return dictGetRandomKey(d);
unsigned int idx = rand() % count;
return entries[idx];
}
/* Function to reverse bits. Algorithm from: /* Function to reverse bits. Algorithm from:
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */ * http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
static unsigned long rev(unsigned long v) { static unsigned long rev(unsigned long v) {
......
...@@ -166,6 +166,7 @@ dictIterator *dictGetSafeIterator(dict *d); ...@@ -166,6 +166,7 @@ dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter); dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter); void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d); dictEntry *dictGetRandomKey(dict *d);
dictEntry *dictGetFairRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count); unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictGetStats(char *buf, size_t bufsize, dict *d); void dictGetStats(char *buf, size_t bufsize, dict *d);
uint64_t dictGenHashFunction(const void *key, int len); uint64_t dictGenHashFunction(const void *key, int len);
......
...@@ -364,7 +364,7 @@ size_t freeMemoryGetNotCountedMemory(void) { ...@@ -364,7 +364,7 @@ size_t freeMemoryGetNotCountedMemory(void) {
} }
} }
if (server.aof_state != AOF_OFF) { if (server.aof_state != AOF_OFF) {
overhead += sdslen(server.aof_buf)+aofRewriteBufferSize(); overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
} }
return overhead; return overhead;
} }
...@@ -444,6 +444,10 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev ...@@ -444,6 +444,10 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
* Otehrwise if we are over the memory limit, but not enough memory * Otehrwise if we are over the memory limit, but not enough memory
* was freed to return back under the limit, the function returns C_ERR. */ * was freed to return back under the limit, the function returns C_ERR. */
int freeMemoryIfNeeded(void) { int freeMemoryIfNeeded(void) {
/* By default replicas should ignore maxmemory
* and just be masters exact copies. */
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
size_t mem_reported, mem_tofree, mem_freed; size_t mem_reported, mem_tofree, mem_freed;
mstime_t latency, eviction_latency; mstime_t latency, eviction_latency;
long long delta; long long delta;
...@@ -618,3 +622,14 @@ cant_free: ...@@ -618,3 +622,14 @@ cant_free:
return C_ERR; return C_ERR;
} }
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
* function if right now there are the conditions to do so safely:
*
* - There must be no script in timeout condition.
* - Nor we are loading data right now.
*
*/
int freeMemoryIfNeededAndSafe(void) {
if (server.lua_timedout || server.loading) return C_OK;
return freeMemoryIfNeeded();
}
...@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = NULL; 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)) { checkType(c, zobj, OBJ_ZSET)) {
return; return;
} }
...@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
/* If no matching results, the user gets an empty reply. */ /* If no matching results, the user gets an empty reply. */
if (ga->used == 0 && storekey == NULL) { if (ga->used == 0 && storekey == NULL) {
addReply(c, shared.emptymultibulk); addReplyNull(c);
geoArrayFree(ga); geoArrayFree(ga);
return; return;
} }
...@@ -597,11 +597,11 @@ void georadiusGeneric(client *c, int flags) { ...@@ -597,11 +597,11 @@ void georadiusGeneric(client *c, int flags) {
if (withhash) if (withhash)
option_length++; 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 * either all strings of just zset members *or* a nested multi-bulk
* reply containing the zset member string _and_ all the additional * reply containing the zset member string _and_ all the additional
* options the user enabled for this request. */ * options the user enabled for this request. */
addReplyMultiBulkLen(c, returned_items); addReplyArrayLen(c, returned_items);
/* Finally send results back to the caller */ /* Finally send results back to the caller */
int i; int i;
...@@ -613,7 +613,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -613,7 +613,7 @@ void georadiusGeneric(client *c, int flags) {
* as a nested multi-bulk. Add 1 to account for result value * as a nested multi-bulk. Add 1 to account for result value
* itself. */ * itself. */
if (option_length) if (option_length)
addReplyMultiBulkLen(c, option_length + 1); addReplyArrayLen(c, option_length + 1);
addReplyBulkSds(c,gp->member); addReplyBulkSds(c,gp->member);
gp->member = NULL; gp->member = NULL;
...@@ -625,7 +625,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -625,7 +625,7 @@ void georadiusGeneric(client *c, int flags) {
addReplyLongLong(c, gp->score); addReplyLongLong(c, gp->score);
if (withcoords) { if (withcoords) {
addReplyMultiBulkLen(c, 2); addReplyArrayLen(c, 2);
addReplyHumanLongDouble(c, gp->longitude); addReplyHumanLongDouble(c, gp->longitude);
addReplyHumanLongDouble(c, gp->latitude); addReplyHumanLongDouble(c, gp->latitude);
} }
...@@ -706,11 +706,11 @@ void geohashCommand(client *c) { ...@@ -706,11 +706,11 @@ void geohashCommand(client *c) {
/* Geohash elements one after the other, using a null bulk reply for /* Geohash elements one after the other, using a null bulk reply for
* missing elements. */ * missing elements. */
addReplyMultiBulkLen(c,c->argc-2); addReplyArrayLen(c,c->argc-2);
for (j = 2; j < c->argc; j++) { for (j = 2; j < c->argc; j++) {
double score; double score;
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) { if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
addReply(c,shared.nullbulk); addReplyNull(c);
} else { } else {
/* The internal format we use for geocoding is a bit different /* The internal format we use for geocoding is a bit different
* than the standard, since we use as initial latitude range * than the standard, since we use as initial latitude range
...@@ -721,7 +721,7 @@ void geohashCommand(client *c) { ...@@ -721,7 +721,7 @@ void geohashCommand(client *c) {
/* Decode... */ /* Decode... */
double xy[2]; double xy[2];
if (!decodeGeohash(score,xy)) { if (!decodeGeohash(score,xy)) {
addReply(c,shared.nullbulk); addReplyNull(c);
continue; continue;
} }
...@@ -759,19 +759,19 @@ void geoposCommand(client *c) { ...@@ -759,19 +759,19 @@ void geoposCommand(client *c) {
/* Report elements one after the other, using a null bulk reply for /* Report elements one after the other, using a null bulk reply for
* missing elements. */ * missing elements. */
addReplyMultiBulkLen(c,c->argc-2); addReplyArrayLen(c,c->argc-2);
for (j = 2; j < c->argc; j++) { for (j = 2; j < c->argc; j++) {
double score; double score;
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) { if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
addReply(c,shared.nullmultibulk); addReplyNullArray(c);
} else { } else {
/* Decode... */ /* Decode... */
double xy[2]; double xy[2];
if (!decodeGeohash(score,xy)) { if (!decodeGeohash(score,xy)) {
addReply(c,shared.nullmultibulk); addReplyNullArray(c);
continue; continue;
} }
addReplyMultiBulkLen(c,2); addReplyArrayLen(c,2);
addReplyHumanLongDouble(c,xy[0]); addReplyHumanLongDouble(c,xy[0]);
addReplyHumanLongDouble(c,xy[1]); addReplyHumanLongDouble(c,xy[1]);
} }
...@@ -797,7 +797,7 @@ void geodistCommand(client *c) { ...@@ -797,7 +797,7 @@ void geodistCommand(client *c) {
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = NULL; 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; == NULL || checkType(c, zobj, OBJ_ZSET)) return;
/* Get the scores. We need both otherwise NULL is returned. */ /* Get the scores. We need both otherwise NULL is returned. */
...@@ -805,13 +805,13 @@ void geodistCommand(client *c) { ...@@ -805,13 +805,13 @@ void geodistCommand(client *c) {
if (zsetScore(zobj, c->argv[2]->ptr, &score1) == C_ERR || if (zsetScore(zobj, c->argv[2]->ptr, &score1) == C_ERR ||
zsetScore(zobj, c->argv[3]->ptr, &score2) == C_ERR) zsetScore(zobj, c->argv[3]->ptr, &score2) == C_ERR)
{ {
addReply(c,shared.nullbulk); addReplyNull(c);
return; return;
} }
/* Decode & compute the distance. */ /* Decode & compute the distance. */
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2)) if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
addReply(c,shared.nullbulk); addReplyNull(c);
else else
addReplyDoubleDistance(c, addReplyDoubleDistance(c,
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter); geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment