diff --git a/redis.conf b/redis.conf index 47a393c7a..b7824c372 100644 --- a/redis.conf +++ b/redis.conf @@ -378,10 +378,10 @@ proc-title-template "{title} {listen-addr} {server-mode}" # Save the DB to disk. # -# save +# save [ ...] # -# Redis will save the DB if both the given number of seconds and the given -# number of write operations against the DB occurred. +# Redis will save the DB if the given number of seconds elapsed and it +# surpassed the given number of write operations against the DB. # # Snapshotting can be completely disabled with a single empty string argument # as in following example: @@ -393,11 +393,9 @@ proc-title-template "{title} {listen-addr} {server-mode}" # * After 300 seconds (5 minutes) if at least 100 keys changed # * After 60 seconds if at least 10000 keys changed # -# You can set these explicitly by uncommenting the three following lines. +# You can set these explicitly by uncommenting the following line. # -# save 3600 1 -# save 300 100 -# save 60 10000 +# save 3600 1 300 100 60 10000 # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. diff --git a/src/config.c b/src/config.c index 3b97ea5b5..2b2ff737d 100644 --- a/src/config.c +++ b/src/config.c @@ -142,12 +142,6 @@ int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 }; * int is_valid_fn(val, err) * Return 1 when val is valid, and 0 when invalid. * Optionally set err to a static error string. - * int update_fn(val, prev, err) - * This function is called only for CONFIG SET command (not at config file parsing) - * It is called after the actual config is applied, - * Return 1 for success, and 0 for failure. - * Optionally set err to a static error string. - * On failure the config change will be reverted. */ /* Configuration values that require no special handling to set, get, load or @@ -156,14 +150,12 @@ typedef struct boolConfigData { int *config; /* The pointer to the server config this value is stored in */ const int default_value; /* The default value of the config on rewrite */ int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */ - int (*update_fn)(int val, int prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */ } boolConfigData; typedef struct stringConfigData { char **config; /* Pointer to the server config this value is stored in. */ const char *default_value; /* Default value of the config on rewrite. */ int (*is_valid_fn)(char* val, const char **err); /* Optional function to check validity of new value (generic doc above) */ - int (*update_fn)(char* val, char* prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */ int convert_empty_to_null; /* Boolean indicating if empty strings should be stored as a NULL value. */ } stringConfigData; @@ -172,7 +164,6 @@ typedef struct sdsConfigData { sds *config; /* Pointer to the server config this value is stored in. */ const char *default_value; /* Default value of the config on rewrite. */ int (*is_valid_fn)(sds val, const char **err); /* Optional function to check validity of new value (generic doc above) */ - int (*update_fn)(sds val, sds prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */ int convert_empty_to_null; /* Boolean indicating if empty SDS strings should be stored as a NULL value. */ } sdsConfigData; @@ -182,7 +173,6 @@ typedef struct enumConfigData { configEnum *enum_value; /* The underlying enum type this data represents */ const int default_value; /* The default value of the config on rewrite */ int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */ - int (*update_fn)(int val, int prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */ } enumConfigData; typedef enum numericType { @@ -222,7 +212,6 @@ typedef struct numericConfigData { long long upper_bound; /* The upper bound of this numeric value */ const long long default_value; /* The default value of the config on rewrite */ int (*is_valid_fn)(long long val, const char **err); /* Optional function to check validity of new value (generic doc above) */ - int (*update_fn)(long long val, long long prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */ } numericConfigData; typedef union typeData { @@ -233,14 +222,20 @@ typedef union typeData { numericConfigData numeric; } typeData; +typedef int (*apply_fn)(const char **err); typedef struct typeInterface { /* Called on server start, to init the server with default value */ void (*init)(typeData data); - /* Called on server startup and CONFIG SET, returns 1 on success, 0 on error - * and can set a verbose err string, update is true when called from CONFIG SET */ - int (*set)(typeData data, sds *argv, int argc, int update, const char **err); - /* Called on CONFIG GET, required to add output to the client */ - void (*get)(client *c, typeData data); + /* Called on server startup and CONFIG SET, returns 1 on success, + * 2 meaning no actual change done, 0 on error and can set a verbose err + * string */ + int (*set)(typeData data, sds *argv, int argc, const char **err); + /* Optional: called after `set()` to apply the config change. Used only in + * the context of CONFIG SET. Returns 1 on success, 0 on failure. + * Optionally set err to a static error string. */ + apply_fn apply; + /* Called on CONFIG GET, returns sds to be used in reply */ + sds (*get)(typeData data); /* Called on CONFIG REWRITE, required to rewrite the config state */ void (*rewrite)(typeData data, const char *name, struct rewriteConfigState *state); } typeInterface; @@ -333,63 +328,6 @@ void queueLoadModule(sds path, sds *argv, int argc) { listAddNodeTail(server.loadmodule_queue,loadmod); } -/* Parse an array of CONFIG_OOM_COUNT sds strings, validate and populate - * server.oom_score_adj_values if valid. - */ - -static int updateOOMScoreAdjValues(sds *args, const char **err, int apply) { - int i; - int values[CONFIG_OOM_COUNT]; - - for (i = 0; i < CONFIG_OOM_COUNT; i++) { - char *eptr; - long long val = strtoll(args[i], &eptr, 10); - - if (*eptr != '\0' || val < -2000 || val > 2000) { - if (err) *err = "Invalid oom-score-adj-values, elements must be between -2000 and 2000."; - return C_ERR; - } - - values[i] = val; - } - - /* Verify that the values make sense. If they don't omit a warning but - * keep the configuration, which may still be valid for privileged processes. - */ - - if (values[CONFIG_OOM_REPLICA] < values[CONFIG_OOM_MASTER] || - values[CONFIG_OOM_BGCHILD] < values[CONFIG_OOM_REPLICA]) { - serverLog(LL_WARNING, - "The oom-score-adj-values configuration may not work for non-privileged processes! " - "Please consult the documentation."); - } - - /* Store values, retain previous config for rollback in case we fail. */ - int old_values[CONFIG_OOM_COUNT]; - for (i = 0; i < CONFIG_OOM_COUNT; i++) { - old_values[i] = server.oom_score_adj_values[i]; - server.oom_score_adj_values[i] = values[i]; - } - - /* When parsing the config file, we want to apply only when all is done. */ - if (!apply) - return C_OK; - - /* Update */ - if (setOOMScoreAdj(-1) == C_ERR) { - /* Roll back */ - for (i = 0; i < CONFIG_OOM_COUNT; i++) - server.oom_score_adj_values[i] = old_values[i]; - - if (err) - *err = "Failed to apply oom-score-adj-values configuration, check server logs."; - - return C_ERR; - } - - return C_OK; -} - /* Parse an array of `arg_len` sds strings, validate and populate * server.client_obuf_limits if valid. * Used in CONFIG SET and configuration file parsing. */ @@ -452,12 +390,18 @@ void initConfigValues() { } } +/* Note this is here to support detecting we're running a config set from + * within conf file parsing. This is only needed to support the deprecated + * abnormal aggregate `save T C` functionality. Remove in the future. */ +static int reading_config_file; + void loadServerConfigFromString(char *config) { char buf[1024]; const char *err = NULL; int linenum = 0, totlines, i; sds *lines; + reading_config_file = 1; lines = sdssplitlen(config,strlen(config),"\n",1,&totlines); for (i = 0; i < totlines; i++) { @@ -497,7 +441,7 @@ void loadServerConfigFromString(char *config) { goto loaderr; } /* Set config using all arguments that follows */ - if (!config->interface.set(config->data, &argv[1], argc-1, 0, &err)) { + if (!config->interface.set(config->data, &argv[1], argc-1, &err)) { goto loaderr; } @@ -594,6 +538,7 @@ void loadServerConfigFromString(char *config) { if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; sdsfreesplitres(lines,totlines); + reading_config_file = 0; return; loaderr: @@ -687,63 +632,163 @@ void loadServerConfig(char *filename, char config_from_stdin, char *options) { sdsfree(config); } +static int performInterfaceSet(standardConfig *config, sds value, const char **errstr) { + sds *argv; + int argc, res; + + if (config->flags & MULTI_ARG_CONFIG) { + argv = sdssplitlen(value, sdslen(value), " ", 1, &argc); + } else { + argv = (char**)&value; + argc = 1; + } + + /* Set the config */ + res = config->interface.set(config->data, argv, argc, errstr); + if (config->flags & MULTI_ARG_CONFIG) sdsfreesplitres(argv, argc); + return res; +} + +static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, int count, apply_fn *apply_fns) { + int i; + const char *errstr = "unknown error"; + /* Set all backup values */ + for (i = 0; i < count; i++) { + if (!performInterfaceSet(set_configs[i], old_values[i], &errstr)) + serverLog(LL_WARNING, "Failed restoring failed CONFIG SET command. Error setting %s to '%s': %s", + set_configs[i]->name, old_values[i], errstr); + } + /* Apply backup */ + if (apply_fns) { + for (i = 0; i < count && apply_fns[i] != NULL; i++) { + if (!apply_fns[i](&errstr)) + serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr); + } + } +} + /*----------------------------------------------------------------------------- * CONFIG SET implementation *----------------------------------------------------------------------------*/ + void configSetCommand(client *c) { - robj *o; - sds *argv; - int argc; const char *errstr = NULL; - serverAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2])); - serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3])); - o = c->argv[3]; + standardConfig **set_configs; /* TODO: make this a dict for better performance */ + sds *new_values; + sds *old_values = NULL; + apply_fn *apply_fns; /* TODO: make this a set for better performance */ + int config_count, i, j; + int invalid_args = 0; - /* Iterate the configs that are standard */ - for (standardConfig *config = configs; config->name != NULL; config++) { - if (!(config->flags & IMMUTABLE_CONFIG) && - (!strcasecmp(c->argv[2]->ptr,config->name) || - (config->alias && !strcasecmp(c->argv[2]->ptr,config->alias)))) - { - if (config->flags & SENSITIVE_CONFIG) { - redactClientCommandArgument(c,3); - } + /* Make sure we have an even number of arguments: conf-val pairs */ + if (c->argc & 1) { + addReplyErrorObject(c, shared.syntaxerr); + return; + } + config_count = (c->argc - 2) / 2; - if (config->flags & MULTI_ARG_CONFIG) { - argv = sdssplitlen(o->ptr, sdslen(o->ptr), " ", 1, &argc); - if (argv == NULL) { - goto badfmt; + set_configs = zcalloc(sizeof(standardConfig*)*config_count); + new_values = zmalloc(sizeof(sds*)*config_count); + old_values = zcalloc(sizeof(sds*)*config_count); + apply_fns = zcalloc(sizeof(apply_fn)*config_count); + + /* Find all relevant configs */ + for (i = 0; i < config_count; i++) { + for (standardConfig *config = configs; config->name != NULL; config++) { + if ((!strcasecmp(c->argv[2+i*2]->ptr,config->name) || + (config->alias && !strcasecmp(c->argv[2]->ptr,config->alias)))) { + + /* Note: it's important we run over ALL passed configs and check if we need to call `redactClientCommandArgument()`. + * This is in order to avoid anyone using this command for a log/slowlog/monitor/etc. displaying sensitive info. + * So even if we encounter an error we still continue running over the remaining arguments. */ + if (config->flags & SENSITIVE_CONFIG) { + redactClientCommandArgument(c,2+i*2+1); } - } else { - argv = (char**)&o->ptr; - argc = 1; - } - if (!config->interface.set(config->data, argv, argc, 1, &errstr)) { - if (config->flags & MULTI_ARG_CONFIG) sdsfreesplitres(argv, argc); - goto badfmt; + if (!invalid_args) { + if (config->flags & IMMUTABLE_CONFIG) { + /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ + errstr = "can't set immutable config"; + invalid_args = 1; + } + + /* If this config appears twice then fail */ + for (j = 0; j < i; j++) { + if (set_configs[j] == config) { + /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ + errstr = "duplicate parameter"; + invalid_args = 1; + break; + } + } + set_configs[i] = config; + new_values[i] = c->argv[2+i*2+1]->ptr; + } + break; + } + } + /* Fail if we couldn't find this config */ + /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ + if (!invalid_args && !set_configs[i]) { + errstr = "unrecognized parameter"; + invalid_args = 1; + } + } + + if (invalid_args) goto err; + + /* Backup old values before setting new ones */ + for (i = 0; i < config_count; i++) + old_values[i] = set_configs[i]->interface.get(set_configs[i]->data); + + /* Set all new values (don't apply yet) */ + for (i = 0; i < config_count; i++) { + int res = performInterfaceSet(set_configs[i], new_values[i], &errstr); + if (!res) { + restoreBackupConfig(set_configs, old_values, i+1, NULL); + goto err; + } else if (res == 1) { + /* A new value was set, if this config has an apply function then store it for execution later */ + if (set_configs[i]->interface.apply) { + /* Check if this apply function is already stored */ + int exists = 0; + for (j = 0; apply_fns[j] != NULL && j <= i; j++) { + if (apply_fns[j] == set_configs[i]->interface.apply) { + exists = 1; + break; + } + } + /* Apply function not stored, store it */ + if (!exists) + apply_fns[j] = set_configs[i]->interface.apply; } - if (config->flags & MULTI_ARG_CONFIG) sdsfreesplitres(argv, argc); - addReply(c,shared.ok); - return; } } - addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", - (char*)c->argv[2]->ptr); - return; - -badfmt: /* Bad format errors */ - if (errstr) { - addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s' - %s", - (char*)o->ptr, - (char*)c->argv[2]->ptr, - errstr); - } else { - addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'", - (char*)o->ptr, - (char*)c->argv[2]->ptr); + /* Apply all configs after being set */ + for (i = 0; i < config_count && apply_fns[i] != NULL; i++) { + if (!apply_fns[i](&errstr)) { + serverLog(LL_WARNING, "Failed applying new %s configuration, restoring previous settings.", set_configs[i]->name); + restoreBackupConfig(set_configs, old_values, config_count, apply_fns); + goto err; + } } + addReply(c,shared.ok); + goto end; + +err: + if (errstr) { + addReplyErrorFormat(c,"Config set failed - %s", errstr); + } else { + addReplyError(c,"Invalid arguments"); + } +end: + zfree(set_configs); + zfree(new_values); + for (i = 0; i < config_count; i++) + sdsfree(old_values[i]); + zfree(old_values); + zfree(apply_fns); } /*----------------------------------------------------------------------------- @@ -760,12 +805,12 @@ void configGetCommand(client *c) { for (standardConfig *config = configs; config->name != NULL; config++) { if (stringmatch(pattern,config->name,1)) { addReplyBulkCString(c,config->name); - config->interface.get(c,config->data); + addReplyBulkSds(c, config->interface.get(config->data)); matches++; } if (config->alias && stringmatch(pattern,config->alias,1)) { addReplyBulkCString(c,config->alias); - config->interface.get(c,config->data); + addReplyBulkSds(c, config->interface.get(config->data)); matches++; } } @@ -1516,11 +1561,12 @@ static char loadbuf[LOADBUF_SIZE]; .alias = (config_alias), \ .flags = (config_flags), -#define embedConfigInterface(initfn, setfn, getfn, rewritefn) .interface = { \ +#define embedConfigInterface(initfn, setfn, getfn, rewritefn, applyfn) .interface = { \ .init = (initfn), \ .set = (setfn), \ .get = (getfn), \ - .rewrite = (rewritefn) \ + .rewrite = (rewritefn), \ + .apply = (applyfn) \ }, /* What follows is the generic config types that are supported. To add a new @@ -1540,7 +1586,7 @@ static void boolConfigInit(typeData data) { *data.yesno.config = data.yesno.default_value; } -static int boolConfigSet(typeData data, sds *argv, int argc, int update, const char **err) { +static int boolConfigSet(typeData data, sds *argv, int argc, const char **err) { UNUSED(argc); int yn = yesnotoi(argv[0]); if (yn == -1) { @@ -1550,30 +1596,28 @@ static int boolConfigSet(typeData data, sds *argv, int argc, int update, const c if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err)) return 0; int prev = *(data.yesno.config); - *(data.yesno.config) = yn; - if (update && data.yesno.update_fn && !data.yesno.update_fn(yn, prev, err)) { - *(data.yesno.config) = prev; - return 0; + if (prev != yn) { + *(data.yesno.config) = yn; + return 1; } - return 1; + return 2; } -static void boolConfigGet(client *c, typeData data) { - addReplyBulkCString(c, *data.yesno.config ? "yes" : "no"); +static sds boolConfigGet(typeData data) { + return sdsnew(*data.yesno.config ? "yes" : "no"); } static void boolConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) { rewriteConfigYesNoOption(state, name,*(data.yesno.config), data.yesno.default_value); } -#define createBoolConfig(name, alias, flags, config_addr, default, is_valid, update) { \ +#define createBoolConfig(name, alias, flags, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ - embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite) \ + embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite, apply) \ .data.yesno = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ - .update_fn = (update), \ } \ } @@ -1582,23 +1626,22 @@ static void stringConfigInit(typeData data) { *data.string.config = (data.string.convert_empty_to_null && !data.string.default_value) ? NULL : zstrdup(data.string.default_value); } -static int stringConfigSet(typeData data, sds *argv, int argc, int update, const char **err) { +static int stringConfigSet(typeData data, sds *argv, int argc, const char **err) { UNUSED(argc); if (data.string.is_valid_fn && !data.string.is_valid_fn(argv[0], err)) return 0; char *prev = *data.string.config; - *data.string.config = (data.string.convert_empty_to_null && !argv[0][0]) ? NULL : zstrdup(argv[0]); - if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) { - zfree(*data.string.config); - *data.string.config = prev; - return 0; + char *new = (data.string.convert_empty_to_null && !argv[0][0]) ? NULL : argv[0]; + if (new != prev && (new == NULL || prev == NULL || strcmp(prev, new))) { + *data.string.config = new != NULL ? zstrdup(new) : NULL; + zfree(prev); + return 1; } - zfree(prev); - return 1; + return 2; } -static void stringConfigGet(client *c, typeData data) { - addReplyBulkCString(c, *data.string.config ? *data.string.config : ""); +static sds stringConfigGet(typeData data) { + return sdsnew(*data.string.config ? *data.string.config : ""); } static void stringConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) { @@ -1610,26 +1653,25 @@ static void sdsConfigInit(typeData data) { *data.sds.config = (data.sds.convert_empty_to_null && !data.sds.default_value) ? NULL: sdsnew(data.sds.default_value); } -static int sdsConfigSet(typeData data, sds *argv, int argc, int update, const char **err) { +static int sdsConfigSet(typeData data, sds *argv, int argc, const char **err) { UNUSED(argc); if (data.sds.is_valid_fn && !data.sds.is_valid_fn(argv[0], err)) return 0; sds prev = *data.sds.config; - *data.sds.config = (data.sds.convert_empty_to_null && (sdslen(argv[0]) == 0)) ? NULL : sdsdup(argv[0]); - if (update && data.sds.update_fn && !data.sds.update_fn(*data.sds.config, prev, err)) { - sdsfree(*data.sds.config); - *data.sds.config = prev; - return 0; + sds new = (data.string.convert_empty_to_null && (sdslen(argv[0]) == 0)) ? NULL : argv[0]; + if (new != prev && (new == NULL || prev == NULL || sdscmp(prev, new))) { + *data.sds.config = new != NULL ? sdsdup(new) : NULL; + sdsfree(prev); + return 1; } - sdsfree(prev); - return 1; + return 2; } -static void sdsConfigGet(client *c, typeData data) { +static sds sdsConfigGet(typeData data) { if (*data.sds.config) { - addReplyBulkSds(c, sdsdup(*data.sds.config)); + return sdsdup(*data.sds.config); } else { - addReplyBulkCString(c, ""); + return sdsnew(""); } } @@ -1641,26 +1683,24 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf #define ALLOW_EMPTY_STRING 0 #define EMPTY_STRING_IS_NULL 1 -#define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, update) { \ +#define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ - embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite) \ + embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite, apply) \ .data.string = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ - .update_fn = (update), \ .convert_empty_to_null = (empty_to_null), \ } \ } -#define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, update) { \ +#define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ - embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite) \ + embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite, apply) \ .data.sds = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ - .update_fn = (update), \ .convert_empty_to_null = (empty_to_null), \ } \ } @@ -1670,7 +1710,7 @@ static void enumConfigInit(typeData data) { *data.enumd.config = data.enumd.default_value; } -static int enumConfigSet(typeData data, sds *argv, int argc, int update, const char **err) { +static int enumConfigSet(typeData data, sds *argv, int argc, const char **err) { UNUSED(argc); int enumval = configEnumGetValue(data.enumd.enum_value, argv[0]); if (enumval == INT_MIN) { @@ -1694,30 +1734,28 @@ static int enumConfigSet(typeData data, sds *argv, int argc, int update, const c if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err)) return 0; int prev = *(data.enumd.config); - *(data.enumd.config) = enumval; - if (update && data.enumd.update_fn && !data.enumd.update_fn(enumval, prev, err)) { - *(data.enumd.config) = prev; - return 0; + if (prev != enumval) { + *(data.enumd.config) = enumval; + return 1; } - return 1; + return 2; } -static void enumConfigGet(client *c, typeData data) { - addReplyBulkCString(c, configEnumGetNameOrUnknown(data.enumd.enum_value,*data.enumd.config)); +static sds enumConfigGet(typeData data) { + return sdsnew(configEnumGetNameOrUnknown(data.enumd.enum_value,*data.enumd.config)); } static void enumConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) { rewriteConfigEnumOption(state, name,*(data.enumd.config), data.enumd.enum_value, data.enumd.default_value); } -#define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, update) { \ +#define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ - embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite) \ + embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite, apply) \ .data.enumd = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ - .update_fn = (update), \ .enum_value = (enum), \ } \ } @@ -1869,7 +1907,7 @@ static int numericParseString(typeData data, sds value, const char **err, long l return 0; } -static int numericConfigSet(typeData data, sds *argv, int argc, int update, const char **err) { +static int numericConfigSet(typeData data, sds *argv, int argc, const char **err) { UNUSED(argc); long long ll, prev = 0; @@ -1883,16 +1921,15 @@ static int numericConfigSet(typeData data, sds *argv, int argc, int update, cons return 0; GET_NUMERIC_TYPE(prev) - SET_NUMERIC_TYPE(ll) - - if (update && data.numeric.update_fn && !data.numeric.update_fn(ll, prev, err)) { - SET_NUMERIC_TYPE(prev) - return 0; + if (prev != ll) { + SET_NUMERIC_TYPE(ll) + return 1; } - return 1; + + return 2; } -static void numericConfigGet(client *c, typeData data) { +static sds numericConfigGet(typeData data) { char buf[128]; long long value = 0; @@ -1910,7 +1947,7 @@ static void numericConfigGet(client *c, typeData data) { } else { ll2string(buf, sizeof(buf), value); } - addReplyBulkCString(c, buf); + return sdsnew(buf); } static void numericConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) { @@ -1929,90 +1966,89 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite } } -#define embedCommonNumericalConfig(name, alias, _flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) { \ +#define embedCommonNumericalConfig(name, alias, _flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) { \ embedCommonConfig(name, alias, _flags) \ - embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite) \ + embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite, apply) \ .data.numeric = { \ .lower_bound = (lower), \ .upper_bound = (upper), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ - .update_fn = (update), \ .flags = (num_conf_flags), -#define createIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_INT, \ .config.i = &(config_addr) \ } \ } -#define createUIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createUIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_UINT, \ .config.ui = &(config_addr) \ } \ } -#define createLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_LONG, \ .config.l = &(config_addr) \ } \ } -#define createULongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createULongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_ULONG, \ .config.ul = &(config_addr) \ } \ } -#define createLongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createLongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_LONG_LONG, \ .config.ll = &(config_addr) \ } \ } -#define createULongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createULongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_ULONG_LONG, \ .config.ull = &(config_addr) \ } \ } -#define createSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_SIZE_T, \ .config.st = &(config_addr) \ } \ } -#define createSSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createSSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_SSIZE_T, \ .config.sst = &(config_addr) \ } \ } -#define createTimeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createTimeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_TIME_T, \ .config.tt = &(config_addr) \ } \ } -#define createOffTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ - embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, update) \ +#define createOffTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ + embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_OFF_T, \ .config.ot = &(config_addr) \ } \ } -#define createSpecialConfig(name, alias, modifiable, setfn, getfn, rewritefn) { \ +#define createSpecialConfig(name, alias, modifiable, setfn, getfn, rewritefn, applyfn) { \ embedCommonConfig(name, alias, modifiable) \ - embedConfigInterface(NULL, setfn, getfn, rewritefn) \ + embedConfigInterface(NULL, setfn, getfn, rewritefn, applyfn) \ } static int isValidActiveDefrag(int val, const char **err) { @@ -2056,9 +2092,7 @@ static int isValidProcTitleTemplate(char *val, const char **err) { return 1; } -static int updateProcTitleTemplate(char *val, char *prev, const char **err) { - UNUSED(val); - UNUSED(prev); +static int updateProcTitleTemplate(const char **err) { if (redisSetProcTitle(NULL) == C_ERR) { *err = "failed to set process title"; return 0; @@ -2066,25 +2100,18 @@ static int updateProcTitleTemplate(char *val, char *prev, const char **err) { return 1; } -static int updateHZ(long long val, long long prev, const char **err) { - UNUSED(prev); +static int updateHZ(const char **err) { UNUSED(err); /* Hz is more a hint from the user, so we accept values out of range * but cap them to reasonable values. */ - server.config_hz = val; if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ; if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; server.hz = server.config_hz; return 1; } -static int updatePort(long long val, long long prev, const char **err) { - /* Do nothing if port is unchanged */ - if (val == prev) { - return 1; - } - - if (changeListenPort(val, &server.ipfd, acceptTcpHandler) == C_ERR) { +static int updatePort(const char **err) { + if (changeListenPort(server.port, &server.ipfd, acceptTcpHandler) == C_ERR) { *err = "Unable to listen on this port. Check server logs."; return 0; } @@ -2092,28 +2119,23 @@ static int updatePort(long long val, long long prev, const char **err) { return 1; } -static int updateJemallocBgThread(int val, int prev, const char **err) { - UNUSED(prev); +static int updateJemallocBgThread(const char **err) { UNUSED(err); - set_jemalloc_bg_thread(val); + set_jemalloc_bg_thread(server.jemalloc_bg_thread); return 1; } -static int updateReplBacklogSize(long long val, long long prev, const char **err) { - /* resizeReplicationBacklog sets server.repl_backlog_size, and relies on - * being able to tell when the size changes, so restore prev before calling it. */ +static int updateReplBacklogSize(const char **err) { UNUSED(err); - server.repl_backlog_size = prev; - resizeReplicationBacklog(val); + resizeReplicationBacklog(); return 1; } -static int updateMaxmemory(long long val, long long prev, const char **err) { - UNUSED(prev); +static int updateMaxmemory(const char **err) { UNUSED(err); - if (val) { + if (server.maxmemory) { size_t used = zmalloc_used_memory()-freeMemoryGetNotCountedMemory(); - if ((unsigned long long)val < used) { + if (server.maxmemory < used) { serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", server.maxmemory, used); } performEvictions(); @@ -2121,27 +2143,22 @@ static int updateMaxmemory(long long val, long long prev, const char **err) { return 1; } -static int updateGoodSlaves(long long val, long long prev, const char **err) { - UNUSED(val); - UNUSED(prev); +static int updateGoodSlaves(const char **err) { UNUSED(err); refreshGoodSlavesCount(); return 1; } -static int updateWatchdogPeriod(long long val, long long prev, const char **err) { - UNUSED(val); - UNUSED(prev); +static int updateWatchdogPeriod(const char **err) { UNUSED(err); applyWatchdogPeriod(); return 1; } -static int updateAppendonly(int val, int prev, const char **err) { - UNUSED(prev); - if (val == 0 && server.aof_state != AOF_OFF) { +static int updateAppendonly(const char **err) { + if (!server.aof_enabled && server.aof_state != AOF_OFF) { stopAppendOnly(); - } else if (val && server.aof_state == AOF_OFF) { + } else if (server.aof_enabled && server.aof_state == AOF_OFF) { if (startAppendOnly() == C_ERR) { *err = "Unable to turn on AOF. Check server logs."; return 0; @@ -2150,90 +2167,79 @@ static int updateAppendonly(int val, int prev, const char **err) { return 1; } -static int updateSighandlerEnabled(int val, int prev, const char **err) { +static int updateSighandlerEnabled(const char **err) { UNUSED(err); - UNUSED(prev); - if (val) + if (server.crashlog_enabled) setupSignalHandlers(); else removeSignalHandlers(); return 1; } -static int updateMaxclients(long long val, long long prev, const char **err) { - /* Try to check if the OS is capable of supporting so many FDs. */ - if (val > prev) { - adjustOpenFilesLimit(); - if (server.maxclients != val) { - static char msg[128]; - sprintf(msg, "The operating system is not able to handle the specified number of clients, try with %d", server.maxclients); - *err = msg; - if (server.maxclients > prev) { - server.maxclients = prev; - adjustOpenFilesLimit(); - } - return 0; - } - if ((unsigned int) aeGetSetSize(server.el) < - server.maxclients + CONFIG_FDSET_INCR) +static int updateMaxclients(const char **err) { + unsigned int new_maxclients = server.maxclients; + adjustOpenFilesLimit(); + if (server.maxclients != new_maxclients) { + static char msg[128]; + sprintf(msg, "The operating system is not able to handle the specified number of clients, try with %d", server.maxclients); + *err = msg; + return 0; + } + if ((unsigned int) aeGetSetSize(server.el) < + server.maxclients + CONFIG_FDSET_INCR) + { + if (aeResizeSetSize(server.el, + server.maxclients + CONFIG_FDSET_INCR) == AE_ERR) { - if (aeResizeSetSize(server.el, - server.maxclients + CONFIG_FDSET_INCR) == AE_ERR) - { - *err = "The event loop API used by Redis is not able to handle the specified number of clients"; - return 0; - } - } - } - return 1; -} - -static int updateOOMScoreAdj(int val, int prev, const char **err) { - UNUSED(prev); - - if (val) { - if (setOOMScoreAdj(-1) == C_ERR) { - *err = "Failed to set current oom_score_adj. Check server logs."; + *err = "The event loop API used by Redis is not able to handle the specified number of clients"; return 0; } } + return 1; +} + +static int updateOOMScoreAdj(const char **err) { + if (setOOMScoreAdj(-1) == C_ERR) { + *err = "Failed to set current oom_score_adj. Check server logs."; + return 0; + } return 1; } - -int updateRequirePass(sds val, sds prev, const char **err) { - UNUSED(prev); +int updateRequirePass(const char **err) { UNUSED(err); /* The old "requirepass" directive just translates to setting * a password to the default user. The only thing we do * additionally is to remember the cleartext password in this * case, for backward compatibility with Redis <= 5. */ - ACLUpdateDefaultUserPassword(val); + ACLUpdateDefaultUserPassword(server.requirepass); return 1; } +static int applyBind(const char **err) { + if (changeBindAddr() == C_ERR) { + *err = "Failed to bind to specified addresses."; + return 0; + } -int updateClusterFlags(int val, int prev, const char **err) { - UNUSED(val); - UNUSED(prev); + return 1; +} + +int updateClusterFlags(const char **err) { UNUSED(err); clusterUpdateMyselfFlags(); return 1; } -int updateClusterIp(char *val, char *prev, const char **err) { - UNUSED(val); - UNUSED(prev); +static int updateClusterIp(const char **err) { UNUSED(err); clusterUpdateMyselfIp(); return 1; } #ifdef USE_OPENSSL -static int updateTlsCfg(char *val, char *prev, const char **err) { - UNUSED(val); - UNUSED(prev); +static int applyTlsCfg(const char **err) { UNUSED(err); /* If TLS is enabled, try to configure OpenSSL. */ @@ -2244,31 +2250,15 @@ static int updateTlsCfg(char *val, char *prev, const char **err) { } return 1; } -static int updateTlsCfgBool(int val, int prev, const char **err) { - UNUSED(val); - UNUSED(prev); - return updateTlsCfg(NULL, NULL, err); -} -static int updateTlsCfgInt(long long val, long long prev, const char **err) { - UNUSED(val); - UNUSED(prev); - return updateTlsCfg(NULL, NULL, err); -} - -static int updateTLSPort(long long val, long long prev, const char **err) { - /* Do nothing if port is unchanged */ - if (val == prev) { - return 1; - } - - /* Configure TLS if tls is enabled */ - if (prev == 0 && tlsConfigure(&server.tls_ctx_config) == C_ERR) { +static int applyTLSPort(const char **err) { + /* Configure TLS in case it wasn't enabled */ + if (!isTlsConfigured() && tlsConfigure(&server.tls_ctx_config) == C_ERR) { *err = "Unable to update TLS configuration. Check server logs."; return 0; } - if (changeListenPort(val, &server.tlsfd, acceptTLSHandler) == C_ERR) { + if (changeListenPort(server.tls_port, &server.tlsfd, acceptTLSHandler) == C_ERR) { *err = "Unable to listen on this port. Check server logs."; return 0; } @@ -2278,9 +2268,8 @@ static int updateTLSPort(long long val, long long prev, const char **err) { #endif /* USE_OPENSSL */ -static int setConfigDirOption(typeData data, sds *argv, int argc, int update, const char **err) { +static int setConfigDirOption(typeData data, sds *argv, int argc, const char **err) { UNUSED(data); - UNUSED(update); if (argc != 1) { *err = "wrong number of arguments"; return 0; @@ -2292,17 +2281,17 @@ static int setConfigDirOption(typeData data, sds *argv, int argc, int update, co return 1; } -static void getConfigDirOption(client *c, typeData data) { +static sds getConfigDirOption(typeData data) { UNUSED(data); char buf[1024]; if (getcwd(buf,sizeof(buf)) == NULL) buf[0] = '\0'; - addReplyBulkCString(c,buf); + return sdsnew(buf); } -static int setConfigSaveOption(typeData data, sds *argv, int argc, int update, const char **err) { +static int setConfigSaveOption(typeData data, sds *argv, int argc, const char **err) { UNUSED(data); int j; @@ -2330,7 +2319,7 @@ static int setConfigSaveOption(typeData data, sds *argv, int argc, int update, c } } /* Finally set the new config */ - if (update) { + if (!reading_config_file) { resetServerSaveParams(); } else { /* We don't reset save params before loading, because if they're not part @@ -2355,7 +2344,7 @@ static int setConfigSaveOption(typeData data, sds *argv, int argc, int update, c return 1; } -static void getConfigSaveOption(client *c, typeData data) { +static sds getConfigSaveOption(typeData data) { UNUSED(data); sds buf = sdsempty(); int j; @@ -2368,17 +2357,15 @@ static void getConfigSaveOption(client *c, typeData data) { buf = sdscatlen(buf," ",1); } - addReplyBulkCString(c,buf); - sdsfree(buf); + return buf; } -static int setConfigClientOutputBufferLimitOption(typeData data, sds *argv, int argc, int update, const char **err) { +static int setConfigClientOutputBufferLimitOption(typeData data, sds *argv, int argc, const char **err) { UNUSED(data); - UNUSED(update); return updateClientOutputBufferLimit(argv, argc, err); } -static void getConfigClientOutputBufferLimitOption(client *c, typeData data) { +static sds getConfigClientOutputBufferLimitOption(typeData data) { UNUSED(data); sds buf = sdsempty(); int j; @@ -2391,21 +2378,54 @@ static void getConfigClientOutputBufferLimitOption(client *c, typeData data) { if (j != CLIENT_TYPE_OBUF_COUNT-1) buf = sdscatlen(buf," ",1); } - addReplyBulkCString(c,buf); - sdsfree(buf); + return buf; } -static int setConfigOOMScoreAdjValuesOption(typeData data, sds *argv, int argc, int update, const char **err) { +/* Parse an array of CONFIG_OOM_COUNT sds strings, validate and populate + * server.oom_score_adj_values if valid. + */ +static int setConfigOOMScoreAdjValuesOption(typeData data, sds *argv, int argc, const char **err) { + int i; + int values[CONFIG_OOM_COUNT]; UNUSED(data); + if (argc != CONFIG_OOM_COUNT) { *err = "wrong number of arguments"; return 0; } - if (updateOOMScoreAdjValues(argv,err,update) == C_ERR) return 0; + + for (i = 0; i < CONFIG_OOM_COUNT; i++) { + char *eptr; + long long val = strtoll(argv[i], &eptr, 10); + + if (*eptr != '\0' || val < -2000 || val > 2000) { + if (err) *err = "Invalid oom-score-adj-values, elements must be between -2000 and 2000."; + return -1; + } + + values[i] = val; + } + + /* Verify that the values make sense. If they don't omit a warning but + * keep the configuration, which may still be valid for privileged processes. + */ + + if (values[CONFIG_OOM_REPLICA] < values[CONFIG_OOM_MASTER] || + values[CONFIG_OOM_BGCHILD] < values[CONFIG_OOM_REPLICA]) + { + serverLog(LL_WARNING, + "The oom-score-adj-values configuration may not work for non-privileged processes! " + "Please consult the documentation."); + } + + for (i = 0; i < CONFIG_OOM_COUNT; i++) { + server.oom_score_adj_values[i] = values[i]; + } + return 1; } -static void getConfigOOMScoreAdjValuesOption(client *c, typeData data) { +static sds getConfigOOMScoreAdjValuesOption(typeData data) { UNUSED(data); sds buf = sdsempty(); int j; @@ -2416,13 +2436,11 @@ static void getConfigOOMScoreAdjValuesOption(client *c, typeData data) { buf = sdscatlen(buf," ",1); } - addReplyBulkCString(c,buf); - sdsfree(buf); + return buf; } -static int setConfigNotifyKeyspaceEventsOption(typeData data, sds *argv, int argc, int update, const char **err) { +static int setConfigNotifyKeyspaceEventsOption(typeData data, sds *argv, int argc, const char **err) { UNUSED(data); - UNUSED(update); if (argc != 1) { *err = "wrong number of arguments"; return 0; @@ -2436,46 +2454,36 @@ static int setConfigNotifyKeyspaceEventsOption(typeData data, sds *argv, int arg return 1; } -static void getConfigNotifyKeyspaceEventsOption(client *c, typeData data) { +static sds getConfigNotifyKeyspaceEventsOption(typeData data) { UNUSED(data); - sds flags = keyspaceEventsFlagsToString(server.notify_keyspace_events); - addReplyBulkSds(c,flags); + return keyspaceEventsFlagsToString(server.notify_keyspace_events); } -static int setConfigBindOption(typeData data, sds* argv, int argc, int update, const char **err) { +static int setConfigBindOption(typeData data, sds* argv, int argc, const char **err) { UNUSED(data); + int j; if (argc > CONFIG_BINDADDR_MAX) { *err = "Too many bind addresses specified."; return 0; } - if (update) { - if (changeBindAddr(argv, argc) == C_ERR) { - *err = "Failed to bind to specified addresses."; - return 0; - } - } else { - int j; + /* A single empty argument is treated as a zero bindaddr count */ + if (argc == 1 && sdslen(argv[0]) == 0) argc = 0; - /* A single empty argument is treated as a zero bindaddr count */ - if (argc == 1 && sdslen(argv[0]) == 0) argc = 0; - - /* Free old bind addresses */ - for (j = 0; j < server.bindaddr_count; j++) { - zfree(server.bindaddr[j]); - } - for (j = 0; j < argc; j++) - server.bindaddr[j] = zstrdup(argv[j]); - server.bindaddr_count = argc; + /* Free old bind addresses */ + for (j = 0; j < server.bindaddr_count; j++) { + zfree(server.bindaddr[j]); } + for (j = 0; j < argc; j++) + server.bindaddr[j] = zstrdup(argv[j]); + server.bindaddr_count = argc; return 1; } -static int setConfigReplicaOfOption(typeData data, sds* argv, int argc, int update, const char **err) { +static int setConfigReplicaOfOption(typeData data, sds* argv, int argc, const char **err) { UNUSED(data); - UNUSED(update); if (argc != 2) { *err = "wrong number of arguments"; @@ -2498,14 +2506,12 @@ static int setConfigReplicaOfOption(typeData data, sds* argv, int argc, int upda return 1; } -static void getConfigBindOption(client *c, typeData data) { +static sds getConfigBindOption(typeData data) { UNUSED(data); - sds aux = sdsjoin(server.bindaddr,server.bindaddr_count," "); - addReplyBulkCString(c,aux); - sdsfree(aux); + return sdsjoin(server.bindaddr,server.bindaddr_count," "); } -static void getConfigReplicaOfOption(client *c, typeData data) { +static sds getConfigReplicaOfOption(typeData data) { UNUSED(data); char buf[256]; if (server.masterhost) @@ -2513,7 +2519,7 @@ static void getConfigReplicaOfOption(client *c, typeData data) { server.masterhost, server.masterport); else buf[0] = '\0'; - addReplyBulkCString(c,buf); + return sdsnew(buf); } standardConfig configs[] = { @@ -2674,36 +2680,36 @@ standardConfig configs[] = { createOffTConfig("loading-process-events-interval-bytes", NULL, MODIFIABLE_CONFIG, 1024, INT_MAX, server.loading_process_events_interval_bytes, 1024*1024*2, INTEGER_CONFIG, NULL, NULL), #ifdef USE_OPENSSL - createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */ - createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt), - createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt), - createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, updateTlsCfgBool), - createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, updateTlsCfgBool), + createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, applyTLSPort), /* TCP port. */ + createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, applyTlsCfg), + createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, applyTlsCfg), + createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, applyTlsCfg), + createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, applyTlsCfg), createEnumConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, server.tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL), - createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool), - createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool), - createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg), - createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg), - createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, updateTlsCfg), - createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg), - createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg), - createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, updateTlsCfg), - createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg), - createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg), - createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg), - createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, updateTlsCfg), - createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, updateTlsCfg), - createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, updateTlsCfg), + createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, applyTlsCfg), + createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, applyTlsCfg), + createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, applyTlsCfg), + createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, applyTlsCfg), + createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, applyTlsCfg), + createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, applyTlsCfg), + createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, applyTlsCfg), + createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, applyTlsCfg), + createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, applyTlsCfg), + createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, applyTlsCfg), + createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, applyTlsCfg), + createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, applyTlsCfg), + createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, applyTlsCfg), + createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, applyTlsCfg), #endif /* Special configs */ - createSpecialConfig("dir", NULL, MODIFIABLE_CONFIG, setConfigDirOption, getConfigDirOption, rewriteConfigDirOption), - createSpecialConfig("save", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigSaveOption, getConfigSaveOption, rewriteConfigSaveOption), - createSpecialConfig("client-output-buffer-limit", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigClientOutputBufferLimitOption, getConfigClientOutputBufferLimitOption, rewriteConfigClientOutputBufferLimitOption), - createSpecialConfig("oom-score-adj-values", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigOOMScoreAdjValuesOption, getConfigOOMScoreAdjValuesOption, rewriteConfigOOMScoreAdjValuesOption), - createSpecialConfig("notify-keyspace-events", NULL, MODIFIABLE_CONFIG, setConfigNotifyKeyspaceEventsOption, getConfigNotifyKeyspaceEventsOption, rewriteConfigNotifyKeyspaceEventsOption), - createSpecialConfig("bind", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigBindOption, getConfigBindOption, rewriteConfigBindOption), - createSpecialConfig("replicaof", "slaveof", IMMUTABLE_CONFIG | MULTI_ARG_CONFIG, setConfigReplicaOfOption, getConfigReplicaOfOption, rewriteConfigReplicaOfOption), + createSpecialConfig("dir", NULL, MODIFIABLE_CONFIG, setConfigDirOption, getConfigDirOption, rewriteConfigDirOption, NULL), + createSpecialConfig("save", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigSaveOption, getConfigSaveOption, rewriteConfigSaveOption, NULL), + createSpecialConfig("client-output-buffer-limit", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigClientOutputBufferLimitOption, getConfigClientOutputBufferLimitOption, rewriteConfigClientOutputBufferLimitOption, NULL), + createSpecialConfig("oom-score-adj-values", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigOOMScoreAdjValuesOption, getConfigOOMScoreAdjValuesOption, rewriteConfigOOMScoreAdjValuesOption, updateOOMScoreAdj), + createSpecialConfig("notify-keyspace-events", NULL, MODIFIABLE_CONFIG, setConfigNotifyKeyspaceEventsOption, getConfigNotifyKeyspaceEventsOption, rewriteConfigNotifyKeyspaceEventsOption, NULL), + createSpecialConfig("bind", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigBindOption, getConfigBindOption, rewriteConfigBindOption, applyBind), + createSpecialConfig("replicaof", "slaveof", IMMUTABLE_CONFIG | MULTI_ARG_CONFIG, setConfigReplicaOfOption, getConfigReplicaOfOption, rewriteConfigReplicaOfOption, NULL), /* NULL Terminator */ {NULL} diff --git a/src/replication.c b/src/replication.c index 0f2512fcb..1a4aa4c2d 100644 --- a/src/replication.c +++ b/src/replication.c @@ -122,17 +122,13 @@ void createReplicationBacklog(void) { } /* This function is called when the user modifies the replication backlog - * size at runtime. It is up to the function to both update the - * server.repl_backlog_size and to resize the buffer and setup it so that - * it contains the same data as the previous one (possibly less data, but - * the most recent bytes, or the same data and more free space in case the + * size at runtime. It is up to the function to resize the buffer and setup it + * so that it contains the same data as the previous one (possibly less data, + * but the most recent bytes, or the same data and more free space in case the * buffer is enlarged). */ -void resizeReplicationBacklog(long long newsize) { - if (newsize < CONFIG_REPL_BACKLOG_MIN_SIZE) - newsize = CONFIG_REPL_BACKLOG_MIN_SIZE; - if (server.repl_backlog_size == newsize) return; - - server.repl_backlog_size = newsize; +void resizeReplicationBacklog(void) { + if (server.repl_backlog_size < CONFIG_REPL_BACKLOG_MIN_SIZE) + server.repl_backlog_size = CONFIG_REPL_BACKLOG_MIN_SIZE; if (server.repl_backlog) incrementalTrimReplicationBacklog(REPL_BACKLOG_TRIM_BLOCKS_PER_CALL); } diff --git a/src/server.c b/src/server.c index e4ee48d65..05ab6b93a 100644 --- a/src/server.c +++ b/src/server.c @@ -200,7 +200,7 @@ struct redisServer server; /* Server global state */ */ struct redisCommand configSubcommands[] = { - {"set",configSetCommand,4, + {"set",configSetCommand,-4, "admin ok-stale no-script"}, {"get",configGetCommand,3, @@ -3838,8 +3838,6 @@ static void readOOMScoreAdj(void) { * depending on current role. */ int setOOMScoreAdj(int process_class) { - - if (server.oom_score_adj == OOM_SCORE_ADJ_NO) return C_OK; if (process_class == -1) process_class = (server.masterhost ? CONFIG_OOM_REPLICA : CONFIG_OOM_MASTER); @@ -3850,11 +3848,14 @@ int setOOMScoreAdj(int process_class) { int val; char buf[64]; - val = server.oom_score_adj_values[process_class]; - if (server.oom_score_adj == OOM_SCORE_RELATIVE) - val += server.oom_score_adj_base; - if (val > 1000) val = 1000; - if (val < -1000) val = -1000; + if (server.oom_score_adj != OOM_SCORE_ADJ_NO) { + val = server.oom_score_adj_values[process_class]; + if (server.oom_score_adj == OOM_SCORE_RELATIVE) + val += server.oom_score_adj_base; + if (val > 1000) val = 1000; + if (val < -1000) val = -1000; + } else + val = server.oom_score_adj_base; snprintf(buf, sizeof(buf) - 1, "%d\n", val); @@ -7190,57 +7191,19 @@ void redisAsciiArt(void) { zfree(buf); } -int changeBindAddr(sds *addrlist, int addrlist_len) { - int i; - int result = C_OK; - - char *prev_bindaddr[CONFIG_BINDADDR_MAX]; - int prev_bindaddr_count; - +int changeBindAddr(void) { /* Close old TCP and TLS servers */ closeSocketListeners(&server.ipfd); closeSocketListeners(&server.tlsfd); - /* Keep previous settings */ - prev_bindaddr_count = server.bindaddr_count; - memcpy(prev_bindaddr, server.bindaddr, sizeof(server.bindaddr)); - - /* Copy new settings */ - memset(server.bindaddr, 0, sizeof(server.bindaddr)); - for (i = 0; i < addrlist_len; i++) { - server.bindaddr[i] = zstrdup(addrlist[i]); - } - server.bindaddr_count = addrlist_len; - /* Bind to the new port */ if ((server.port != 0 && listenToPort(server.port, &server.ipfd) != C_OK) || (server.tls_port != 0 && listenToPort(server.tls_port, &server.tlsfd) != C_OK)) { - serverLog(LL_WARNING, "Failed to bind, trying to restore old listening sockets."); + serverLog(LL_WARNING, "Failed to bind"); - /* Restore old bind addresses */ - for (i = 0; i < addrlist_len; i++) { - zfree(server.bindaddr[i]); - } - memcpy(server.bindaddr, prev_bindaddr, sizeof(server.bindaddr)); - server.bindaddr_count = prev_bindaddr_count; - - /* Re-Listen TCP and TLS */ - server.ipfd.count = 0; - if (server.port != 0 && listenToPort(server.port, &server.ipfd) != C_OK) { - serverPanic("Failed to restore old listening TCP socket."); - } - - server.tlsfd.count = 0; - if (server.tls_port != 0 && listenToPort(server.tls_port, &server.tlsfd) != C_OK) { - serverPanic("Failed to restore old listening TLS socket."); - } - - result = C_ERR; - } else { - /* Free old bind addresses */ - for (i = 0; i < prev_bindaddr_count; i++) { - zfree(prev_bindaddr[i]); - } + closeSocketListeners(&server.ipfd); + closeSocketListeners(&server.tlsfd); + return C_ERR; } /* Create TCP and TLS event handlers */ @@ -7253,15 +7216,17 @@ int changeBindAddr(sds *addrlist, int addrlist_len) { if (server.set_proc_title) redisSetProcTitle(NULL); - return result; + return C_OK; } int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler) { socketFds new_sfd = {{0}}; + /* Close old servers */ + closeSocketListeners(sfd); + /* Just close the server if port disabled */ if (port == 0) { - closeSocketListeners(sfd); if (server.set_proc_title) redisSetProcTitle(NULL); return C_OK; } @@ -7277,9 +7242,6 @@ int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler) { return C_ERR; } - /* Close old servers */ - closeSocketListeners(sfd); - /* Copy new descriptors */ sfd->count = new_sfd.count; memcpy(sfd->fd, new_sfd.fd, sizeof(new_sfd.fd)); diff --git a/src/server.h b/src/server.h index ad458652e..c06b23631 100644 --- a/src/server.h +++ b/src/server.h @@ -2302,7 +2302,7 @@ void replicationCron(void); void replicationStartPendingFork(void); void replicationHandleMasterDisconnection(void); void replicationCacheMaster(client *c); -void resizeReplicationBacklog(long long newsize); +void resizeReplicationBacklog(); void replicationSetMaster(char *ip, int port); void replicationUnsetMaster(void); void refreshGoodSlavesCount(void); @@ -2497,7 +2497,7 @@ void setupSignalHandlers(void); void removeSignalHandlers(void); int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler); int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler); -int changeBindAddr(sds *addrlist, int addrlist_len); +int changeBindAddr(void); struct redisCommand *lookupCommand(robj **argv ,int argc); struct redisCommand *lookupCommandBySdsLogic(dict *commands, sds s); struct redisCommand *lookupCommandBySds(sds s); @@ -3064,6 +3064,7 @@ void swapMainDbWithTempDb(redisDb *tempDb); void tlsInit(void); void tlsCleanup(void); int tlsConfigure(redisTLSContextConfig *ctx_config); +int isTlsConfigured(void); #define redisDebug(fmt, ...) \ printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) diff --git a/src/tls.c b/src/tls.c index 2a78b6562..2fcf57b8f 100644 --- a/src/tls.c +++ b/src/tls.c @@ -372,6 +372,12 @@ error: return C_ERR; } +/* Return 1 if TLS was already configured, 0 otherwise. + */ +int isTlsConfigured(void) { + return redis_tls_ctx != NULL; +} + #ifdef TLS_DEBUGGING #define TLSCONN_DEBUG(fmt, ...) \ serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__) diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl index 480ada471..fc729e98b 100644 --- a/tests/unit/introspection.tcl +++ b/tests/unit/introspection.tcl @@ -288,6 +288,116 @@ start_server {tags {"introspection"}} { assert_equal [r config get save] {save {}} } } {} {external:skip} + + test {CONFIG SET with multiple args} { + set some_configs {maxmemory 10000001 repl-backlog-size 10000002 save {3000 5}} + + # Backup + set backups {} + foreach c [dict keys $some_configs] { + lappend backups $c [lindex [r config get $c] 1] + } + + # multi config set and veirfy + assert_equal [eval "r config set $some_configs"] "OK" + dict for {c val} $some_configs { + assert_equal [lindex [r config get $c] 1] $val + } + + # Restore backup + assert_equal [eval "r config set $backups"] "OK" + } + + test {CONFIG SET rollback on set error} { + # This test passes an invalid percent value to maxmemory-clients which should cause an + # input verification failure during the "set" phase before trying to apply the + # configuration. We want to make sure the correct failure happens and everything + # is rolled back. + # backup maxmemory config + set mm_backup [lindex [r config get maxmemory] 1] + set mmc_backup [lindex [r config get maxmemory-clients] 1] + set qbl_backup [lindex [r config get client-query-buffer-limit] 1] + # Set some value to maxmemory + assert_equal [r config set maxmemory 10000002] "OK" + # Set another value to maxmeory together with another invalid config + assert_error "ERR Config set failed - percentage argument must be less or equal to 100" { + r config set maxmemory 10000001 maxmemory-clients 200% client-query-buffer-limit invalid + } + # Validate we rolled back to original values + assert_equal [lindex [r config get maxmemory] 1] 10000002 + assert_equal [lindex [r config get maxmemory-clients] 1] $mmc_backup + assert_equal [lindex [r config get client-query-buffer-limit] 1] $qbl_backup + # Make sure we revert back to the previous maxmemory + assert_equal [r config set maxmemory $mm_backup] "OK" + } + + test {CONFIG SET rollback on apply error} { + # This test tries to configure a used port number in redis. This is expected + # to pass the `CONFIG SET` validity checking implementation but fail on + # actual "apply" of the setting. This will validate that after an "apply" + # failure we rollback to the previous values. + proc dummy_accept {chan addr port} {} + + set some_configs {maxmemory 10000001 port 0 client-query-buffer-limit 10m} + + # On Linux we also set the oom score adj which has an apply function. This is + # used to verify that even successful applies are rolled back if some other + # config's apply fails. + set oom_adj_avail [expr {!$::external && [exec uname] == "Linux"}] + if {$oom_adj_avail} { + proc get_oom_score_adj {} { + set pid [srv 0 pid] + set fd [open "/proc/$pid/oom_score_adj" "r"] + set val [gets $fd] + close $fd + return $val + } + set some_configs [linsert $some_configs 0 oom-score-adj yes oom-score-adj-values {1 1 1}] + set read_oom_adj [get_oom_score_adj] + } + + # Backup + set backups {} + foreach c [dict keys $some_configs] { + lappend backups $c [lindex [r config get $c] 1] + } + + + set used_port [expr ([dict get $backups port]+1)%65536] + dict set some_configs port $used_port + + + # Run a dummy server on used_port so we know we can't configure redis to + # use it. It's ok for this to fail because that means used_port is invalid + # anyway + catch {socket -server dummy_accept $used_port} + # Try to listen on the used port, pass some more configs to make sure the + # returned failure message is for the first bad config and everything is rolled back. + assert_error "ERR Config set failed - Unable to listen on this port*" { + eval "r config set $some_configs" + } + # Make sure we reverted back to previous configs + dict for {conf val} $backups { + assert_equal [lindex [r config get $conf] 1] $val + } + + if {$oom_adj_avail} { + assert_equal [get_oom_score_adj] $read_oom_adj + } + + # Make sure we can still communicate with the server (on the original port) + set r1 [redis_client] + assert_equal [$r1 ping] "PONG" + $r1 close + } + + test {CONFIG SET duplicate configs} { + assert_error "ERR*duplicate*" {r config set maxmemory 10000001 maxmemory 10000002} + } + + test {CONFIG SET set immutable} { + assert_error "ERR*immutable*" {r config set daemonize yes} + } # Config file at this point is at a weird state, and includes all # known keywords. Might be a good idea to avoid adding tests here.