Commit 270a1114 authored by vattezhang's avatar vattezhang
Browse files

Merge branch 'unstable' of github.com:antirez/redis into unstable

parents 0f0f787a 8a46d32b
...@@ -1183,6 +1183,61 @@ latency-monitor-threshold 0 ...@@ -1183,6 +1183,61 @@ latency-monitor-threshold 0
# specify at least one of K or E, no events will be delivered. # specify at least one of K or E, no events will be delivered.
notify-keyspace-events "" notify-keyspace-events ""
############################### GOPHER SERVER #################################
# Redis contains an implementation of the Gopher protocol, as specified in
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
#
# The Gopher protocol was very popular in the late '90s. It is an alternative
# to the web, and the implementation both server and client side is so simple
# that the Redis server has just 100 lines of code in order to implement this
# support.
#
# What do you do with Gopher nowadays? Well Gopher never *really* died, and
# lately there is a movement in order for the Gopher more hierarchical content
# composed of just plain text documents to be resurrected. Some want a simpler
# internet, others believe that the mainstream internet became too much
# controlled, and it's cool to create an alternative space for people that
# want a bit of fresh air.
#
# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
# as a gift.
#
# --- HOW IT WORKS? ---
#
# The Redis Gopher support uses the inline protocol of Redis, and specifically
# two kind of inline requests that were anyway illegal: an empty request
# or any request that starts with "/" (there are no Redis commands starting
# with such a slash). Normal RESP2/RESP3 requests are completely out of the
# path of the Gopher protocol implementation and are served as usually as well.
#
# If you open a connection to Redis when Gopher is enabled and send it
# a string like "/foo", if there is a key named "/foo" it is served via the
# Gopher protocol.
#
# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher
# talking), you likely need a script like the following:
#
# https://github.com/antirez/gopher2redis
#
# --- SECURITY WARNING ---
#
# If you plan to put Redis on the internet in a publicly accessible address
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
# Once a password is set:
#
# 1. The Gopher server (when enabled, not by default) will kill serve
# content via Gopher.
# 2. However other commands cannot be called before the client will
# authenticate.
#
# So use the 'requirepass' option to protect your instance.
#
# To enable Gopher support uncomment the following line and set
# the option from no (the default) to yes.
#
# gopher-enabled no
############################### ADVANCED CONFIG ############################### ############################### ADVANCED CONFIG ###############################
# Hashes are encoded using a memory efficient data structure when they have a # Hashes are encoded using a memory efficient data structure when they have a
......
...@@ -164,11 +164,11 @@ endif ...@@ -164,11 +164,11 @@ 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 lolwut.o lolwut5.o acl.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
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o zmalloc.o redis-benchmark.o REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
REDIS_CHECK_RDB_NAME=redis-check-rdb REDIS_CHECK_RDB_NAME=redis-check-rdb
REDIS_CHECK_AOF_NAME=redis-check-aof REDIS_CHECK_AOF_NAME=redis-check-aof
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
*/ */
#include "server.h" #include "server.h"
#include <fcntl.h>
/* ============================================================================= /* =============================================================================
* Global state for ACLs * Global state for ACLs
...@@ -887,6 +888,22 @@ int ACLCheckUserCredentials(robj *username, robj *password) { ...@@ -887,6 +888,22 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
return C_ERR; 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 /* 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 * 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 * should have an assigned ID (that is used to index the bitmap). This function
...@@ -1147,6 +1164,7 @@ sds ACLLoadFromFile(const char *filename) { ...@@ -1147,6 +1164,7 @@ sds ACLLoadFromFile(const char *filename) {
int totlines; int totlines;
sds *lines, errors = sdsempty(); sds *lines, errors = sdsempty();
lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines); lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines);
sdsfree(acls);
/* We need a fake user to validate the rules before making changes /* We need a fake user to validate the rules before making changes
* to the real user mentioned in the ACL line. */ * to the real user mentioned in the ACL line. */
...@@ -1261,6 +1279,70 @@ sds ACLLoadFromFile(const char *filename) { ...@@ -1261,6 +1279,70 @@ sds ACLLoadFromFile(const char *filename) {
} }
} }
/* 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 /* 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 * 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 pending list of users defined in redis.conf, or from the ACL file.
...@@ -1300,29 +1382,45 @@ void ACLLoadUsersAtStartup(void) { ...@@ -1300,29 +1382,45 @@ void ACLLoadUsersAtStartup(void) {
/* ACL -- show and modify the configuration of ACL users. /* ACL -- show and modify the configuration of ACL users.
* ACL HELP * ACL HELP
* ACL LOAD
* ACL LIST * ACL LIST
* ACL USERS * ACL USERS
* ACL CAT [<category>] * ACL CAT [<category>]
* ACL SETUSER <username> ... acl rules ... * ACL SETUSER <username> ... acl rules ...
* ACL DELUSER <username> * ACL DELUSER <username> [...]
* ACL GETUSER <username> * ACL GETUSER <username>
* ACL GENPASS
* ACL WHOAMI
*/ */
void aclCommand(client *c) { void aclCommand(client *c) {
char *sub = c->argv[1]->ptr; char *sub = c->argv[1]->ptr;
if (!strcasecmp(sub,"setuser") && c->argc >= 3) { if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
sds username = c->argv[2]->ptr; 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)); user *u = ACLGetUserByName(username,sdslen(username));
if (!u) u = ACLCreateUser(username,sdslen(username)); if (u) ACLCopyUser(tempu, u);
serverAssert(u != NULL);
for (int j = 3; j < c->argc; j++) { for (int j = 3; j < c->argc; j++) {
if (ACLSetUser(u,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) { if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
char *errmsg = ACLSetUserStringError(); char *errmsg = ACLSetUserStringError();
addReplyErrorFormat(c, addReplyErrorFormat(c,
"Error in ACL SETUSER modifier '%s': %s", "Error in ACL SETUSER modifier '%s': %s",
(char*)c->argv[j]->ptr, errmsg); (char*)c->argv[j]->ptr, errmsg);
ACLFreeUser(tempu);
return; 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); addReply(c,shared.ok);
} else if (!strcasecmp(sub,"deluser") && c->argc >= 3) { } else if (!strcasecmp(sub,"deluser") && c->argc >= 3) {
int deleted = 0; int deleted = 0;
...@@ -1332,6 +1430,10 @@ void aclCommand(client *c) { ...@@ -1332,6 +1430,10 @@ void aclCommand(client *c) {
addReplyError(c,"The 'default' user cannot be removed"); addReplyError(c,"The 'default' user cannot be removed");
return; return;
} }
}
for (int j = 2; j < c->argc; j++) {
sds username = c->argv[j]->ptr;
user *u; user *u;
if (raxRemove(Users,(unsigned char*)username, if (raxRemove(Users,(unsigned char*)username,
sdslen(username), sdslen(username),
...@@ -1424,11 +1526,12 @@ void aclCommand(client *c) { ...@@ -1424,11 +1526,12 @@ void aclCommand(client *c) {
} else { } else {
addReplyNull(c); addReplyNull(c);
} }
} else if (!strcasecmp(sub,"load") && c->argc == 2) { } else if (server.acl_filename[0] == '\0' &&
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."); 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; return;
} else { } else if (!strcasecmp(sub,"load") && c->argc == 2) {
sds errors = ACLLoadFromFile(server.acl_filename); sds errors = ACLLoadFromFile(server.acl_filename);
if (errors == NULL) { if (errors == NULL) {
addReply(c,shared.ok); addReply(c,shared.ok);
...@@ -1436,6 +1539,13 @@ void aclCommand(client *c) { ...@@ -1436,6 +1539,13 @@ void aclCommand(client *c) {
addReplyError(c,errors); addReplyError(c,errors);
sdsfree(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) { } else if (!strcasecmp(sub,"cat") && c->argc == 2) {
void *dl = addReplyDeferredLen(c); void *dl = addReplyDeferredLen(c);
...@@ -1463,15 +1573,21 @@ void aclCommand(client *c) { ...@@ -1463,15 +1573,21 @@ void aclCommand(client *c) {
} }
dictReleaseIterator(di); dictReleaseIterator(di);
setDeferredArrayLen(c,dl,arraylen); setDeferredArrayLen(c,dl,arraylen);
} else if (!strcasecmp(sub,"genpass") && c->argc == 2) {
char pass[32]; /* 128 bits of actual pseudo random data. */
getRandomHexChars(pass,sizeof(pass));
addReplyBulkCBuffer(c,pass,sizeof(pass));
} else if (!strcasecmp(sub,"help")) { } else if (!strcasecmp(sub,"help")) {
const char *help[] = { const char *help[] = {
"LOAD -- Reload users from the ACL file.",
"LIST -- Show user details in config file format.", "LIST -- Show user details in config file format.",
"USERS -- List all the registered usernames.", "USERS -- List all the registered usernames.",
"SETUSER <username> [attribs ...] -- Create or modify a user.", "SETUSER <username> [attribs ...] -- Create or modify a user.",
"GETUSER <username> -- Get the user details.", "GETUSER <username> -- Get the user details.",
"DELUSER <username> -- Delete a user.", "DELUSER <username> [...] -- Delete a list of users.",
"CAT -- List available categories.", "CAT -- List available categories.",
"CAT <category> -- List commands inside category.", "CAT <category> -- List commands inside category.",
"GENPASS -- Generate a secure user password.",
"WHOAMI -- Return the current connection username.", "WHOAMI -- Return the current connection username.",
NULL NULL
}; };
...@@ -1492,3 +1608,47 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) { ...@@ -1492,3 +1608,47 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
} }
setDeferredSetLen(c, flaglen, 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);
}
...@@ -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) {
...@@ -1141,6 +1145,8 @@ void configSetCommand(client *c) { ...@@ -1141,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(
...@@ -1472,6 +1478,7 @@ void configGetCommand(client *c) { ...@@ -1472,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",
...@@ -2301,6 +2308,7 @@ int rewriteConfig(char *path) { ...@@ -2301,6 +2308,7 @@ 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.config_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);
......
This diff is collapsed.
...@@ -241,7 +241,7 @@ robj *dbRandomKey(redisDb *db) { ...@@ -241,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);
......
...@@ -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);
......
/*
* Copyright (c) 2019, 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"
/* Emit an item in Gopher directory listing format:
* <type><descr><TAB><selector><TAB><hostname><TAB><port>
* If descr or selector are NULL, then the "(NULL)" string is used instead. */
void addReplyGopherItem(client *c, const char *type, const char *descr,
const char *selector, const char *hostname, int port)
{
sds item = sdscatfmt(sdsempty(),"%s%s\t%s\t%s\t%i\r\n",
type, descr,
selector ? selector : "(NULL)",
hostname ? hostname : "(NULL)",
port);
addReplyProto(c,item,sdslen(item));
sdsfree(item);
}
/* This is called by processInputBuffer() when an inline request is processed
* with Gopher mode enabled, and the request happens to have zero or just one
* argument. In such case we get the relevant key and reply using the Gopher
* protocol. */
void processGopherRequest(client *c) {
robj *keyname = c->argc == 0 ? createStringObject("/",1) : c->argv[0];
robj *o = lookupKeyRead(c->db,keyname);
/* If there is no such key, return with a Gopher error. */
if (o == NULL || o->type != OBJ_STRING) {
char *errstr;
if (o == NULL)
errstr = "Error: no content at the specified key";
else
errstr = "Error: selected key type is invalid "
"for Gopher output";
addReplyGopherItem(c,"i",errstr,NULL,NULL,0);
addReplyGopherItem(c,"i","Redis Gopher server",NULL,NULL,0);
} else {
addReply(c,o);
}
/* Cleanup, also make sure to emit the final ".CRLF" line. Note that
* the connection will be closed immediately after this because the client
* will be flagged with CLIENT_CLOSE_AFTER_REPLY, in accordance with the
* Gopher protocol. */
if (c->argc == 0) decrRefCount(keyname);
/* Note that in theory we should terminate the Gopher request with
* ".<CR><LF>" (called Lastline in the RFC) like that:
*
* addReplyProto(c,".\r\n",3);
*
* However after examining the current clients landscape, it's probably
* going to do more harm than good for several reasons:
*
* 1. Clients should not have any issue with missing .<CR><LF> as for
* specification, and in the real world indeed certain servers
* implementations never used to send the terminator.
*
* 2. Redis does not know if it's serving a text file or a binary file:
* at the same time clients will not remove the ".<CR><LF>" bytes at
* tne end when downloading a binary file from the server, so adding
* the "Lastline" terminator without knowing the content is just
* dangerous.
*
* 3. The utility gopher2redis.rb that we provide for Redis, and any
* other similar tool you may use as Gopher authoring system for
* Redis, can just add the "Lastline" when needed.
*/
}
...@@ -636,23 +636,7 @@ void addReplyNullArray(client *c) { ...@@ -636,23 +636,7 @@ void addReplyNullArray(client *c) {
/* Create the length prefix of a bulk reply, example: $2234 */ /* Create the length prefix of a bulk reply, example: $2234 */
void addReplyBulkLen(client *c, robj *obj) { void addReplyBulkLen(client *c, robj *obj) {
size_t len; size_t len = stringObjectLen(obj);
if (sdsEncodedObject(obj)) {
len = sdslen(obj->ptr);
} else {
long n = (long)obj->ptr;
/* Compute how many bytes will take this integer as a radix 10 string */
len = 1;
if (n < 0) {
len++;
n = -n;
}
while((n = n/10) != 0) {
len++;
}
}
if (len < OBJ_SHARED_BULKHDR_LEN) if (len < OBJ_SHARED_BULKHDR_LEN)
addReply(c,shared.bulkhdr[len]); addReply(c,shared.bulkhdr[len]);
...@@ -1564,6 +1548,17 @@ void processInputBuffer(client *c) { ...@@ -1564,6 +1548,17 @@ void processInputBuffer(client *c) {
if (c->reqtype == PROTO_REQ_INLINE) { if (c->reqtype == PROTO_REQ_INLINE) {
if (processInlineBuffer(c) != C_OK) break; if (processInlineBuffer(c) != C_OK) break;
/* If the Gopher mode and we got zero or one argument, process
* the request in Gopher mode. */
if (server.gopher_enabled &&
((c->argc == 1 && ((char*)(c->argv[0]->ptr))[0] == '/') ||
c->argc == 0))
{
processGopherRequest(c);
resetClient(c);
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
break;
}
} else if (c->reqtype == PROTO_REQ_MULTIBULK) { } else if (c->reqtype == PROTO_REQ_MULTIBULK) {
if (processMultibulkBuffer(c) != C_OK) break; if (processMultibulkBuffer(c) != C_OK) break;
} else { } else {
...@@ -1819,6 +1814,45 @@ sds getAllClientsInfoString(int type) { ...@@ -1819,6 +1814,45 @@ sds getAllClientsInfoString(int type) {
return o; return o;
} }
/* This function implements CLIENT SETNAME, including replying to the
* user with an error if the charset is wrong (in that case C_ERR is
* returned). If the function succeeeded C_OK is returned, and it's up
* to the caller to send a reply if needed.
*
* Setting an empty string as name has the effect of unsetting the
* currently set name: the client will remain unnamed.
*
* This function is also used to implement the HELLO SETNAME option. */
int clientSetNameOrReply(client *c, robj *name) {
int len = sdslen(name->ptr);
char *p = name->ptr;
/* Setting the client name to an empty string actually removes
* the current name. */
if (len == 0) {
if (c->name) decrRefCount(c->name);
c->name = NULL;
addReply(c,shared.ok);
return C_OK;
}
/* Otherwise check if the charset is ok. We need to do this otherwise
* CLIENT LIST format will break. You should always be able to
* split by space to get the different fields. */
for (int j = 0; j < len; j++) {
if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
addReplyError(c,
"Client names cannot contain spaces, "
"newlines or special characters.");
return C_ERR;
}
}
if (c->name) decrRefCount(c->name);
c->name = name;
incrRefCount(name);
return C_OK;
}
void clientCommand(client *c) { void clientCommand(client *c) {
listNode *ln; listNode *ln;
listIter li; listIter li;
...@@ -1995,32 +2029,7 @@ NULL ...@@ -1995,32 +2029,7 @@ NULL
addReply(c,shared.czero); addReply(c,shared.czero);
} }
} else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) {
int j, len = sdslen(c->argv[2]->ptr); if (clientSetNameOrReply(c,c->argv[2]) == C_OK)
char *p = c->argv[2]->ptr;
/* Setting the client name to an empty string actually removes
* the current name. */
if (len == 0) {
if (c->name) decrRefCount(c->name);
c->name = NULL;
addReply(c,shared.ok);
return;
}
/* Otherwise check if the charset is ok. We need to do this otherwise
* CLIENT LIST format will break. You should always be able to
* split by space to get the different fields. */
for (j = 0; j < len; j++) {
if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
addReplyError(c,
"Client names cannot contain spaces, "
"newlines or special characters.");
return;
}
}
if (c->name) decrRefCount(c->name);
c->name = c->argv[2];
incrRefCount(c->name);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"getname") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"getname") && c->argc == 2) {
if (c->name) if (c->name)
...@@ -2039,7 +2048,7 @@ NULL ...@@ -2039,7 +2048,7 @@ NULL
} }
} }
/* HELLO <protocol-version> [AUTH <user> <password>] */ /* HELLO <protocol-version> [AUTH <user> <password>] [SETNAME <name>] */
void helloCommand(client *c) { void helloCommand(client *c) {
long long ver; long long ver;
...@@ -2050,12 +2059,23 @@ void helloCommand(client *c) { ...@@ -2050,12 +2059,23 @@ void helloCommand(client *c) {
return; return;
} }
/* Switching to protocol v2 is not allowed. But we send a specific for (int j = 2; j < c->argc; j++) {
* error message in this case. */ int moreargs = (c->argc-1) - j;
if (ver == 2) { const char *opt = c->argv[j]->ptr;
addReplyError(c,"Switching to RESP version 2 is not allowed."); if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) {
addReplyError(c,"-WRONGPASS invalid username-password pair");
return; return;
} }
j += 2;
} else if (!strcasecmp(opt,"SETNAME") && moreargs) {
if (clientSetNameOrReply(c, c->argv[j+1]) == C_ERR) return;
j++;
} else {
addReplyErrorFormat(c,"Syntax error in HELLO option '%s'",opt);
return;
}
}
/* At this point we need to be authenticated to continue. */ /* At this point we need to be authenticated to continue. */
if (!c->authenticated) { if (!c->authenticated) {
...@@ -2066,8 +2086,8 @@ void helloCommand(client *c) { ...@@ -2066,8 +2086,8 @@ void helloCommand(client *c) {
return; return;
} }
/* Let's switch to RESP3 mode. */ /* Let's switch to the specified RESP mode. */
c->resp = 3; c->resp = ver;
addReplyMapLen(c,7); addReplyMapLen(c,7);
addReplyBulkCString(c,"server"); addReplyBulkCString(c,"server");
......
...@@ -1963,6 +1963,23 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1963,6 +1963,23 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
"Can't load Lua script from RDB file! " "Can't load Lua script from RDB file! "
"BODY: %s", auxval->ptr); "BODY: %s", auxval->ptr);
} }
} else if (!strcasecmp(auxkey->ptr,"redis-ver")) {
serverLog(LL_NOTICE,"Loading RDB produced by version %s",
auxval->ptr);
} else if (!strcasecmp(auxkey->ptr,"ctime")) {
time_t age = time(NULL)-strtol(auxval->ptr,NULL,10);
if (age < 0) age = 0;
serverLog(LL_NOTICE,"RDB age %ld seconds",
(unsigned long) age);
} else if (!strcasecmp(auxkey->ptr,"used-mem")) {
long long usedmem = strtoll(auxval->ptr,NULL,10);
serverLog(LL_NOTICE,"RDB memory usage when created %.2f Mb",
(double) usedmem / (1024*1024));
} else if (!strcasecmp(auxkey->ptr,"aof-preamble")) {
long long haspreamble = strtoll(auxval->ptr,NULL,10);
if (haspreamble) serverLog(LL_NOTICE,"RDB has an AOF tail");
} else if (!strcasecmp(auxkey->ptr,"redis-bits")) {
/* Just ignored. */
} else { } else {
/* We ignore fields we don't understand, as by AUX field /* We ignore fields we don't understand, as by AUX field
* contract. */ * contract. */
......
This diff is collapsed.
This diff is collapsed.
...@@ -2038,6 +2038,15 @@ void replicaofCommand(client *c) { ...@@ -2038,6 +2038,15 @@ void replicaofCommand(client *c) {
} else { } else {
long port; long port;
if (c->flags & CLIENT_SLAVE)
{
/* If a client is already a replica they cannot run this command,
* because it involves flushing all replicas (including this
* client) */
addReplyError(c, "Command is not valid when client is a replica.");
return;
}
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK)) if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
return; return;
......
...@@ -2222,6 +2222,7 @@ void initServerConfig(void) { ...@@ -2222,6 +2222,7 @@ void initServerConfig(void) {
server.ipfd_count = 0; server.ipfd_count = 0;
server.sofd = -1; server.sofd = -1;
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE; server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
server.dbnum = CONFIG_DEFAULT_DBNUM; server.dbnum = CONFIG_DEFAULT_DBNUM;
server.verbosity = CONFIG_DEFAULT_VERBOSITY; server.verbosity = CONFIG_DEFAULT_VERBOSITY;
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT; server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
...@@ -2610,7 +2611,7 @@ int listenToPort(int port, int *fds, int *count) { ...@@ -2610,7 +2611,7 @@ int listenToPort(int port, int *fds, int *count) {
(*count)++; (*count)++;
} else if (errno == EAFNOSUPPORT) { } else if (errno == EAFNOSUPPORT) {
unsupported++; unsupported++;
serverLog(LL_WARNING,"Not listening to IPv6: unsupproted"); serverLog(LL_WARNING,"Not listening to IPv6: unsupported");
} }
if (*count == 1 || unsupported) { if (*count == 1 || unsupported) {
...@@ -2622,7 +2623,7 @@ int listenToPort(int port, int *fds, int *count) { ...@@ -2622,7 +2623,7 @@ int listenToPort(int port, int *fds, int *count) {
(*count)++; (*count)++;
} else if (errno == EAFNOSUPPORT) { } else if (errno == EAFNOSUPPORT) {
unsupported++; unsupported++;
serverLog(LL_WARNING,"Not listening to IPv4: unsupproted"); serverLog(LL_WARNING,"Not listening to IPv4: unsupported");
} }
} }
/* Exit the loop if we were able to bind * on IPv4 and IPv6, /* Exit the loop if we were able to bind * on IPv4 and IPv6,
...@@ -3599,51 +3600,6 @@ int writeCommandsDeniedByDiskError(void) { ...@@ -3599,51 +3600,6 @@ int writeCommandsDeniedByDiskError(void) {
} }
} }
/* 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 (ACLCheckUserCredentials(username,password) == C_OK) {
c->authenticated = 1;
c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
addReply(c,shared.ok);
} else {
addReplyError(c,"-WRONGPASS invalid username-password pair");
}
/* Free the "default" string object we created for the two
* arguments form. */
if (c->argc == 2) decrRefCount(username);
}
/* The PING command. It works in a different way if the client is in /* The PING command. It works in a different way if the client is in
* in Pub/Sub mode. */ * in Pub/Sub mode. */
void pingCommand(client *c) { void pingCommand(client *c) {
...@@ -4600,7 +4556,7 @@ void loadDataFromDisk(void) { ...@@ -4600,7 +4556,7 @@ void loadDataFromDisk(void) {
(float)(ustime()-start)/1000000); (float)(ustime()-start)/1000000);
/* Restore the replication ID / offset from the RDB file. */ /* Restore the replication ID / offset from the RDB file. */
if (server.masterhost && if ((server.masterhost || (server.cluster_enabled && nodeIsSlave(server.cluster->myself)))&&
rsi.repl_id_is_set && rsi.repl_id_is_set &&
rsi.repl_offset != -1 && rsi.repl_offset != -1 &&
/* Note that older implementations may save a repl_stream_db /* Note that older implementations may save a repl_stream_db
......
...@@ -121,6 +121,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -121,6 +121,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0 #define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
#define CONFIG_DEFAULT_TCP_KEEPALIVE 300 #define CONFIG_DEFAULT_TCP_KEEPALIVE 300
#define CONFIG_DEFAULT_PROTECTED_MODE 1 #define CONFIG_DEFAULT_PROTECTED_MODE 1
#define CONFIG_DEFAULT_GOPHER_ENABLED 0
#define CONFIG_DEFAULT_LOGFILE "" #define CONFIG_DEFAULT_LOGFILE ""
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0 #define CONFIG_DEFAULT_SYSLOG_ENABLED 0
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1 #define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
...@@ -1054,6 +1055,8 @@ struct redisServer { ...@@ -1054,6 +1055,8 @@ struct redisServer {
dict *migrate_cached_sockets;/* MIGRATE cached sockets */ dict *migrate_cached_sockets;/* MIGRATE cached sockets */
uint64_t next_client_id; /* Next client unique ID. Incremental. */ uint64_t next_client_id; /* Next client unique ID. Incremental. */
int protected_mode; /* Don't accept external connections. */ int protected_mode; /* Don't accept external connections. */
int gopher_enabled; /* If true the server will reply to gopher
queries. Will still serve RESP2 queries. */
/* RDB / AOF loading information */ /* RDB / AOF loading information */
int loading; /* We are loading data from disk if true */ int loading; /* We are loading data from disk if true */
off_t loading_total_bytes; off_t loading_total_bytes;
...@@ -1511,6 +1514,7 @@ void setDeferredAttributeLen(client *c, void *node, long length); ...@@ -1511,6 +1514,7 @@ void setDeferredAttributeLen(client *c, void *node, long length);
void setDeferredPushLen(client *c, void *node, long length); void setDeferredPushLen(client *c, void *node, long length);
void processInputBuffer(client *c); void processInputBuffer(client *c);
void processInputBufferAndReplicate(client *c); void processInputBufferAndReplicate(client *c);
void processGopherRequest(client *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
...@@ -1737,6 +1741,7 @@ void ACLInit(void); ...@@ -1737,6 +1741,7 @@ void ACLInit(void);
#define ACL_DENIED_CMD 1 #define ACL_DENIED_CMD 1
#define ACL_DENIED_KEY 2 #define ACL_DENIED_KEY 2
int ACLCheckUserCredentials(robj *username, robj *password); int ACLCheckUserCredentials(robj *username, robj *password);
int ACLAuthenticateUser(client *c, robj *username, robj *password);
unsigned long ACLGetCommandID(const char *cmdname); unsigned long ACLGetCommandID(const char *cmdname);
user *ACLGetUserByName(const char *name, size_t namelen); user *ACLGetUserByName(const char *name, size_t namelen);
int ACLCheckCommandPerm(client *c); int ACLCheckCommandPerm(client *c);
...@@ -1882,7 +1887,6 @@ void setTypeConvert(robj *subject, int enc); ...@@ -1882,7 +1887,6 @@ void setTypeConvert(robj *subject, int enc);
void hashTypeConvert(robj *o, int enc); void hashTypeConvert(robj *o, int enc);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end); void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);
int hashTypeExists(robj *o, sds key); int hashTypeExists(robj *o, sds key);
int hashTypeDelete(robj *o, sds key); int hashTypeDelete(robj *o, sds key);
unsigned long hashTypeLength(const robj *o); unsigned long hashTypeLength(const robj *o);
......
...@@ -207,7 +207,7 @@ sds setTypeNextObject(setTypeIterator *si) { ...@@ -207,7 +207,7 @@ sds setTypeNextObject(setTypeIterator *si) {
* used field with values which are easy to trap if misused. */ * used field with values which are easy to trap if misused. */
int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) { int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
if (setobj->encoding == OBJ_ENCODING_HT) { if (setobj->encoding == OBJ_ENCODING_HT) {
dictEntry *de = dictGetRandomKey(setobj->ptr); dictEntry *de = dictGetFairRandomKey(setobj->ptr);
*sdsele = dictGetKey(de); *sdsele = dictGetKey(de);
*llele = -123456789; /* Not needed. Defensive. */ *llele = -123456789; /* Not needed. Defensive. */
} else if (setobj->encoding == OBJ_ENCODING_INTSET) { } else if (setobj->encoding == OBJ_ENCODING_INTSET) {
......
...@@ -21,8 +21,9 @@ int main(int argc, char **argv) { ...@@ -21,8 +21,9 @@ int main(int argc, char **argv) {
} }
srand(time(NULL)); srand(time(NULL));
char *filename = argv[1];
cycles = atoi(argv[2]); cycles = atoi(argv[2]);
fd = open("dump.rdb",O_RDWR); fd = open(filename,O_RDWR);
if (fd == -1) { if (fd == -1) {
perror("open"); perror("open");
exit(1); exit(1);
......
This utility plots the distribution of SRANDMEMBER to evaluate how fair it is. The utilities in this directory plot the distribution of SRANDMEMBER to
See http://theshfl.com/redis_sets for more information on the topic. evaluate how fair it is.
See http://theshfl.com/redis_sets for more information on the topic that lead
to such investigation fix.
showdist.rb -- shows the distribution of the frequency elements are returned.
The x axis is the number of times elements were returned, and
the y axis is how many elements were returned with such
frequency.
showfreq.rb -- shows the frequency each element was returned.
The x axis is the element number.
The y axis is the times it was returned.
require 'redis'
r = Redis.new
r.select(9)
r.del("myset");
r.sadd("myset",(0..999).to_a)
freq = {}
500.times {
res = r.pipelined {
1000.times {
r.srandmember("myset")
}
}
res.each{|ele|
freq[ele] = 0 if freq[ele] == nil
freq[ele] += 1
}
}
# Print the frequency each element was yeld to process it with gnuplot
freq.each{|item,count|
puts "#{item} #{count}"
}
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