summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--redis.conf12
-rw-r--r--src/config.c762
-rw-r--r--src/replication.c16
-rw-r--r--src/server.c74
-rw-r--r--src/server.h5
-rw-r--r--src/tls.c6
-rw-r--r--tests/unit/introspection.tcl110
7 files changed, 532 insertions, 453 deletions
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 <seconds> <changes>
+# save <seconds> <changes> [<seconds> <changes> ...]
#
-# 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;
+
+ /* 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;
- /* 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);
- }
+ 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);
- if (config->flags & MULTI_ARG_CONFIG) {
- argv = sdssplitlen(o->ptr, sdslen(o->ptr), " ", 1, &argc);
- if (argv == NULL) {
- goto badfmt;
+ /* 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;
+ /* 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;
-badfmt: /* Bad format errors */
+err:
if (errstr) {
- addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s' - %s",
- (char*)o->ptr,
- (char*)c->argv[2]->ptr,
- errstr);
+ addReplyErrorFormat(c,"Config set failed - %s", errstr);
} else {
- addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
- (char*)o->ptr,
- (char*)c->argv[2]->ptr);
+ 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;
- }
+ *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.";
- return 0;
- }
+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.");
-
- /* 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.");
- }
+ serverLog(LL_WARNING, "Failed to bind");
- 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.