redict/src/config.c

1798 lines
74 KiB
C

/* Configuration file parsing and CONFIG GET/SET commands implementation.
*
* Copyright (c) 2009-2012, 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 "redis.h"
#include "cluster.h"
#include <fcntl.h>
#include <sys/stat.h>
static struct {
const char *name;
const int value;
} validSyslogFacilities[] = {
{"user", LOG_USER},
{"local0", LOG_LOCAL0},
{"local1", LOG_LOCAL1},
{"local2", LOG_LOCAL2},
{"local3", LOG_LOCAL3},
{"local4", LOG_LOCAL4},
{"local5", LOG_LOCAL5},
{"local6", LOG_LOCAL6},
{"local7", LOG_LOCAL7},
{NULL, 0}
};
clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_LIMIT_NUM_CLASSES] = {
{0, 0, 0}, /* normal */
{1024*1024*256, 1024*1024*64, 60}, /* slave */
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
};
/*-----------------------------------------------------------------------------
* Config file parsing
*----------------------------------------------------------------------------*/
int yesnotoi(char *s) {
if (!strcasecmp(s,"yes")) return 1;
else if (!strcasecmp(s,"no")) return 0;
else return -1;
}
void appendServerSaveParams(time_t seconds, int changes) {
server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1));
server.saveparams[server.saveparamslen].seconds = seconds;
server.saveparams[server.saveparamslen].changes = changes;
server.saveparamslen++;
}
void resetServerSaveParams() {
zfree(server.saveparams);
server.saveparams = NULL;
server.saveparamslen = 0;
}
void loadServerConfigFromString(char *config) {
char *err = NULL;
int linenum = 0, totlines, i;
sds *lines;
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
for (i = 0; i < totlines; i++) {
sds *argv;
int argc;
linenum = i+1;
lines[i] = sdstrim(lines[i]," \t\r\n");
/* Skip comments and blank lines */
if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
/* Split into arguments */
argv = sdssplitargs(lines[i],&argc);
if (argv == NULL) {
err = "Unbalanced quotes in configuration line";
goto loaderr;
}
/* Skip this line if the resulting command vector is empty. */
if (argc == 0) {
sdsfreesplitres(argv,argc);
continue;
}
sdstolower(argv[0]);
/* Execute config directives */
if (!strcasecmp(argv[0],"timeout") && argc == 2) {
server.maxidletime = atoi(argv[1]);
if (server.maxidletime < 0) {
err = "Invalid timeout value"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"tcp-keepalive") && argc == 2) {
server.tcpkeepalive = atoi(argv[1]);
if (server.tcpkeepalive < 0) {
err = "Invalid tcp-keepalive value"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
server.port = atoi(argv[1]);
if (server.port < 0 || server.port > 65535) {
err = "Invalid port"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"bind") && argc >= 2) {
int j, addresses = argc-1;
if (addresses > REDIS_BINDADDR_MAX) {
err = "Too many bind addresses specified"; goto loaderr;
}
for (j = 0; j < addresses; j++)
server.bindaddr[j] = zstrdup(argv[j+1]);
server.bindaddr_count = addresses;
} else if (!strcasecmp(argv[0],"unixsocket") && argc == 2) {
server.unixsocket = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
errno = 0;
server.unixsocketperm = (mode_t)strtol(argv[1], NULL, 8);
if (errno || server.unixsocketperm > 0777) {
err = "Invalid socket file permissions"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"save")) {
if (argc == 3) {
int seconds = atoi(argv[1]);
int changes = atoi(argv[2]);
if (seconds < 1 || changes < 0) {
err = "Invalid save parameters"; goto loaderr;
}
appendServerSaveParams(seconds,changes);
} else if (argc == 2 && !strcasecmp(argv[1],"")) {
resetServerSaveParams();
}
} else if (!strcasecmp(argv[0],"dir") && argc == 2) {
if (chdir(argv[1]) == -1) {
redisLog(REDIS_WARNING,"Can't chdir to '%s': %s",
argv[1], strerror(errno));
exit(1);
}
} else if (!strcasecmp(argv[0],"loglevel") && argc == 2) {
if (!strcasecmp(argv[1],"debug")) server.verbosity = REDIS_DEBUG;
else if (!strcasecmp(argv[1],"verbose")) server.verbosity = REDIS_VERBOSE;
else if (!strcasecmp(argv[1],"notice")) server.verbosity = REDIS_NOTICE;
else if (!strcasecmp(argv[1],"warning")) server.verbosity = REDIS_WARNING;
else {
err = "Invalid log level. Must be one of debug, notice, warning";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
FILE *logfp;
zfree(server.logfile);
server.logfile = zstrdup(argv[1]);
if (server.logfile[0] != '\0') {
/* Test if we are able to open the file. The server will not
* be able to abort just for this problem later... */
logfp = fopen(server.logfile,"a");
if (logfp == NULL) {
err = sdscatprintf(sdsempty(),
"Can't open the log file: %s", strerror(errno));
goto loaderr;
}
fclose(logfp);
}
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"syslog-ident") && argc == 2) {
if (server.syslog_ident) zfree(server.syslog_ident);
server.syslog_ident = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
int i;
for (i = 0; validSyslogFacilities[i].name; i++) {
if (!strcasecmp(validSyslogFacilities[i].name, argv[1])) {
server.syslog_facility = validSyslogFacilities[i].value;
break;
}
}
if (!validSyslogFacilities[i].name) {
err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"databases") && argc == 2) {
server.dbnum = atoi(argv[1]);
if (server.dbnum < 1) {
err = "Invalid number of databases"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1],NULL);
} else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
server.maxclients = atoi(argv[1]);
if (server.maxclients < 1) {
err = "Invalid max clients limit"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
server.maxmemory = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
if (!strcasecmp(argv[1],"volatile-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
} else if (!strcasecmp(argv[1],"volatile-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
} else if (!strcasecmp(argv[1],"volatile-ttl")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
} else if (!strcasecmp(argv[1],"allkeys-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
} else if (!strcasecmp(argv[1],"allkeys-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
} else if (!strcasecmp(argv[1],"noeviction")) {
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
} else {
err = "Invalid maxmemory policy";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"maxmemory-samples") && argc == 2) {
server.maxmemory_samples = atoi(argv[1]);
if (server.maxmemory_samples <= 0) {
err = "maxmemory-samples must be 1 or greater";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
server.masterhost = sdsnew(argv[1]);
server.masterport = atoi(argv[2]);
server.repl_state = REDIS_REPL_CONNECT;
} else if (!strcasecmp(argv[0],"repl-ping-slave-period") && argc == 2) {
server.repl_ping_slave_period = atoi(argv[1]);
if (server.repl_ping_slave_period <= 0) {
err = "repl-ping-slave-period must be 1 or greater";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) {
server.repl_timeout = atoi(argv[1]);
if (server.repl_timeout <= 0) {
err = "repl-timeout must be 1 or greater";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"repl-disable-tcp-nodelay") && argc==2) {
if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"repl-backlog-size") && argc == 2) {
long long size = memtoll(argv[1],NULL);
if (size <= 0) {
err = "repl-backlog-size must be 1 or greater.";
goto loaderr;
}
resizeReplicationBacklog(size);
} else if (!strcasecmp(argv[0],"repl-backlog-ttl") && argc == 2) {
server.repl_backlog_time_limit = atoi(argv[1]);
if (server.repl_backlog_time_limit < 0) {
err = "repl-backlog-ttl can't be negative ";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
server.masterauth = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"slave-read-only") && argc == 2) {
if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"hz") && argc == 2) {
server.hz = atoi(argv[1]);
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
int yes;
if ((yes = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
server.aof_state = yes ? REDIS_AOF_ON : REDIS_AOF_OFF;
} else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
if (!pathIsBaseName(argv[1])) {
err = "appendfilename can't be a path, just a filename";
goto loaderr;
}
zfree(server.aof_filename);
server.aof_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
&& argc == 2) {
if ((server.aof_no_fsync_on_rewrite= yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
if (!strcasecmp(argv[1],"no")) {
server.aof_fsync = AOF_FSYNC_NO;
} else if (!strcasecmp(argv[1],"always")) {
server.aof_fsync = AOF_FSYNC_ALWAYS;
} else if (!strcasecmp(argv[1],"everysec")) {
server.aof_fsync = AOF_FSYNC_EVERYSEC;
} else {
err = "argument must be 'no', 'always' or 'everysec'";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"auto-aof-rewrite-percentage") &&
argc == 2)
{
server.aof_rewrite_perc = atoi(argv[1]);
if (server.aof_rewrite_perc < 0) {
err = "Invalid negative percentage for AOF auto rewrite";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"auto-aof-rewrite-min-size") &&
argc == 2)
{
server.aof_rewrite_min_size = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
argc == 2)
{
if ((server.aof_rewrite_incremental_fsync = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
if (strlen(argv[1]) > REDIS_AUTHPASS_MAX_LEN) {
err = "Password is longer than REDIS_AUTHPASS_MAX_LEN";
goto loaderr;
}
server.requirepass = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
zfree(server.pidfile);
server.pidfile = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
if (!pathIsBaseName(argv[1])) {
err = "dbfilename can't be a path, just a filename";
goto loaderr;
}
zfree(server.rdb_filename);
server.rdb_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
server.hash_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
server.list_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
server.set_max_intset_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
server.zset_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
struct redisCommand *cmd = lookupCommand(argv[1]);
int retval;
if (!cmd) {
err = "No such command in rename-command";
goto loaderr;
}
/* If the target command name is the empty string we just
* remove it from the command table. */
retval = dictDelete(server.commands, argv[1]);
redisAssert(retval == DICT_OK);
/* Otherwise we re-add the command under a different name. */
if (sdslen(argv[2]) != 0) {
sds copy = sdsdup(argv[2]);
retval = dictAdd(server.commands, copy, cmd);
if (retval != DICT_OK) {
sdsfree(copy);
err = "Target command name already exists"; goto loaderr;
}
}
} else if (!strcasecmp(argv[0],"cluster-enabled") && argc == 2) {
if ((server.cluster_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
zfree(server.cluster_configfile);
server.cluster_configfile = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
server.cluster_node_timeout = atoi(argv[1]);
if (server.cluster_node_timeout <= 0) {
err = "cluster node timeout must be 1 or greater"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
server.lua_time_limit = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
argc == 2)
{
server.slowlog_log_slower_than = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
server.slowlog_max_len = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
argc == 5)
{
int class = getClientLimitClassByName(argv[1]);
unsigned long long hard, soft;
int soft_seconds;
if (class == -1) {
err = "Unrecognized client limit class";
goto loaderr;
}
hard = memtoll(argv[2],NULL);
soft = memtoll(argv[3],NULL);
soft_seconds = atoi(argv[4]);
if (soft_seconds < 0) {
err = "Negative number of seconds in soft limit is invalid";
goto loaderr;
}
server.client_obuf_limits[class].hard_limit_bytes = hard;
server.client_obuf_limits[class].soft_limit_bytes = soft;
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
} else if (!strcasecmp(argv[0],"stop-writes-on-bgsave-error") &&
argc == 2) {
if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) {
server.slave_priority = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) {
server.repl_min_slaves_to_write = atoi(argv[1]);
if (server.repl_min_slaves_to_write < 0) {
err = "Invalid value for min-slaves-to-write."; goto loaderr;
}
} else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) {
server.repl_min_slaves_max_lag = atoi(argv[1]);
if (server.repl_min_slaves_max_lag < 0) {
err = "Invalid value for min-slaves-max-lag."; goto loaderr;
}
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
int flags = keyspaceEventsStringToFlags(argv[1]);
if (flags == -1) {
err = "Invalid event class character. Use 'g$lshzxeA'.";
goto loaderr;
}
server.notify_keyspace_events = flags;
} else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */
if (argc != 1) {
if (!server.sentinel_mode) {
err = "sentinel directive while not in sentinel mode";
goto loaderr;
}
err = sentinelHandleConfiguration(argv+1,argc-1);
if (err) goto loaderr;
}
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
sdsfreesplitres(argv,argc);
}
sdsfreesplitres(lines,totlines);
/* Sanity checks. */
if (server.cluster_enabled && server.masterhost) {
err = "slaveof directive not allowed in cluster mode";
goto loaderr;
}
return;
loaderr:
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
fprintf(stderr, ">>> '%s'\n", lines[i]);
fprintf(stderr, "%s\n", err);
exit(1);
}
/* Load the server configuration from the specified filename.
* The function appends the additional configuration directives stored
* in the 'options' string to the config file before loading.
*
* Both filename and options can be NULL, in such a case are considered
* empty. This way loadServerConfig can be used to just load a file or
* just load a string. */
void loadServerConfig(char *filename, char *options) {
sds config = sdsempty();
char buf[REDIS_CONFIGLINE_MAX+1];
/* Load the file content */
if (filename) {
FILE *fp;
if (filename[0] == '-' && filename[1] == '\0') {
fp = stdin;
} else {
if ((fp = fopen(filename,"r")) == NULL) {
redisLog(REDIS_WARNING,
"Fatal error, can't open config file '%s'", filename);
exit(1);
}
}
while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL)
config = sdscat(config,buf);
if (fp != stdin) fclose(fp);
}
/* Append the additional options */
if (options) {
config = sdscat(config,"\n");
config = sdscat(config,options);
}
loadServerConfigFromString(config);
sdsfree(config);
}
/*-----------------------------------------------------------------------------
* CONFIG SET implementation
*----------------------------------------------------------------------------*/
void configSetCommand(redisClient *c) {
robj *o;
long long ll;
redisAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
redisAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3];
if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
if (!pathIsBaseName(o->ptr)) {
addReplyError(c, "dbfilename can't be a path, just a filename");
return;
}
zfree(server.rdb_filename);
server.rdb_filename = zstrdup(o->ptr);
} else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) {
if (sdslen(o->ptr) > REDIS_AUTHPASS_MAX_LEN) goto badfmt;
zfree(server.requirepass);
server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) {
zfree(server.masterauth);
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
server.maxmemory = ll;
if (server.maxmemory) {
if (server.maxmemory < zmalloc_used_memory()) {
redisLog(REDIS_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.");
}
freeMemoryIfNeeded();
}
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
int orig_value = server.maxclients;
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
/* Try to check if the OS is capable of supporting so many FDs. */
server.maxclients = ll;
if (ll > orig_value) {
adjustOpenFilesLimit();
if (server.maxclients != ll) {
addReplyErrorFormat(c,"The operating system is not able to handle the specified number of clients, try with %d", server.maxclients);
server.maxclients = orig_value;
return;
}
if (aeGetSetSize(server.el) <
server.maxclients + REDIS_EVENTLOOP_FDSET_INCR)
{
if (aeResizeSetSize(server.el,
server.maxclients + REDIS_EVENTLOOP_FDSET_INCR) == AE_ERR)
{
addReplyError(c,"The event loop API used by Redis is not able to handle the specified number of clients");
server.maxclients = orig_value;
return;
}
}
}
} else if (!strcasecmp(c->argv[2]->ptr,"hz")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hz = ll;
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
if (!strcasecmp(o->ptr,"volatile-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
} else if (!strcasecmp(o->ptr,"volatile-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
} else if (!strcasecmp(o->ptr,"volatile-ttl")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
} else if (!strcasecmp(o->ptr,"allkeys-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
} else if (!strcasecmp(o->ptr,"allkeys-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
} else if (!strcasecmp(o->ptr,"noeviction")) {
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll <= 0) goto badfmt;
server.maxmemory_samples = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > LONG_MAX) goto badfmt;
server.maxidletime = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"tcp-keepalive")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > INT_MAX) goto badfmt;
server.tcpkeepalive = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
if (!strcasecmp(o->ptr,"no")) {
server.aof_fsync = AOF_FSYNC_NO;
} else if (!strcasecmp(o->ptr,"everysec")) {
server.aof_fsync = AOF_FSYNC_EVERYSEC;
} else if (!strcasecmp(o->ptr,"always")) {
server.aof_fsync = AOF_FSYNC_ALWAYS;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"no-appendfsync-on-rewrite")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.aof_no_fsync_on_rewrite = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {
int enable = yesnotoi(o->ptr);
if (enable == -1) goto badfmt;
if (enable == 0 && server.aof_state != REDIS_AOF_OFF) {
stopAppendOnly();
} else if (enable && server.aof_state == REDIS_AOF_OFF) {
if (startAppendOnly() == REDIS_ERR) {
addReplyError(c,
"Unable to turn on AOF. Check server logs.");
return;
}
}
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-percentage")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.aof_rewrite_perc = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-min-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.aof_rewrite_min_size = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"aof-rewrite-incremental-fsync")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.aof_rewrite_incremental_fsync = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
/* Perform sanity check before setting the new config:
* - Even number of args
* - Seconds >= 1, changes >= 0 */
if (vlen & 1) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
for (j = 0; j < vlen; j++) {
char *eptr;
long val;
val = strtoll(v[j], &eptr, 10);
if (eptr[0] != '\0' ||
((j & 1) == 0 && val < 1) ||
((j & 1) == 1 && val < 0)) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
}
/* Finally set the new config */
resetServerSaveParams();
for (j = 0; j < vlen; j += 2) {
time_t seconds;
int changes;
seconds = strtoll(v[j],NULL,10);
changes = strtoll(v[j+1],NULL,10);
appendServerSaveParams(seconds, changes);
}
sdsfreesplitres(v,vlen);
} else if (!strcasecmp(c->argv[2]->ptr,"slave-serve-stale-data")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.repl_serve_stale_data = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"slave-read-only")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.repl_slave_ro = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"dir")) {
if (chdir((char*)o->ptr) == -1) {
addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
return;
}
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.set_max_intset_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.zset_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.zset_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.lua_time_limit = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-log-slower-than")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR) goto badfmt;
server.slowlog_log_slower_than = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.slowlog_max_len = (unsigned)ll;
} else if (!strcasecmp(c->argv[2]->ptr,"loglevel")) {
if (!strcasecmp(o->ptr,"warning")) {
server.verbosity = REDIS_WARNING;
} else if (!strcasecmp(o->ptr,"notice")) {
server.verbosity = REDIS_NOTICE;
} else if (!strcasecmp(o->ptr,"verbose")) {
server.verbosity = REDIS_VERBOSE;
} else if (!strcasecmp(o->ptr,"debug")) {
server.verbosity = REDIS_DEBUG;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"client-output-buffer-limit")) {
int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
/* We need a multiple of 4: <class> <hard> <soft> <soft_seconds> */
if (vlen % 4) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
/* Sanity check of single arguments, so that we either refuse the
* whole configuration string or accept it all, even if a single
* error in a single client class is present. */
for (j = 0; j < vlen; j++) {
char *eptr;
long val;
if ((j % 4) == 0) {
if (getClientLimitClassByName(v[j]) == -1) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
} else {
val = strtoll(v[j], &eptr, 10);
if (eptr[0] != '\0' || val < 0) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
}
}
/* Finally set the new config */
for (j = 0; j < vlen; j += 4) {
int class;
unsigned long long hard, soft;
int soft_seconds;
class = getClientLimitClassByName(v[j]);
hard = strtoll(v[j+1],NULL,10);
soft = strtoll(v[j+2],NULL,10);
soft_seconds = strtoll(v[j+3],NULL,10);
server.client_obuf_limits[class].hard_limit_bytes = hard;
server.client_obuf_limits[class].soft_limit_bytes = soft;
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
}
sdsfreesplitres(v,vlen);
} else if (!strcasecmp(c->argv[2]->ptr,"stop-writes-on-bgsave-error")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.stop_writes_on_bgsave_err = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-ping-slave-period")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
server.repl_ping_slave_period = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
server.repl_timeout = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
resizeReplicationBacklog(ll);
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.repl_backlog_time_limit = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"watchdog-period")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
if (ll)
enableWatchdog(ll);
else
disableWatchdog();
} else if (!strcasecmp(c->argv[2]->ptr,"rdbcompression")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.rdb_compression = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
int flags = keyspaceEventsStringToFlags(o->ptr);
if (flags == -1) goto badfmt;
server.notify_keyspace_events = flags;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-disable-tcp-nodelay")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.repl_disable_tcp_nodelay = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"slave-priority")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
server.slave_priority = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-to-write")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
server.repl_min_slaves_to_write = ll;
refreshGoodSlavesCount();
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-max-lag")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
server.repl_min_slaves_max_lag = ll;
refreshGoodSlavesCount();
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-node-timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll <= 0) goto badfmt;
server.cluster_node_timeout = ll;
} else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr);
return;
}
addReply(c,shared.ok);
return;
badfmt: /* Bad format errors */
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
(char*)o->ptr,
(char*)c->argv[2]->ptr);
}
/*-----------------------------------------------------------------------------
* CONFIG GET implementation
*----------------------------------------------------------------------------*/
#define config_get_string_field(_name,_var) do { \
if (stringmatch(pattern,_name,0)) { \
addReplyBulkCString(c,_name); \
addReplyBulkCString(c,_var ? _var : ""); \
matches++; \
} \
} while(0);
#define config_get_bool_field(_name,_var) do { \
if (stringmatch(pattern,_name,0)) { \
addReplyBulkCString(c,_name); \
addReplyBulkCString(c,_var ? "yes" : "no"); \
matches++; \
} \
} while(0);
#define config_get_numerical_field(_name,_var) do { \
if (stringmatch(pattern,_name,0)) { \
ll2string(buf,sizeof(buf),_var); \
addReplyBulkCString(c,_name); \
addReplyBulkCString(c,buf); \
matches++; \
} \
} while(0);
void configGetCommand(redisClient *c) {
robj *o = c->argv[2];
void *replylen = addDeferredMultiBulkLength(c);
char *pattern = o->ptr;
char buf[128];
int matches = 0;
redisAssertWithInfo(c,o,sdsEncodedObject(o));
/* String values */
config_get_string_field("dbfilename",server.rdb_filename);
config_get_string_field("requirepass",server.requirepass);
config_get_string_field("masterauth",server.masterauth);
config_get_string_field("unixsocket",server.unixsocket);
config_get_string_field("logfile",server.logfile);
config_get_string_field("pidfile",server.pidfile);
/* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory);
config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
config_get_numerical_field("timeout",server.maxidletime);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
config_get_numerical_field("auto-aof-rewrite-percentage",
server.aof_rewrite_perc);
config_get_numerical_field("auto-aof-rewrite-min-size",
server.aof_rewrite_min_size);
config_get_numerical_field("hash-max-ziplist-entries",
server.hash_max_ziplist_entries);
config_get_numerical_field("hash-max-ziplist-value",
server.hash_max_ziplist_value);
config_get_numerical_field("list-max-ziplist-entries",
server.list_max_ziplist_entries);
config_get_numerical_field("list-max-ziplist-value",
server.list_max_ziplist_value);
config_get_numerical_field("set-max-intset-entries",
server.set_max_intset_entries);
config_get_numerical_field("zset-max-ziplist-entries",
server.zset_max_ziplist_entries);
config_get_numerical_field("zset-max-ziplist-value",
server.zset_max_ziplist_value);
config_get_numerical_field("lua-time-limit",server.lua_time_limit);
config_get_numerical_field("slowlog-log-slower-than",
server.slowlog_log_slower_than);
config_get_numerical_field("slowlog-max-len",
server.slowlog_max_len);
config_get_numerical_field("port",server.port);
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-timeout",server.repl_timeout);
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("maxclients",server.maxclients);
config_get_numerical_field("watchdog-period",server.watchdog_period);
config_get_numerical_field("slave-priority",server.slave_priority);
config_get_numerical_field("min-slaves-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("hz",server.hz);
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
/* Bool (yes/no) values */
config_get_bool_field("no-appendfsync-on-rewrite",
server.aof_no_fsync_on_rewrite);
config_get_bool_field("slave-serve-stale-data",
server.repl_serve_stale_data);
config_get_bool_field("slave-read-only",
server.repl_slave_ro);
config_get_bool_field("stop-writes-on-bgsave-error",
server.stop_writes_on_bgsave_err);
config_get_bool_field("daemonize", server.daemonize);
config_get_bool_field("rdbcompression", server.rdb_compression);
config_get_bool_field("rdbchecksum", server.rdb_checksum);
config_get_bool_field("activerehashing", server.activerehashing);
config_get_bool_field("repl-disable-tcp-nodelay",
server.repl_disable_tcp_nodelay);
config_get_bool_field("aof-rewrite-incremental-fsync",
server.aof_rewrite_incremental_fsync);
/* Everything we can't handle with macros follows. */
if (stringmatch(pattern,"appendonly",0)) {
addReplyBulkCString(c,"appendonly");
addReplyBulkCString(c,server.aof_state == REDIS_AOF_OFF ? "no" : "yes");
matches++;
}
if (stringmatch(pattern,"dir",0)) {
char buf[1024];
if (getcwd(buf,sizeof(buf)) == NULL)
buf[0] = '\0';
addReplyBulkCString(c,"dir");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"maxmemory-policy",0)) {
char *s;
switch(server.maxmemory_policy) {
case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break;
case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break;
case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break;
case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break;
case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break;
case REDIS_MAXMEMORY_NO_EVICTION: s = "noeviction"; break;
default: s = "unknown"; break; /* too harmless to panic */
}
addReplyBulkCString(c,"maxmemory-policy");
addReplyBulkCString(c,s);
matches++;
}
if (stringmatch(pattern,"appendfsync",0)) {
char *policy;
switch(server.aof_fsync) {
case AOF_FSYNC_NO: policy = "no"; break;
case AOF_FSYNC_EVERYSEC: policy = "everysec"; break;
case AOF_FSYNC_ALWAYS: policy = "always"; break;
default: policy = "unknown"; break; /* too harmless to panic */
}
addReplyBulkCString(c,"appendfsync");
addReplyBulkCString(c,policy);
matches++;
}
if (stringmatch(pattern,"save",0)) {
sds buf = sdsempty();
int j;
for (j = 0; j < server.saveparamslen; j++) {
buf = sdscatprintf(buf,"%jd %d",
(intmax_t)server.saveparams[j].seconds,
server.saveparams[j].changes);
if (j != server.saveparamslen-1)
buf = sdscatlen(buf," ",1);
}
addReplyBulkCString(c,"save");
addReplyBulkCString(c,buf);
sdsfree(buf);
matches++;
}
if (stringmatch(pattern,"loglevel",0)) {
char *s;
switch(server.verbosity) {
case REDIS_WARNING: s = "warning"; break;
case REDIS_VERBOSE: s = "verbose"; break;
case REDIS_NOTICE: s = "notice"; break;
case REDIS_DEBUG: s = "debug"; break;
default: s = "unknown"; break; /* too harmless to panic */
}
addReplyBulkCString(c,"loglevel");
addReplyBulkCString(c,s);
matches++;
}
if (stringmatch(pattern,"client-output-buffer-limit",0)) {
sds buf = sdsempty();
int j;
for (j = 0; j < REDIS_CLIENT_LIMIT_NUM_CLASSES; j++) {
buf = sdscatprintf(buf,"%s %llu %llu %ld",
getClientLimitClassName(j),
server.client_obuf_limits[j].hard_limit_bytes,
server.client_obuf_limits[j].soft_limit_bytes,
(long) server.client_obuf_limits[j].soft_limit_seconds);
if (j != REDIS_CLIENT_LIMIT_NUM_CLASSES-1)
buf = sdscatlen(buf," ",1);
}
addReplyBulkCString(c,"client-output-buffer-limit");
addReplyBulkCString(c,buf);
sdsfree(buf);
matches++;
}
if (stringmatch(pattern,"unixsocketperm",0)) {
char buf[32];
snprintf(buf,sizeof(buf),"%o",server.unixsocketperm);
addReplyBulkCString(c,"unixsocketperm");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"slaveof",0)) {
char buf[256];
addReplyBulkCString(c,"slaveof");
if (server.masterhost)
snprintf(buf,sizeof(buf),"%s %d",
server.masterhost, server.masterport);
else
buf[0] = '\0';
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"notify-keyspace-events",0)) {
robj *flagsobj = createObject(REDIS_STRING,
keyspaceEventsFlagsToString(server.notify_keyspace_events));
addReplyBulkCString(c,"notify-keyspace-events");
addReplyBulk(c,flagsobj);
decrRefCount(flagsobj);
matches++;
}
if (stringmatch(pattern,"bind",0)) {
sds aux = sdsjoin(server.bindaddr,server.bindaddr_count," ");
addReplyBulkCString(c,"bind");
addReplyBulkCString(c,aux);
sdsfree(aux);
matches++;
}
setDeferredMultiBulkLength(c,replylen,matches*2);
}
/*-----------------------------------------------------------------------------
* CONFIG REWRITE implementation
*----------------------------------------------------------------------------*/
/* IGNORE:
*
* rename-command
* include
*
* Special handling:
*
* notify-keyspace-events
* client-output-buffer-limit
* save
* appendonly
* appendfsync
* dir
* maxmemory-policy
* loglevel
* unixsocketperm
* slaveof
*
* Type of config directives:
*
* CUSTOM
* VERBATIM
* YESNO
* L
* LL
*
*/
/* We use the following dictionary type to store where a configuration
* option is mentioned in the old configuration file, so it's
* like "maxmemory" -> list of line numbers (first line is zero). */
unsigned int dictSdsHash(const void *key);
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
void dictSdsDestructor(void *privdata, void *val);
void dictListDestructor(void *privdata, void *val);
dictType optionToLineDictType = {
dictSdsHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
dictSdsDestructor, /* key destructor */
dictListDestructor /* val destructor */
};
/* The config rewrite state. */
struct rewriteConfigState {
dict *option_to_line; /* Option -> list of config file lines map */
int numlines; /* Number of lines in current config */
sds *lines; /* Current lines as an array of sds strings */
int has_tail; /* True if we already added directives that were
not present in the original config file. */
};
/* Append the new line to the current configuration state. */
void rewriteConfigAppendLine(struct rewriteConfigState *state, sds line) {
state->lines = zrealloc(state->lines, sizeof(char*) * (state->numlines+1));
state->lines[state->numlines++] = line;
}
/* Populate the option -> list of line numbers map. */
void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds option, int linenum) {
list *l = dictFetchValue(state->option_to_line,option);
if (l == NULL) {
l = listCreate();
dictAdd(state->option_to_line,sdsdup(option),l);
}
listAddNodeTail(l,(void*)(long)linenum);
}
/* Read the old file, split it into lines to populate a newly created
* config rewrite state, and return it to the caller.
*
* If it is impossible to read the old file, NULL is returned.
* If the old file does not exist at all, an empty state is returned. */
struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
FILE *fp = fopen(path,"r");
struct rewriteConfigState *state = zmalloc(sizeof(*state));
char buf[REDIS_CONFIGLINE_MAX+1];
int linenum = -1;
if (fp == NULL && errno != ENOENT) return NULL;
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
state->numlines = 0;
state->lines = NULL;
state->has_tail = 0;
if (fp == NULL) return state;
/* Read the old file line by line, populate the state. */
while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL) {
int argc;
sds *argv;
sds line = sdstrim(sdsnew(buf),"\r\n\t ");
linenum++; /* Zero based, so we init at -1 */
/* Handle comments and empty lines. */
if (line[0] == '#' || line[0] == '\0') {
rewriteConfigAppendLine(state,line);
continue;
}
/* Not a comment, split into arguments. */
argv = sdssplitargs(line,&argc);
if (argv == NULL) {
/* Apparently the line is unparsable for some reason, for
* instance it may have unbalanced quotes. Load it as a
* comment. */
sds aux = sdsnew("# ??? ");
aux = sdscatsds(aux,line);
sdsfree(line);
rewriteConfigAppendLine(state,aux);
continue;
}
sdstolower(argv[0]); /* We only want lowercase config directives. */
/* Now we populate the state according to the content of this line.
* Append the line and populate the option -> line numbers map. */
rewriteConfigAppendLine(state,line);
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
sdsfreesplitres(argv,argc);
}
fclose(fp);
return state;
}
/* Rewrite the specified configuration option with the new "line".
* It progressively uses lines of the file that were already used for the same
* configuraiton option in the old version of the file, removing that line from
* the map of options -> line numbers.
*
* If there are lines associated with a given configuration option and
* "force" is non-zero, the line is appended to the configuration file.
* Usually "force" is true when an option has not its default value, so it
* must be rewritten even if not present previously.
*
* The first time a line is appended into a configuration file, a comment
* is added to show that starting from that point the config file was generated
* by CONFIG REWRITE.
*
* "line" is either used, or freed, so the caller does not need to free it
* in any way. */
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force) {
sds o = sdsnew(option);
list *l = dictFetchValue(state->option_to_line,o);
if (!l && !force) {
/* Option not used previously, and we are not forced to use it. */
sdsfree(line);
sdsfree(o);
return;
}
if (l) {
listNode *ln = listFirst(l);
int linenum = (long) ln->value;
/* There are still lines in the old configuration file we can reuse
* for this option. Replace the line with the new one. */
listDelNode(l,ln);
if (listLength(l) == 0) dictDelete(state->option_to_line,o);
sdsfree(state->lines[linenum]);
state->lines[linenum] = line;
} else {
/* Append a new line. */
if (!state->has_tail) {
rewriteConfigAppendLine(state,
sdsnew("# Generated by CONFIG REWRITE"));
state->has_tail = 1;
}
rewriteConfigAppendLine(state,line);
}
sdsfree(o);
}
/* Write the long long 'bytes' value as a string in a way that is parsable
* inside redis.conf. If possible uses the GB, MB, KB notation. */
int rewriteConfigFormatMemory(char *buf, size_t len, long long bytes) {
int gb = 1024*1024*1024;
int mb = 1024*1024;
int kb = 1024;
if (bytes && (bytes % gb) == 0) {
return snprintf(buf,len,"%lldgb",bytes/gb);
} else if (bytes && (bytes % mb) == 0) {
return snprintf(buf,len,"%lldmb",bytes/mb);
} else if (bytes && (bytes % kb) == 0) {
return snprintf(buf,len,"%lldkb",bytes/kb);
} else {
return snprintf(buf,len,"%lld",bytes);
}
}
/* Rewrite a simple "option-name <bytes>" configuration option. */
void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, long long value, long long defvalue) {
char buf[64];
int force = value != defvalue;
sds line;
rewriteConfigFormatMemory(buf,sizeof(buf),value);
line = sdscatprintf(sdsempty(),"%s %s",option,buf);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite a yes/no option. */
void rewriteConfigYesNoOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
int force = value != defvalue;
sds line = sdscatprintf(sdsempty(),"%s %s",option,
value ? "yes" : "no");
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite a string option. */
void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, char *value, char *defvalue) {
int force = 1;
sds line;
/* String options set to NULL need to be not present at all in the
* configuration file to be set to NULL again at the next reboot. */
if (value == NULL) return;
/* Compare the strings as sds strings to have a binary safe comparison. */
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatrepr(line, value, strlen(value));
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite a numerical (long long range) option. */
void rewriteConfigNumericalOption(struct rewriteConfigState *state, char *option, long long value, long long defvalue) {
int force = value != defvalue;
sds line = sdscatprintf(sdsempty(),"%s %lld",option,value);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite a octal option. */
void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
int force = value != defvalue;
sds line = sdscatprintf(sdsempty(),"%s %o",option,value);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite an enumeration option, after the "value" every enum/value pair
* is specified, terminated by NULL. After NULL the default value is
* specified. See how the function is used for more information. */
void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, ...) {
va_list ap;
char *enum_name, *matching_name = NULL;
int enum_val, def_val, force;
sds line;
va_start(ap, value);
while(1) {
enum_name = va_arg(ap,char*);
enum_val = va_arg(ap,int);
if (enum_name == NULL) {
def_val = enum_val;
break;
}
if (value == enum_val) matching_name = enum_name;
}
va_end(ap);
force = value != def_val;
line = sdscatprintf(sdsempty(),"%s %s",option,matching_name);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite the syslog-fability option. */
void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) {
int value = server.syslog_facility, j;
int force = value != LOG_LOCAL0;
char *name = NULL, *option = "syslog-facility";
sds line;
for (j = 0; validSyslogFacilities[j].name; j++) {
if (validSyslogFacilities[j].value == value) {
name = (char*) validSyslogFacilities[j].name;
break;
}
}
line = sdscatprintf(sdsempty(),"%s %s",option,name);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite the save option. */
void rewriteConfigSaveOption(struct rewriteConfigState *state) {
int j;
sds line;
/* Note that if there are no save parameters at all, all the current
* config line with "save" will be detected as orphaned and deleted,
* resulting into no RDB persistence as expected. */
for (j = 0; j < server.saveparamslen; j++) {
line = sdscatprintf(sdsempty(),"save %ld %d",
server.saveparams[j].seconds, server.saveparams[j].changes);
rewriteConfigRewriteLine(state,"save",line,1);
}
}
/* Rewrite the dir option, always using absolute paths.*/
void rewriteConfigDirOption(struct rewriteConfigState *state) {
char cwd[1024];
if (getcwd(cwd,sizeof(cwd)) == NULL) return; /* no rewrite on error. */
rewriteConfigStringOption(state,"dir",cwd,NULL);
}
/* Rewrite the slaveof option. */
void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
char *option = "slaveof";
sds line;
/* If this is a master, we want all the slaveof config options
* in the file to be removed. */
if (server.masterhost == NULL) return;
line = sdscatprintf(sdsempty(),"%s %s %d", option,
server.masterhost, server.masterport);
rewriteConfigRewriteLine(state,option,line,1);
}
/* Rewrite the appendonly option. */
void rewriteConfigAppendonlyOption(struct rewriteConfigState *state) {
int force = server.aof_state != REDIS_AOF_OFF;
char *option = "appendonly";
sds line;
line = sdscatprintf(sdsempty(),"%s %s", option,
(server.aof_state == REDIS_AOF_OFF) ? "no" : "yes");
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite the notify-keyspace-events option. */
void rewriteConfigNotifykeyspaceeventsOption(struct rewriteConfigState *state) {
int force = server.notify_keyspace_events != 0;
char *option = "notify-keyspace-events";
sds line, flags;
flags = keyspaceEventsFlagsToString(server.notify_keyspace_events);
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatrepr(line, flags, sdslen(flags));
sdsfree(flags);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Rewrite the client-output-buffer-limit option. */
void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state) {
int j;
char *option = "client-output-buffer-limit";
for (j = 0; j < REDIS_CLIENT_LIMIT_NUM_CLASSES; j++) {
int force = (server.client_obuf_limits[j].hard_limit_bytes !=
clientBufferLimitsDefaults[j].hard_limit_bytes) ||
(server.client_obuf_limits[j].soft_limit_bytes !=
clientBufferLimitsDefaults[j].soft_limit_bytes) ||
(server.client_obuf_limits[j].soft_limit_seconds !=
clientBufferLimitsDefaults[j].soft_limit_seconds);
sds line;
char hard[64], soft[64];
rewriteConfigFormatMemory(hard,sizeof(hard),
server.client_obuf_limits[j].hard_limit_bytes);
rewriteConfigFormatMemory(soft,sizeof(soft),
server.client_obuf_limits[j].soft_limit_bytes);
line = sdscatprintf(sdsempty(),"%s %s %s %s %ld",
option, getClientLimitClassName(j), hard, soft,
(long) server.client_obuf_limits[j].soft_limit_seconds);
rewriteConfigRewriteLine(state,option,line,force);
}
}
/* Rewrite the bind option. */
void rewriteConfigBindOption(struct rewriteConfigState *state) {
int force = 1;
sds line, addresses;
char *option = "bind";
/* Nothing to rewrite if we don't have bind addresses. */
if (server.bindaddr_count == 0) return;
/* Rewrite as bind <addr1> <addr2> ... <addrN> */
addresses = sdsjoin(server.bindaddr,server.bindaddr_count," ");
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatsds(line, addresses);
sdsfree(addresses);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Glue together the configuration lines in the current configuration
* rewrite state into a single string, stripping multiple empty lines. */
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
sds content = sdsempty();
int j, was_empty = 0;
for (j = 0; j < state->numlines; j++) {
/* Every cluster of empty lines is turned into a single empty line. */
if (sdslen(state->lines[j]) == 0) {
if (was_empty) continue;
was_empty = 1;
} else {
was_empty = 0;
}
content = sdscatsds(content,state->lines[j]);
content = sdscatlen(content,"\n",1);
}
return content;
}
/* Free the configuration rewrite state. */
void rewriteConfigReleaseState(struct rewriteConfigState *state) {
sdsfreesplitres(state->lines,state->numlines);
dictRelease(state->option_to_line);
zfree(state);
}
/* At the end of the rewrite process the state contains the remaining
* map between "option name" => "lines in the original config file".
* Lines used by the rewrite process were removed by the function
* rewriteConfigRewriteLine(), all the other lines are "orphaned" and
* should be replaced by empty lines.
*
* This function does just this, iterating all the option names and
* blanking all the lines still associated. */
void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
dictIterator *di = dictGetIterator(state->option_to_line);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
list *l = dictGetVal(de);
while(listLength(l)) {
listNode *ln = listFirst(l);
int linenum = (long) ln->value;
sdsfree(state->lines[linenum]);
state->lines[linenum] = sdsempty();
listDelNode(l,ln);
}
}
dictReleaseIterator(di);
}
/* This function overwrites the old configuration file with the new content.
*
* 1) The old file length is obtained.
* 2) If the new content is smaller, padding is added.
* 3) A single write(2) call is used to replace the content of the file.
* 4) Later the file is truncated to the length of the new content.
*
* This way we are sure the file is left in a consistent state even if the
* process is stopped between any of the four operations.
*
* The function returns 0 on success, otherwise -1 is returned and errno
* set accordingly. */
int rewriteConfigOverwriteFile(char *configfile, sds content) {
int retval = 0;
int fd = open(configfile,O_RDWR|O_CREAT,0644);
int content_size = sdslen(content), padding = 0;
struct stat sb;
sds content_padded;
/* 1) Open the old file (or create a new one if it does not
* exist), get the size. */
if (fd == -1) return -1; /* errno set by open(). */
if (fstat(fd,&sb) == -1) {
close(fd);
return -1; /* errno set by fstat(). */
}
/* 2) Pad the content at least match the old file size. */
content_padded = sdsdup(content);
if (content_size < sb.st_size) {
/* If the old file was bigger, pad the content with
* a newline plus as many "#" chars as required. */
padding = sb.st_size - content_size;
content_padded = sdsgrowzero(content_padded,sb.st_size);
content_padded[content_size] = '\n';
memset(content_padded+content_size+1,'#',padding-1);
}
/* 3) Write the new content using a single write(2). */
if (write(fd,content_padded,strlen(content_padded)) == -1) {
retval = -1;
goto cleanup;
}
/* 4) Truncate the file to the right length if we used padding. */
if (padding) {
if (ftruncate(fd,content_size) == -1) {
/* Non critical error... */
}
}
cleanup:
sdsfree(content_padded);
close(fd);
return retval;
}
/* Rewrite the configuration file at "path".
* If the configuration file already exists, we try at best to retain comments
* and overall structure.
*
* Configuration parameters that are at their default value, unless already
* explicitly included in the old configuration file, are not rewritten.
*
* On error -1 is returned and errno is set accordingly, otherwise 0. */
int rewriteConfig(char *path) {
struct rewriteConfigState *state;
sds newcontent;
int retval;
/* Step 1: read the old config into our rewrite state. */
if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;
/* Step 2: rewrite every single option, replacing or appending it inside
* the rewrite state. */
/* TODO: Turn every default into a define, use it also in
* initServerConfig(). */
rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0);
rewriteConfigStringOption(state,"pidfile",server.pidfile,REDIS_DEFAULT_PID_FILE);
rewriteConfigNumericalOption(state,"port",server.port,REDIS_SERVERPORT);
rewriteConfigBindOption(state);
rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM);
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,REDIS_MAXIDLETIME);
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,REDIS_DEFAULT_TCP_KEEPALIVE);
rewriteConfigEnumOption(state,"loglevel",server.verbosity,
"debug", REDIS_DEBUG,
"verbose", REDIS_VERBOSE,
"notice", REDIS_NOTICE,
"warning", REDIS_WARNING,
NULL, REDIS_DEFAULT_VERBOSITY);
rewriteConfigStringOption(state,"logfile",server.logfile,REDIS_DEFAULT_LOGFILE);
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,REDIS_DEFAULT_SYSLOG_ENABLED);
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,REDIS_DEFAULT_SYSLOG_IDENT);
rewriteConfigSyslogfacilityOption(state);
rewriteConfigSaveOption(state);
rewriteConfigNumericalOption(state,"databases",server.dbnum,REDIS_DEFAULT_DBNUM);
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,REDIS_DEFAULT_RDB_COMPRESSION);
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,REDIS_DEFAULT_RDB_CHECKSUM);
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,REDIS_DEFAULT_RDB_FILENAME);
rewriteConfigDirOption(state);
rewriteConfigSlaveofOption(state);
rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA);
rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,REDIS_DEFAULT_SLAVE_READ_ONLY);
rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,REDIS_REPL_PING_SLAVE_PERIOD);
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,REDIS_REPL_TIMEOUT);
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,REDIS_DEFAULT_REPL_BACKLOG_SIZE);
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY);
rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,REDIS_DEFAULT_SLAVE_PRIORITY);
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS);
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY);
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,
"volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU,
"allkeys-lru", REDIS_MAXMEMORY_ALLKEYS_LRU,
"volatile-random", REDIS_MAXMEMORY_VOLATILE_RANDOM,
"allkeys-random", REDIS_MAXMEMORY_ALLKEYS_RANDOM,
"volatile-ttl", REDIS_MAXMEMORY_VOLATILE_TTL,
"noeviction", REDIS_MAXMEMORY_NO_EVICTION,
NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES);
rewriteConfigAppendonlyOption(state);
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,
"everysec", AOF_FSYNC_EVERYSEC,
"always", AOF_FSYNC_ALWAYS,
"no", AOF_FSYNC_NO,
NULL, REDIS_DEFAULT_AOF_FSYNC);
rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,REDIS_AOF_REWRITE_PERC);
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,REDIS_AOF_REWRITE_MIN_SIZE);
rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,REDIS_LUA_TIME_LIMIT);
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT);
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,REDIS_SLOWLOG_LOG_SLOWER_THAN);
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,REDIS_SLOWLOG_MAX_LEN);
rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,REDIS_HASH_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"list-max-ziplist-entries",server.list_max_ziplist_entries,REDIS_LIST_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"list-max-ziplist-value",server.list_max_ziplist_value,REDIS_LIST_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,REDIS_DEFAULT_ACTIVE_REHASHING);
rewriteConfigClientoutputbufferlimitOption(state);
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
/* Step 3: remove all the orphaned lines in the old file, that is, lines
* that were used by a config option and are no longer used, like in case
* of multiple "save" options or duplicated options. */
rewriteConfigRemoveOrphaned(state);
/* Step 4: generate a new configuration file from the modified state
* and write it into the original file. */
newcontent = rewriteConfigGetContentFromState(state);
retval = rewriteConfigOverwriteFile(server.configfile,newcontent);
sdsfree(newcontent);
rewriteConfigReleaseState(state);
return retval;
}
/*-----------------------------------------------------------------------------
* CONFIG command entry point
*----------------------------------------------------------------------------*/
void configCommand(redisClient *c) {
if (!strcasecmp(c->argv[1]->ptr,"set")) {
if (c->argc != 4) goto badarity;
configSetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"get")) {
if (c->argc != 3) goto badarity;
configGetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
if (c->argc != 2) goto badarity;
server.stat_keyspace_hits = 0;
server.stat_keyspace_misses = 0;
server.stat_numcommands = 0;
server.stat_numconnections = 0;
server.stat_expiredkeys = 0;
server.stat_rejected_conn = 0;
server.stat_fork_time = 0;
server.aof_delayed_fsync = 0;
resetCommandTableStats();
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite")) {
if (c->argc != 2) goto badarity;
if (server.configfile == NULL) {
addReplyError(c,"The server is running without a config file");
return;
}
if (rewriteConfig(server.configfile) == -1) {
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
} else {
addReply(c,shared.ok);
}
} else {
addReplyError(c,
"CONFIG subcommand must be one of GET, SET, RESETSTAT, REWRITE");
}
return;
badarity:
addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
(char*) c->argv[1]->ptr);
}