Commit d7971f96 authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'origin/unstable' into 7.0

parents d2b5a579 acfb4f7a
......@@ -4,13 +4,20 @@
"complexity": "O(N) where N is the number of instances",
"group": "sentinel",
"since": "3.2.0",
"arity": 3,
"arity": -3,
"container": "SENTINEL",
"function": "sentinelCommand",
"command_flags": [
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "nodename",
"type": "string",
"multiple": true
}
]
}
}
......@@ -11,6 +11,24 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "ip",
"type": "string"
},
{
"name": "port",
"type": "integer"
},
{
"name": "current-epoch",
"type": "integer"
},
{
"name": "runid",
"type": "string"
}
]
}
}
......@@ -11,6 +11,12 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "master-name",
"type": "string"
}
]
}
}
......@@ -11,6 +11,24 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "name",
"type": "string"
},
{
"name": "ip",
"type": "string"
},
{
"name": "port",
"type": "integer"
},
{
"name": "quorum",
"type": "integer"
}
]
}
}
......@@ -11,6 +11,12 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "master-name",
"type": "string"
}
]
}
}
......@@ -11,6 +11,12 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "master-name",
"type": "string"
}
]
}
}
......@@ -11,6 +11,12 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "pattern",
"type": "pattern"
}
]
}
}
......@@ -11,6 +11,12 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "master-name",
"type": "string"
}
]
}
}
......@@ -11,6 +11,27 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "master-name",
"type": "string"
},
{
"name": "option_value",
"type": "block",
"multiple": true,
"arguments": [
{
"name": "option",
"type": "string"
},
{
"name": "value",
"type": "string"
}
]
}
]
}
}
......@@ -10,6 +10,28 @@
"ADMIN",
"SENTINEL",
"ONLY_SENTINEL"
],
"arguments": [
{
"name": "mode",
"type": "oneof",
"optional":true,
"multiple":true,
"arguments": [
{
"name": "crash-after-election",
"type": "pure-token"
},
{
"name": "crash-after-promotion",
"type": "pure-token"
},
{
"name": "help",
"type": "pure-token"
}
]
}
]
}
}
......@@ -9,7 +9,7 @@
"history": [
[
"7.0.0",
"Added the `NOW`, `FORCE` and `ABORT` modifiers. Introduced waiting for lagging replicas before exiting."
"Added the `NOW`, `FORCE` and `ABORT` modifiers."
]
],
"command_flags": [
......
......@@ -24,7 +24,7 @@
},
{
"name": "port",
"type": "string"
"type": "integer"
}
]
}
......
......@@ -96,7 +96,7 @@
"token": "GET",
"name": "pattern",
"key_spec_index": 1,
"type": "string",
"type": "pattern",
"optional": true,
"multiple": true,
"multiple_token": true
......
......@@ -82,7 +82,7 @@
"token": "GET",
"name": "pattern",
"key_spec_index": 1,
"type": "string",
"type": "pattern",
"optional": true,
"multiple": true,
"multiple_token": true
......
{
"SPUBLISH": {
"summary": "Post a message to a shard channel",
"complexity": "O(N) where N is the number of clients subscribed to the receiving shard channel.",
"group": "pubsub",
"since": "7.0.0",
"arity": 3,
"function": "spublishCommand",
"command_flags": [
"PUBSUB",
"LOADING",
"STALE",
"FAST",
"MAY_REPLICATE"
],
"arguments": [
{
"name": "channel",
"type": "string"
},
{
"name": "message",
"type": "string"
}
],
"key_specs": [
{
"flags": [
"NOT_KEY"
"SPUBLISH": {
"summary": "Post a message to a shard channel",
"complexity": "O(N) where N is the number of clients subscribed to the receiving shard channel.",
"group": "pubsub",
"since": "7.0.0",
"arity": 3,
"function": "spublishCommand",
"command_flags": [
"PUBSUB",
"LOADING",
"STALE",
"FAST",
"MAY_REPLICATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
]
}
"arguments": [
{
"name": "channel",
"type": "string"
},
{
"name": "message",
"type": "string"
}
],
"key_specs": [
{
"flags": [
"NOT_KEY"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
]
}
}
{
"SSUBSCRIBE": {
"summary": "Listen for messages published to the given shard channels",
"complexity": "O(N) where N is the number of shard channels to subscribe to.",
"group": "pubsub",
"since": "7.0.0",
"arity": -2,
"function": "ssubscribeCommand",
"command_flags": [
"PUBSUB",
"NOSCRIPT",
"LOADING",
"STALE"
],
"arguments": [
{
"name": "channel",
"type": "string",
"multiple": true
}
],
"key_specs": [
{
"flags": [
"NOT_KEY"
"SSUBSCRIBE": {
"summary": "Listen for messages published to the given shard channels",
"complexity": "O(N) where N is the number of shard channels to subscribe to.",
"group": "pubsub",
"since": "7.0.0",
"arity": -2,
"function": "ssubscribeCommand",
"command_flags": [
"PUBSUB",
"NOSCRIPT",
"LOADING",
"STALE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": -1,
"step": 1,
"limit": 0
}
}
}
]
}
"arguments": [
{
"name": "channel",
"type": "string",
"multiple": true
}
],
"key_specs": [
{
"flags": [
"NOT_KEY"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": -1,
"step": 1,
"limit": 0
}
}
}
]
}
}
......@@ -6,12 +6,7 @@
"since": "2.0.0",
"arity": -2,
"function": "subscribeCommand",
"history": [
[
"6.2.0",
"`RESET` can be called to exit subscribed state."
]
],
"history": [],
"command_flags": [
"PUBSUB",
"NOSCRIPT",
......
{
"SUNSUBSCRIBE": {
"summary": "Stop listening for messages posted to the given shard channels",
"complexity": "O(N) where N is the number of clients already subscribed to a channel.",
"group": "pubsub",
"since": "7.0.0",
"arity": -1,
"function": "sunsubscribeCommand",
"command_flags": [
"PUBSUB",
"NOSCRIPT",
"LOADING",
"STALE"
],
"arguments": [
{
"name": "channel",
"type": "string",
"optional": true,
"multiple": true
}
],
"key_specs": [
{
"flags": [
"NOT_KEY"
"SUNSUBSCRIBE": {
"summary": "Stop listening for messages posted to the given shard channels",
"complexity": "O(N) where N is the number of clients already subscribed to a channel.",
"group": "pubsub",
"since": "7.0.0",
"arity": -1,
"function": "sunsubscribeCommand",
"command_flags": [
"PUBSUB",
"NOSCRIPT",
"LOADING",
"STALE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": -1,
"step": 1,
"limit": 0
}
}
}
]
}
"arguments": [
{
"name": "channel",
"type": "string",
"optional": true,
"multiple": true
}
],
"key_specs": [
{
"flags": [
"NOT_KEY"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": -1,
"step": 1,
"limit": 0
}
}
}
]
}
}
......@@ -40,11 +40,6 @@
* Config file name-value maps.
*----------------------------------------------------------------------------*/
typedef struct configEnum {
const char *name;
const int val;
} configEnum;
typedef struct deprecatedConfig {
const char *name;
const int argc_min;
......@@ -168,7 +163,7 @@ int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 };
* rewrite. */
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 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) */
} boolConfigData;
......@@ -182,7 +177,7 @@ typedef struct stringConfigData {
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. */
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 convert_empty_to_null; /* Boolean indicating if empty SDS strings should
be stored as a NULL value. */
......@@ -191,7 +186,7 @@ typedef struct sdsConfigData {
typedef struct enumConfigData {
int *config; /* The pointer to the server config this value is stored in */
configEnum *enum_value; /* The underlying enum type this data represents */
const int default_value; /* The default value of the config on rewrite */
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) */
} enumConfigData;
......@@ -208,11 +203,6 @@ typedef enum numericType {
NUMERIC_TYPE_TIME_T,
} numericType;
#define INTEGER_CONFIG 0 /* No flags means a simple integer configuration */
#define MEMORY_CONFIG (1<<0) /* Indicates if this value can be loaded as a memory value */
#define PERCENT_CONFIG (1<<1) /* Indicates if this value can be loaded as a percent (and stored as a negative int) */
#define OCTAL_CONFIG (1<<2) /* This value uses octal representation */
typedef struct numericConfigData {
union {
int *i;
......@@ -230,7 +220,7 @@ typedef struct numericConfigData {
numericType numeric_type; /* An enum indicating the type of this value */
long long lower_bound; /* The lower bound of this numeric value */
long long upper_bound; /* The upper bound of this numeric value */
const long long default_value; /* The default value of the config on rewrite */
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) */
} numericConfigData;
......@@ -242,43 +232,44 @@ typedef union typeData {
numericConfigData numeric;
} typeData;
typedef struct standardConfig standardConfig;
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);
void (*init)(standardConfig *config);
/* 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);
int (*set)(standardConfig *config, 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);
sds (*get)(standardConfig *config);
/* Called on CONFIG REWRITE, required to rewrite the config state */
void (*rewrite)(typeData data, const char *name, struct rewriteConfigState *state);
void (*rewrite)(standardConfig *config, const char *name, struct rewriteConfigState *state);
} typeInterface;
typedef struct standardConfig {
struct standardConfig {
const char *name; /* The user visible name of this config */
const char *alias; /* An alias that can also be used for this config */
const unsigned int flags; /* Flags for this specific config */
unsigned int flags; /* Flags for this specific config */
typeInterface interface; /* The function pointers that define the type interface */
typeData data; /* The type specific data exposed used by the interface */
} standardConfig;
configType type; /* The type of config this is. */
void *privdata; /* privdata for this config, for module configs this is a ModuleConfig struct */
};
#define MODIFIABLE_CONFIG 0 /* This is the implied default for a standard
* config, which is mutable. */
#define IMMUTABLE_CONFIG (1ULL<<0) /* Can this value only be set at startup? */
#define SENSITIVE_CONFIG (1ULL<<1) /* Does this value contain sensitive information */
#define DEBUG_CONFIG (1ULL<<2) /* Values that are useful for debugging. */
#define MULTI_ARG_CONFIG (1ULL<<3) /* This config receives multiple arguments. */
#define HIDDEN_CONFIG (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */
#define PROTECTED_CONFIG (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#define DENY_LOADING_CONFIG (1ULL<<6) /* This config is forbidden during loading. */
dict *configs = NULL; /* Runtime config values */
standardConfig configs[];
/* Lookup a config by the provided sds string name, or return NULL
* if the config does not exist */
static standardConfig *lookupConfig(sds name) {
dictEntry *de = dictFind(configs, name);
return de ? dictGetVal(de) : NULL;
}
/*-----------------------------------------------------------------------------
* Enum access functions
......@@ -407,12 +398,6 @@ static int updateClientOutputBufferLimit(sds *args, int arg_len, const char **er
return 1;
}
void initConfigValues() {
for (standardConfig *config = configs; config->name != NULL; config++) {
if (config->interface.init) config->interface.init(config->data);
}
}
/* 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. */
......@@ -458,29 +443,23 @@ void loadServerConfigFromString(char *config) {
sdstolower(argv[0]);
/* Iterate the configs that are standard */
int match = 0;
for (standardConfig *config = configs; config->name != NULL; config++) {
if ((!strcasecmp(argv[0],config->name) ||
(config->alias && !strcasecmp(argv[0],config->alias))))
{
/* For normal single arg configs enforce we have a single argument.
* Note that MULTI_ARG_CONFIGs need to validate arg count on their own */
if (!(config->flags & MULTI_ARG_CONFIG) && argc != 2) {
err = "wrong number of arguments";
goto loaderr;
}
/* Set config using all arguments that follows */
if (!config->interface.set(config->data, &argv[1], argc-1, &err)) {
goto loaderr;
}
match = 1;
break;
standardConfig *config = lookupConfig(argv[0]);
if (config) {
/* For normal single arg configs enforce we have a single argument.
* Note that MULTI_ARG_CONFIGs need to validate arg count on their own */
if (!(config->flags & MULTI_ARG_CONFIG) && argc != 2) {
err = "wrong number of arguments";
goto loaderr;
}
/* Set config using all arguments that follows */
if (!config->interface.set(config, &argv[1], argc-1, &err)) {
goto loaderr;
}
}
/* If there's no matching above, we try matching them with deprecated configs */
if (!match) {
sdsfreesplitres(argv,argc);
continue;
} else {
int match = 0;
for (deprecatedConfig *config = deprecated_configs; config->name != NULL; config++) {
if (!strcasecmp(argv[0], config->name) &&
config->argc_min <= argc &&
......@@ -490,11 +469,10 @@ void loadServerConfigFromString(char *config) {
break;
}
}
}
if (match) {
sdsfreesplitres(argv,argc);
continue;
if (match) {
sdsfreesplitres(argv,argc);
continue;
}
}
/* Execute config directives */
......@@ -535,6 +513,13 @@ void loadServerConfigFromString(char *config) {
}
} else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
queueLoadModule(argv[1],&argv[2],argc-2);
} else if (strchr(argv[0], '.')) {
if (argc != 2) {
err = "Module config specified without value";
goto loaderr;
}
sds name = sdsdup(argv[0]);
if (!dictReplace(server.module_configs_queue, name, sdsdup(argv[1]))) sdsfree(name);
} else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */
......@@ -597,9 +582,10 @@ loaderr:
* Both filename and options can be NULL, in such a case are considered
* empty. This way loadServerConfig can be used to just load a file or
* just load a string. */
#define CONFIG_READ_LEN 1024
void loadServerConfig(char *filename, char config_from_stdin, char *options) {
sds config = sdsempty();
char buf[CONFIG_MAX_LINE+1];
char buf[CONFIG_READ_LEN+1];
FILE *fp;
glob_t globbuf;
......@@ -631,7 +617,7 @@ void loadServerConfig(char *filename, char config_from_stdin, char *options) {
globbuf.gl_pathv[i], strerror(errno));
exit(1);
}
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL)
config = sdscat(config,buf);
fclose(fp);
}
......@@ -647,7 +633,7 @@ void loadServerConfig(char *filename, char config_from_stdin, char *options) {
filename, strerror(errno));
exit(1);
}
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL)
config = sdscat(config,buf);
fclose(fp);
}
......@@ -657,7 +643,7 @@ void loadServerConfig(char *filename, char config_from_stdin, char *options) {
if (config_from_stdin) {
serverLog(LL_WARNING,"Reading config from stdin");
fp = stdin;
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL)
config = sdscat(config,buf);
}
......@@ -682,12 +668,45 @@ static int performInterfaceSet(standardConfig *config, sds value, const char **e
}
/* Set the config */
res = config->interface.set(config->data, argv, argc, errstr);
res = config->interface.set(config, 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) {
/* Find the config by name and attempt to set it to value. */
int performModuleConfigSetFromName(sds name, sds value, const char **err) {
standardConfig *config = lookupConfig(name);
if (!config || !(config->flags & MODULE_CONFIG)) {
*err = "Config name not found";
return 0;
}
return performInterfaceSet(config, value, err);
}
/* Find config by name and attempt to set it to its default value. */
int performModuleConfigSetDefaultFromName(sds name, const char **err) {
standardConfig *config = lookupConfig(name);
serverAssert(config);
if (!(config->flags & MODULE_CONFIG)) {
*err = "Config name not found";
return 0;
}
switch (config->type) {
case BOOL_CONFIG:
return setModuleBoolConfig(config->privdata, config->data.yesno.default_value, err);
case SDS_CONFIG:
return setModuleStringConfig(config->privdata, config->data.sds.default_value, err);
case NUMERIC_CONFIG:
return setModuleNumericConfig(config->privdata, config->data.numeric.default_value, err);
case ENUM_CONFIG:
return setModuleEnumConfig(config->privdata, config->data.enumd.default_value, err);
default:
serverPanic("Config type of module config is not allowed.");
}
return 0;
}
static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, int count, apply_fn *apply_fns, list *module_configs) {
int i;
const char *errstr = "unknown error";
/* Set all backup values */
......@@ -703,6 +722,10 @@ static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, i
serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr);
}
}
if (module_configs) {
if (!moduleConfigApplyConfig(module_configs, &errstr, NULL))
serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr);
}
}
/*-----------------------------------------------------------------------------
......@@ -714,6 +737,8 @@ void configSetCommand(client *c) {
const char *invalid_arg_name = NULL;
const char *err_arg_name = NULL;
standardConfig **set_configs; /* TODO: make this a dict for better performance */
list *module_configs_apply;
const char **config_names;
sds *new_values;
sds *old_values = NULL;
apply_fn *apply_fns; /* TODO: make this a set for better performance */
......@@ -728,7 +753,9 @@ void configSetCommand(client *c) {
}
config_count = (c->argc - 2) / 2;
module_configs_apply = listCreate();
set_configs = zcalloc(sizeof(standardConfig*)*config_count);
config_names = zcalloc(sizeof(char*)*config_count);
new_values = zmalloc(sizeof(sds*)*config_count);
old_values = zcalloc(sizeof(sds*)*config_count);
apply_fns = zcalloc(sizeof(apply_fn)*config_count);
......@@ -736,73 +763,76 @@ void configSetCommand(client *c) {
/* 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);
}
standardConfig *config = lookupConfig(c->argv[2+i*2]->ptr);
/* Fail if we couldn't find this config */
if (!config) {
if (!invalid_args) {
invalid_arg_name = c->argv[2+i*2]->ptr;
invalid_args = 1;
}
continue;
}
if (!invalid_args) {
if (config->flags & IMMUTABLE_CONFIG ||
(config->flags & PROTECTED_CONFIG && !allowProtectedAction(server.enable_protected_configs, c)))
{
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */
errstr = (config->flags & IMMUTABLE_CONFIG) ? "can't set immutable config" : "can't set protected config";
err_arg_name = c->argv[2+i*2]->ptr;
invalid_args = 1;
}
/* 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);
}
if (server.loading && config->flags & DENY_LOADING_CONFIG) {
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */
deny_loading_error = 1;
invalid_args = 1;
}
/* We continue to make sure we redact all the configs */
if (invalid_args) continue;
/* 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";
err_arg_name = c->argv[2+i*2]->ptr;
invalid_args = 1;
break;
}
}
set_configs[i] = config;
new_values[i] = c->argv[2+i*2+1]->ptr;
}
break;
}
if (config->flags & IMMUTABLE_CONFIG ||
(config->flags & PROTECTED_CONFIG && !allowProtectedAction(server.enable_protected_configs, c)))
{
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */
errstr = (config->flags & IMMUTABLE_CONFIG) ? "can't set immutable config" : "can't set protected config";
err_arg_name = c->argv[2+i*2]->ptr;
invalid_args = 1;
continue;
}
/* 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]) {
invalid_arg_name = c->argv[2+i*2]->ptr;
if (server.loading && config->flags & DENY_LOADING_CONFIG) {
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */
deny_loading_error = 1;
invalid_args = 1;
continue;
}
/* 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";
err_arg_name = c->argv[2+i*2]->ptr;
invalid_args = 1;
break;
}
}
set_configs[i] = config;
config_names[i] = config->name;
new_values[i] = c->argv[2+i*2+1]->ptr;
}
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);
old_values[i] = set_configs[i]->interface.get(set_configs[i]);
/* 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);
restoreBackupConfig(set_configs, old_values, i+1, NULL, NULL);
err_arg_name = set_configs[i]->name;
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) {
if (set_configs[i]->flags & MODULE_CONFIG) {
addModuleConfigApply(module_configs_apply, set_configs[i]->privdata);
} else 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++) {
......@@ -824,11 +854,20 @@ void configSetCommand(client *c) {
for (i = 0; i < config_count && apply_fns[i] != NULL; i++) {
if (!apply_fns[i](&errstr)) {
serverLog(LL_WARNING, "Failed applying new configuration. Possibly related to new %s setting. Restoring previous settings.", set_configs[config_map_fns[i]]->name);
restoreBackupConfig(set_configs, old_values, config_count, apply_fns);
restoreBackupConfig(set_configs, old_values, config_count, apply_fns, NULL);
err_arg_name = set_configs[config_map_fns[i]]->name;
goto err;
}
}
/* Apply all module configs that were set. */
if (!moduleConfigApplyConfig(module_configs_apply, &errstr, &err_arg_name)) {
serverLogRaw(LL_WARNING, "Failed applying new module configuration. Restoring previous settings.");
restoreBackupConfig(set_configs, old_values, config_count, apply_fns, module_configs_apply);
goto err;
}
RedisModuleConfigChangeV1 cc = {.num_changes = config_count, .config_names = config_names};
moduleFireServerEvent(REDISMODULE_EVENT_CONFIG, REDISMODULE_SUBEVENT_CONFIG_CHANGE, &cc);
addReply(c,shared.ok);
goto end;
......@@ -845,12 +884,14 @@ err:
}
end:
zfree(set_configs);
zfree(config_names);
zfree(new_values);
for (i = 0; i < config_count; i++)
sdsfree(old_values[i]);
zfree(old_values);
zfree(apply_fns);
zfree(config_map_fns);
listRelease(module_configs_apply);
}
/*-----------------------------------------------------------------------------
......@@ -858,38 +899,51 @@ end:
*----------------------------------------------------------------------------*/
void configGetCommand(client *c) {
void *replylen = addReplyDeferredLen(c);
int matches = 0;
int i;
dictEntry *de;
dictIterator *di;
/* Create a dictionary to store the matched configs */
dict *matches = dictCreate(&externalStringType);
for (i = 0; i < c->argc - 2; i++) {
robj *o = c->argv[2+i];
sds name = o->ptr;
/* If the string doesn't contain glob patterns, just directly
* look up the key in the dictionary. */
if (!strpbrk(name, "[*?")) {
if (dictFind(matches, name)) continue;
standardConfig *config = lookupConfig(name);
if (config) {
dictAdd(matches, name, config);
}
continue;
}
for (standardConfig *config = configs; config->name != NULL; config++) {
int matched_conf = 0;
int matched_alias = 0;
for (i = 0; i < c->argc - 2 && (!matched_conf || !matched_alias); i++) {
robj *o = c->argv[2+i];
char *pattern = o->ptr;
/* Otherwise, do a match against all items in the dictionary. */
di = dictGetIterator(configs);
while ((de = dictNext(di)) != NULL) {
standardConfig *config = dictGetVal(de);
/* Note that hidden configs require an exact match (not a pattern) */
if (!matched_conf &&
(((config->flags & HIDDEN_CONFIG) && !strcasecmp(pattern, config->name)) ||
(!(config->flags & HIDDEN_CONFIG) && stringmatch(pattern, config->name, 1)))) {
addReplyBulkCString(c, config->name);
addReplyBulkSds(c, config->interface.get(config->data));
matches++;
matched_conf = 1;
}
if (!matched_alias && config->alias &&
(((config->flags & HIDDEN_CONFIG) && !strcasecmp(pattern, config->alias)) ||
(!(config->flags & HIDDEN_CONFIG) && stringmatch(pattern, config->alias, 1)))) {
addReplyBulkCString(c, config->alias);
addReplyBulkSds(c, config->interface.get(config->data));
matches++;
matched_alias = 1;
if (config->flags & HIDDEN_CONFIG) continue;
if (dictFind(matches, config->name)) continue;
if (stringmatch(name, de->key, 1)) {
dictAdd(matches, de->key, config);
}
}
dictReleaseIterator(di);
}
setDeferredMapLen(c,replylen,matches);
di = dictGetIterator(matches);
addReplyMapLen(c, dictSize(matches));
while ((de = dictNext(di)) != NULL) {
standardConfig *config = (standardConfig *) dictGetVal(de);
addReplyBulkCString(c, de->key);
addReplyBulkSds(c, config->interface.get(config));
}
dictReleaseIterator(di);
dictRelease(matches);
}
/*-----------------------------------------------------------------------------
......@@ -996,17 +1050,32 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
FILE *fp = fopen(path,"r");
if (fp == NULL && errno != ENOENT) return NULL;
char buf[CONFIG_MAX_LINE+1];
struct redis_stat sb;
if (fp && redis_fstat(fileno(fp),&sb) == -1) return NULL;
int linenum = -1;
struct rewriteConfigState *state = rewriteConfigCreateState();
if (fp == NULL) return state;
if (fp == NULL || sb.st_size == 0) return state;
/* Read the old file line by line, populate the state. */
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL) {
/* Load the file content */
sds config = sdsnewlen(SDS_NOINIT,sb.st_size);
if (fread(config,1,sb.st_size,fp) == 0) {
sdsfree(config);
rewriteConfigReleaseState(state);
fclose(fp);
return NULL;
}
int i, totlines;
sds *lines = sdssplitlen(config,sdslen(config),"\n",1,&totlines);
/* Read the old content line by line, populate the state. */
for (i = 0; i < totlines; i++) {
int argc;
sds *argv;
sds line = sdstrim(sdsnew(buf),"\r\n\t ");
sds line = sdstrim(lines[i],"\r\n\t ");
lines[i] = NULL;
linenum++; /* Zero based, so we init at -1 */
......@@ -1020,12 +1089,13 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
/* Not a comment, split into arguments. */
argv = sdssplitargs(line,&argc);
if (argv == NULL) {
if (argv == NULL || (!server.sentinel_mode && !lookupConfig(argv[0]))) {
/* Apparently the line is unparsable for some reason, for
* instance it may have unbalanced quotes. Load it as a
* comment. */
* instance it may have unbalanced quotes, or may contain a
* config that doesn't exist anymore. Load it as a comment. */
sds aux = sdsnew("# ??? ");
aux = sdscatsds(aux,line);
if (argv) sdsfreesplitres(argv, argc);
sdsfree(line);
rewriteConfigAppendLine(state,aux);
continue;
......@@ -1062,6 +1132,8 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
sdsfreesplitres(argv,argc);
}
fclose(fp);
sdsfreesplitres(lines,totlines);
sdsfree(config);
return state;
}
......@@ -1235,8 +1307,8 @@ void rewriteConfigEnumOption(struct rewriteConfigState *state, const char *optio
}
/* Rewrite the save option. */
void rewriteConfigSaveOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigSaveOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
int j;
sds line;
......@@ -1296,8 +1368,8 @@ void rewriteConfigUserOption(struct rewriteConfigState *state) {
}
/* Rewrite the dir option, always using absolute paths.*/
void rewriteConfigDirOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigDirOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
char cwd[1024];
if (getcwd(cwd,sizeof(cwd)) == NULL) {
......@@ -1308,8 +1380,8 @@ void rewriteConfigDirOption(typeData data, const char *name, struct rewriteConfi
}
/* Rewrite the slaveof option. */
void rewriteConfigReplicaOfOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigReplicaOfOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
sds line;
/* If this is a master, we want all the slaveof config options
......@@ -1325,8 +1397,8 @@ void rewriteConfigReplicaOfOption(typeData data, const char *name, struct rewrit
}
/* Rewrite the notify-keyspace-events option. */
void rewriteConfigNotifyKeyspaceEventsOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigNotifyKeyspaceEventsOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
int force = server.notify_keyspace_events != 0;
sds line, flags;
......@@ -1339,8 +1411,8 @@ void rewriteConfigNotifyKeyspaceEventsOption(typeData data, const char *name, st
}
/* Rewrite the client-output-buffer-limit option. */
void rewriteConfigClientOutputBufferLimitOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigClientOutputBufferLimitOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
int j;
for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) {
int force = (server.client_obuf_limits[j].hard_limit_bytes !=
......@@ -1367,8 +1439,8 @@ void rewriteConfigClientOutputBufferLimitOption(typeData data, const char *name,
}
/* Rewrite the oom-score-adj-values option. */
void rewriteConfigOOMScoreAdjValuesOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigOOMScoreAdjValuesOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
int force = 0;
int j;
sds line;
......@@ -1387,8 +1459,8 @@ void rewriteConfigOOMScoreAdjValuesOption(typeData data, const char *name, struc
}
/* Rewrite the bind option. */
void rewriteConfigBindOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigBindOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
int force = 1;
sds line, addresses;
int is_default = 0;
......@@ -1508,10 +1580,14 @@ sds getConfigDebugInfo() {
/* Iterate the configs and "rewrite" the ones that have
* the debug flag. */
for (standardConfig *config = configs; config->name != NULL; config++) {
dictIterator *di = dictGetIterator(configs);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
standardConfig *config = dictGetVal(de);
if (!(config->flags & DEBUG_CONFIG)) continue;
config->interface.rewrite(config->data, config->name, state);
config->interface.rewrite(config, config->name, state);
}
dictReleaseIterator(di);
sds info = rewriteConfigGetContentFromState(state);
rewriteConfigReleaseState(state);
return info;
......@@ -1599,9 +1675,15 @@ int rewriteConfig(char *path, int force_write) {
* the rewrite state. */
/* Iterate the configs that are standard */
for (standardConfig *config = configs; config->name != NULL; config++) {
if (config->interface.rewrite) config->interface.rewrite(config->data, config->name, state);
dictIterator *di = dictGetIterator(configs);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
standardConfig *config = dictGetVal(de);
/* Only rewrite the primary names */
if (config->flags & ALIAS_CONFIG) continue;
if (config->interface.rewrite) config->interface.rewrite(config, de->key, state);
}
dictReleaseIterator(di);
rewriteConfigUserOption(state);
rewriteConfigLoadmoduleOption(state);
......@@ -1656,38 +1738,46 @@ static char loadbuf[LOADBUF_SIZE];
*/
/* Bool Configs */
static void boolConfigInit(typeData data) {
*data.yesno.config = data.yesno.default_value;
static void boolConfigInit(standardConfig *config) {
*config->data.yesno.config = config->data.yesno.default_value;
}
static int boolConfigSet(typeData data, sds *argv, int argc, const char **err) {
static int boolConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(argc);
int yn = yesnotoi(argv[0]);
if (yn == -1) {
*err = "argument must be 'yes' or 'no'";
return 0;
}
if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err))
if (config->data.yesno.is_valid_fn && !config->data.yesno.is_valid_fn(yn, err))
return 0;
int prev = *(data.yesno.config);
int prev = config->flags & MODULE_CONFIG ? getModuleBoolConfig(config->privdata) : *(config->data.yesno.config);
if (prev != yn) {
*(data.yesno.config) = yn;
if (config->flags & MODULE_CONFIG) {
return setModuleBoolConfig(config->privdata, yn, err);
}
*(config->data.yesno.config) = yn;
return 1;
}
return 2;
}
static sds boolConfigGet(typeData data) {
return sdsnew(*data.yesno.config ? "yes" : "no");
static sds boolConfigGet(standardConfig *config) {
if (config->flags & MODULE_CONFIG) {
return sdsnew(getModuleBoolConfig(config->privdata) ? "yes" : "no");
}
return sdsnew(*config->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);
static void boolConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) {
int val = config->flags & MODULE_CONFIG ? getModuleBoolConfig(config->privdata) : *(config->data.yesno.config);
rewriteConfigYesNoOption(state, name, val, config->data.yesno.default_value);
}
#define createBoolConfig(name, alias, flags, config_addr, default, is_valid, apply) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite, apply) \
.type = BOOL_CONFIG, \
.data.yesno = { \
.config = &(config_addr), \
.default_value = (default), \
......@@ -1696,61 +1786,69 @@ static void boolConfigRewrite(typeData data, const char *name, struct rewriteCon
}
/* String Configs */
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 void stringConfigInit(standardConfig *config) {
*config->data.string.config = (config->data.string.convert_empty_to_null && !config->data.string.default_value) ? NULL : zstrdup(config->data.string.default_value);
}
static int stringConfigSet(typeData data, sds *argv, int argc, const char **err) {
static int stringConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(argc);
if (data.string.is_valid_fn && !data.string.is_valid_fn(argv[0], err))
if (config->data.string.is_valid_fn && !config->data.string.is_valid_fn(argv[0], err))
return 0;
char *prev = *data.string.config;
char *new = (data.string.convert_empty_to_null && !argv[0][0]) ? NULL : argv[0];
char *prev = *config->data.string.config;
char *new = (config->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;
*config->data.string.config = new != NULL ? zstrdup(new) : NULL;
zfree(prev);
return 1;
}
return 2;
}
static sds stringConfigGet(typeData data) {
return sdsnew(*data.string.config ? *data.string.config : "");
static sds stringConfigGet(standardConfig *config) {
return sdsnew(*config->data.string.config ? *config->data.string.config : "");
}
static void stringConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value);
static void stringConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) {
rewriteConfigStringOption(state, name,*(config->data.string.config), config->data.string.default_value);
}
/* SDS Configs */
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 void sdsConfigInit(standardConfig *config) {
*config->data.sds.config = (config->data.sds.convert_empty_to_null && !config->data.sds.default_value) ? NULL : sdsnew(config->data.sds.default_value);
}
static int sdsConfigSet(typeData data, sds *argv, int argc, const char **err) {
static int sdsConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(argc);
if (data.sds.is_valid_fn && !data.sds.is_valid_fn(argv[0], err))
if (config->data.sds.is_valid_fn && !config->data.sds.is_valid_fn(argv[0], err))
return 0;
sds prev = *data.sds.config;
sds new = (data.string.convert_empty_to_null && (sdslen(argv[0]) == 0)) ? NULL : argv[0];
sds prev = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config;
sds new = (config->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);
if (config->flags & MODULE_CONFIG) {
return setModuleStringConfig(config->privdata, new, err);
}
*config->data.sds.config = new != NULL ? sdsdup(new) : NULL;
return 1;
}
if (config->flags & MODULE_CONFIG && prev) sdsfree(prev);
return 2;
}
static sds sdsConfigGet(typeData data) {
if (*data.sds.config) {
return sdsdup(*data.sds.config);
static sds sdsConfigGet(standardConfig *config) {
sds val = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config;
if (val) {
if (config->flags & MODULE_CONFIG) return val;
return sdsdup(val);
} else {
return sdsnew("");
}
}
static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
rewriteConfigSdsOption(state, name, *(data.sds.config), data.sds.default_value);
static void sdsConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) {
sds val = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config;
rewriteConfigSdsOption(state, name, val, config->data.sds.default_value);
if (val) sdsfree(val);
}
......@@ -1760,6 +1858,7 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
#define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite, apply) \
.type = STRING_CONFIG, \
.data.string = { \
.config = &(config_addr), \
.default_value = (default), \
......@@ -1771,6 +1870,7 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
#define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite, apply) \
.type = SDS_CONFIG, \
.data.sds = { \
.config = &(config_addr), \
.default_value = (default), \
......@@ -1780,16 +1880,16 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
}
/* Enum configs */
static void enumConfigInit(typeData data) {
*data.enumd.config = data.enumd.default_value;
static void enumConfigInit(standardConfig *config) {
*config->data.enumd.config = config->data.enumd.default_value;
}
static int enumConfigSet(typeData data, sds *argv, int argc, const char **err) {
static int enumConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(argc);
int enumval = configEnumGetValue(data.enumd.enum_value, argv[0]);
int enumval = configEnumGetValue(config->data.enumd.enum_value, argv[0]);
if (enumval == INT_MIN) {
sds enumerr = sdsnew("argument must be one of the following: ");
configEnum *enumNode = data.enumd.enum_value;
configEnum *enumNode = config->data.enumd.enum_value;
while(enumNode->name != NULL) {
enumerr = sdscatlen(enumerr, enumNode->name,
strlen(enumNode->name));
......@@ -1805,27 +1905,32 @@ static int enumConfigSet(typeData data, sds *argv, int argc, const char **err) {
*err = loadbuf;
return 0;
}
if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err))
if (config->data.enumd.is_valid_fn && !config->data.enumd.is_valid_fn(enumval, err))
return 0;
int prev = *(data.enumd.config);
int prev = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config);
if (prev != enumval) {
*(data.enumd.config) = enumval;
if (config->flags & MODULE_CONFIG)
return setModuleEnumConfig(config->privdata, enumval, err);
*(config->data.enumd.config) = enumval;
return 1;
}
return 2;
}
static sds enumConfigGet(typeData data) {
return sdsnew(configEnumGetNameOrUnknown(data.enumd.enum_value,*data.enumd.config));
static sds enumConfigGet(standardConfig *config) {
int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config);
return sdsnew(configEnumGetNameOrUnknown(config->data.enumd.enum_value,val));
}
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);
static void enumConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) {
int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config);
rewriteConfigEnumOption(state, name, val, config->data.enumd.enum_value, config->data.enumd.default_value);
}
#define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, apply) { \
embedCommonConfig(name, alias, flags) \
embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite, apply) \
.type = ENUM_CONFIG, \
.data.enumd = { \
.config = &(config_addr), \
.default_value = (default), \
......@@ -1836,69 +1941,74 @@ static void enumConfigRewrite(typeData data, const char *name, struct rewriteCon
/* Gets a 'long long val' and sets it into the union, using a macro to get
* compile time type check. */
#define SET_NUMERIC_TYPE(val) \
if (data.numeric.numeric_type == NUMERIC_TYPE_INT) { \
*(data.numeric.config.i) = (int) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_UINT) { \
*(data.numeric.config.ui) = (unsigned int) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_LONG) { \
*(data.numeric.config.l) = (long) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { \
*(data.numeric.config.ul) = (unsigned long) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { \
*(data.numeric.config.ll) = (long long) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { \
*(data.numeric.config.ull) = (unsigned long long) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { \
*(data.numeric.config.st) = (size_t) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { \
*(data.numeric.config.sst) = (ssize_t) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { \
*(data.numeric.config.ot) = (off_t) val; \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { \
*(data.numeric.config.tt) = (time_t) val; \
int setNumericType(standardConfig *config, long long val, const char **err) {
if (config->data.numeric.numeric_type == NUMERIC_TYPE_INT) {
*(config->data.numeric.config.i) = (int) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_UINT) {
*(config->data.numeric.config.ui) = (unsigned int) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG) {
*(config->data.numeric.config.l) = (long) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG) {
*(config->data.numeric.config.ul) = (unsigned long) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) {
if (config->flags & MODULE_CONFIG)
return setModuleNumericConfig(config->privdata, val, err);
else *(config->data.numeric.config.ll) = (long long) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) {
*(config->data.numeric.config.ull) = (unsigned long long) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) {
*(config->data.numeric.config.st) = (size_t) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) {
*(config->data.numeric.config.sst) = (ssize_t) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) {
*(config->data.numeric.config.ot) = (off_t) val;
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) {
*(config->data.numeric.config.tt) = (time_t) val;
}
return 1;
}
/* Gets a 'long long val' and sets it with the value from the union, using a
* macro to get compile time type check. */
#define GET_NUMERIC_TYPE(val) \
if (data.numeric.numeric_type == NUMERIC_TYPE_INT) { \
val = *(data.numeric.config.i); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_UINT) { \
val = *(data.numeric.config.ui); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_LONG) { \
val = *(data.numeric.config.l); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { \
val = *(data.numeric.config.ul); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { \
val = *(data.numeric.config.ll); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { \
val = *(data.numeric.config.ull); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { \
val = *(data.numeric.config.st); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { \
val = *(data.numeric.config.sst); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { \
val = *(data.numeric.config.ot); \
} else if (data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { \
val = *(data.numeric.config.tt); \
if (config->data.numeric.numeric_type == NUMERIC_TYPE_INT) { \
val = *(config->data.numeric.config.i); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_UINT) { \
val = *(config->data.numeric.config.ui); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG) { \
val = *(config->data.numeric.config.l); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { \
val = *(config->data.numeric.config.ul); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { \
if (config->flags & MODULE_CONFIG) val = getModuleNumericConfig(config->privdata); \
else val = *(config->data.numeric.config.ll); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { \
val = *(config->data.numeric.config.ull); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { \
val = *(config->data.numeric.config.st); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { \
val = *(config->data.numeric.config.sst); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { \
val = *(config->data.numeric.config.ot); \
} else if (config->data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { \
val = *(config->data.numeric.config.tt); \
}
/* Numeric configs */
static void numericConfigInit(typeData data) {
SET_NUMERIC_TYPE(data.numeric.default_value)
static void numericConfigInit(standardConfig *config) {
setNumericType(config, config->data.numeric.default_value, NULL);
}
static int numericBoundaryCheck(typeData data, long long ll, const char **err) {
if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG ||
data.numeric.numeric_type == NUMERIC_TYPE_UINT ||
data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) {
static int numericBoundaryCheck(standardConfig *config, long long ll, const char **err) {
if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG ||
config->data.numeric.numeric_type == NUMERIC_TYPE_UINT ||
config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) {
/* Boundary check for unsigned types */
unsigned long long ull = ll;
unsigned long long upper_bound = data.numeric.upper_bound;
unsigned long long lower_bound = data.numeric.lower_bound;
unsigned long long upper_bound = config->data.numeric.upper_bound;
unsigned long long lower_bound = config->data.numeric.lower_bound;
if (ull > upper_bound || ull < lower_bound) {
if (data.numeric.flags & OCTAL_CONFIG) {
if (config->data.numeric.flags & OCTAL_CONFIG) {
snprintf(loadbuf, LOADBUF_SIZE,
"argument must be between %llo and %llo inclusive",
lower_bound,
......@@ -1914,21 +2024,21 @@ static int numericBoundaryCheck(typeData data, long long ll, const char **err) {
}
} else {
/* Boundary check for percentages */
if (data.numeric.flags & PERCENT_CONFIG && ll < 0) {
if (ll < data.numeric.lower_bound) {
if (config->data.numeric.flags & PERCENT_CONFIG && ll < 0) {
if (ll < config->data.numeric.lower_bound) {
snprintf(loadbuf, LOADBUF_SIZE,
"percentage argument must be less or equal to %lld",
-data.numeric.lower_bound);
-config->data.numeric.lower_bound);
*err = loadbuf;
return 0;
}
}
/* Boundary check for signed types */
else if (ll > data.numeric.upper_bound || ll < data.numeric.lower_bound) {
else if (ll > config->data.numeric.upper_bound || ll < config->data.numeric.lower_bound) {
snprintf(loadbuf, LOADBUF_SIZE,
"argument must be between %lld and %lld inclusive",
data.numeric.lower_bound,
data.numeric.upper_bound);
config->data.numeric.lower_bound,
config->data.numeric.upper_bound);
*err = loadbuf;
return 0;
}
......@@ -1936,9 +2046,9 @@ static int numericBoundaryCheck(typeData data, long long ll, const char **err) {
return 1;
}
static int numericParseString(typeData data, sds value, const char **err, long long *res) {
static int numericParseString(standardConfig *config, sds value, const char **err, long long *res) {
/* First try to parse as memory */
if (data.numeric.flags & MEMORY_CONFIG) {
if (config->data.numeric.flags & MEMORY_CONFIG) {
int memerr;
*res = memtoull(value, &memerr);
if (!memerr)
......@@ -1946,7 +2056,7 @@ static int numericParseString(typeData data, sds value, const char **err, long l
}
/* Attempt to parse as percent */
if (data.numeric.flags & PERCENT_CONFIG &&
if (config->data.numeric.flags & PERCENT_CONFIG &&
sdslen(value) > 1 && value[sdslen(value)-1] == '%' &&
string2ll(value, sdslen(value)-1, res) &&
*res >= 0) {
......@@ -1956,7 +2066,7 @@ static int numericParseString(typeData data, sds value, const char **err, long l
}
/* Attempt to parse as an octal number */
if (data.numeric.flags & OCTAL_CONFIG) {
if (config->data.numeric.flags & OCTAL_CONFIG) {
char *endptr;
errno = 0;
*res = strtoll(value, &endptr, 8);
......@@ -1965,58 +2075,57 @@ static int numericParseString(typeData data, sds value, const char **err, long l
}
/* Attempt a simple number (no special flags set) */
if (!data.numeric.flags && string2ll(value, sdslen(value), res))
if (!config->data.numeric.flags && string2ll(value, sdslen(value), res))
return 1;
/* Select appropriate error string */
if (data.numeric.flags & MEMORY_CONFIG &&
data.numeric.flags & PERCENT_CONFIG)
if (config->data.numeric.flags & MEMORY_CONFIG &&
config->data.numeric.flags & PERCENT_CONFIG)
*err = "argument must be a memory or percent value" ;
else if (data.numeric.flags & MEMORY_CONFIG)
else if (config->data.numeric.flags & MEMORY_CONFIG)
*err = "argument must be a memory value";
else if (data.numeric.flags & OCTAL_CONFIG)
else if (config->data.numeric.flags & OCTAL_CONFIG)
*err = "argument couldn't be parsed as an octal number";
else
*err = "argument couldn't be parsed into an integer";
return 0;
}
static int numericConfigSet(typeData data, sds *argv, int argc, const char **err) {
static int numericConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(argc);
long long ll, prev = 0;
if (!numericParseString(data, argv[0], err, &ll))
if (!numericParseString(config, argv[0], err, &ll))
return 0;
if (!numericBoundaryCheck(data, ll, err))
if (!numericBoundaryCheck(config, ll, err))
return 0;
if (data.numeric.is_valid_fn && !data.numeric.is_valid_fn(ll, err))
if (config->data.numeric.is_valid_fn && !config->data.numeric.is_valid_fn(ll, err))
return 0;
GET_NUMERIC_TYPE(prev)
if (prev != ll) {
SET_NUMERIC_TYPE(ll)
return 1;
return setNumericType(config, ll, err);
}
return 2;
}
static sds numericConfigGet(typeData data) {
static sds numericConfigGet(standardConfig *config) {
char buf[128];
long long value = 0;
GET_NUMERIC_TYPE(value)
if (data.numeric.flags & PERCENT_CONFIG && value < 0) {
if (config->data.numeric.flags & PERCENT_CONFIG && value < 0) {
int len = ll2string(buf, sizeof(buf), -value);
buf[len] = '%';
buf[len+1] = '\0';
}
else if (data.numeric.flags & MEMORY_CONFIG) {
else if (config->data.numeric.flags & MEMORY_CONFIG) {
ull2string(buf, sizeof(buf), value);
} else if (data.numeric.flags & OCTAL_CONFIG) {
} else if (config->data.numeric.flags & OCTAL_CONFIG) {
snprintf(buf, sizeof(buf), "%llo", value);
} else {
ll2string(buf, sizeof(buf), value);
......@@ -2024,25 +2133,26 @@ static sds numericConfigGet(typeData data) {
return sdsnew(buf);
}
static void numericConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
static void numericConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) {
long long value = 0;
GET_NUMERIC_TYPE(value)
if (data.numeric.flags & PERCENT_CONFIG && value < 0) {
rewriteConfigPercentOption(state, name, -value, data.numeric.default_value);
} else if (data.numeric.flags & MEMORY_CONFIG) {
rewriteConfigBytesOption(state, name, value, data.numeric.default_value);
} else if (data.numeric.flags & OCTAL_CONFIG) {
rewriteConfigOctalOption(state, name, value, data.numeric.default_value);
if (config->data.numeric.flags & PERCENT_CONFIG && value < 0) {
rewriteConfigPercentOption(state, name, -value, config->data.numeric.default_value);
} else if (config->data.numeric.flags & MEMORY_CONFIG) {
rewriteConfigBytesOption(state, name, value, config->data.numeric.default_value);
} else if (config->data.numeric.flags & OCTAL_CONFIG) {
rewriteConfigOctalOption(state, name, value, config->data.numeric.default_value);
} else {
rewriteConfigNumericalOption(state, name, value, data.numeric.default_value);
rewriteConfigNumericalOption(state, name, value, config->data.numeric.default_value);
}
}
#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, apply) \
.type = NUMERIC_CONFIG, \
.data.numeric = { \
.lower_bound = (lower), \
.upper_bound = (upper), \
......@@ -2121,6 +2231,7 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
}
#define createSpecialConfig(name, alias, modifiable, setfn, getfn, rewritefn, applyfn) { \
.type = SPECIAL_CONFIG, \
embedCommonConfig(name, alias, modifiable) \
embedConfigInterface(NULL, setfn, getfn, rewritefn, applyfn) \
}
......@@ -2397,8 +2508,8 @@ static int applyTLSPort(const char **err) {
#endif /* USE_OPENSSL */
static int setConfigDirOption(typeData data, sds *argv, int argc, const char **err) {
UNUSED(data);
static int setConfigDirOption(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(config);
if (argc != 1) {
*err = "wrong number of arguments";
return 0;
......@@ -2410,8 +2521,8 @@ static int setConfigDirOption(typeData data, sds *argv, int argc, const char **e
return 1;
}
static sds getConfigDirOption(typeData data) {
UNUSED(data);
static sds getConfigDirOption(standardConfig *config) {
UNUSED(config);
char buf[1024];
if (getcwd(buf,sizeof(buf)) == NULL)
......@@ -2420,8 +2531,8 @@ static sds getConfigDirOption(typeData data) {
return sdsnew(buf);
}
static int setConfigSaveOption(typeData data, sds *argv, int argc, const char **err) {
UNUSED(data);
static int setConfigSaveOption(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(config);
int j;
/* Special case: treat single arg "" as zero args indicating empty save configuration */
......@@ -2473,8 +2584,8 @@ static int setConfigSaveOption(typeData data, sds *argv, int argc, const char **
return 1;
}
static sds getConfigSaveOption(typeData data) {
UNUSED(data);
static sds getConfigSaveOption(standardConfig *config) {
UNUSED(config);
sds buf = sdsempty();
int j;
......@@ -2489,13 +2600,13 @@ static sds getConfigSaveOption(typeData data) {
return buf;
}
static int setConfigClientOutputBufferLimitOption(typeData data, sds *argv, int argc, const char **err) {
UNUSED(data);
static int setConfigClientOutputBufferLimitOption(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(config);
return updateClientOutputBufferLimit(argv, argc, err);
}
static sds getConfigClientOutputBufferLimitOption(typeData data) {
UNUSED(data);
static sds getConfigClientOutputBufferLimitOption(standardConfig *config) {
UNUSED(config);
sds buf = sdsempty();
int j;
for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) {
......@@ -2513,11 +2624,11 @@ static sds getConfigClientOutputBufferLimitOption(typeData data) {
/* 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) {
static int setConfigOOMScoreAdjValuesOption(standardConfig *config, sds *argv, int argc, const char **err) {
int i;
int values[CONFIG_OOM_COUNT];
int change = 0;
UNUSED(data);
UNUSED(config);
if (argc != CONFIG_OOM_COUNT) {
*err = "wrong number of arguments";
......@@ -2558,8 +2669,8 @@ static int setConfigOOMScoreAdjValuesOption(typeData data, sds *argv, int argc,
return change ? 1 : 2;
}
static sds getConfigOOMScoreAdjValuesOption(typeData data) {
UNUSED(data);
static sds getConfigOOMScoreAdjValuesOption(standardConfig *config) {
UNUSED(config);
sds buf = sdsempty();
int j;
......@@ -2572,8 +2683,8 @@ static sds getConfigOOMScoreAdjValuesOption(typeData data) {
return buf;
}
static int setConfigNotifyKeyspaceEventsOption(typeData data, sds *argv, int argc, const char **err) {
UNUSED(data);
static int setConfigNotifyKeyspaceEventsOption(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(config);
if (argc != 1) {
*err = "wrong number of arguments";
return 0;
......@@ -2587,13 +2698,13 @@ static int setConfigNotifyKeyspaceEventsOption(typeData data, sds *argv, int arg
return 1;
}
static sds getConfigNotifyKeyspaceEventsOption(typeData data) {
UNUSED(data);
static sds getConfigNotifyKeyspaceEventsOption(standardConfig *config) {
UNUSED(config);
return keyspaceEventsFlagsToString(server.notify_keyspace_events);
}
static int setConfigBindOption(typeData data, sds* argv, int argc, const char **err) {
UNUSED(data);
static int setConfigBindOption(standardConfig *config, sds* argv, int argc, const char **err) {
UNUSED(config);
int j;
if (argc > CONFIG_BINDADDR_MAX) {
......@@ -2615,8 +2726,8 @@ static int setConfigBindOption(typeData data, sds* argv, int argc, const char **
return 1;
}
static int setConfigReplicaOfOption(typeData data, sds* argv, int argc, const char **err) {
UNUSED(data);
static int setConfigReplicaOfOption(standardConfig *config, sds* argv, int argc, const char **err) {
UNUSED(config);
if (argc != 2) {
*err = "wrong number of arguments";
......@@ -2639,13 +2750,13 @@ static int setConfigReplicaOfOption(typeData data, sds* argv, int argc, const ch
return 1;
}
static sds getConfigBindOption(typeData data) {
UNUSED(data);
static sds getConfigBindOption(standardConfig *config) {
UNUSED(config);
return sdsjoin(server.bindaddr,server.bindaddr_count," ");
}
static sds getConfigReplicaOfOption(typeData data) {
UNUSED(data);
static sds getConfigReplicaOfOption(standardConfig *config) {
UNUSED(config);
char buf[256];
if (server.masterhost)
snprintf(buf,sizeof(buf),"%s %d",
......@@ -2661,8 +2772,8 @@ int allowProtectedAction(int config, client *c) {
}
static int setConfigLatencyTrackingInfoPercentilesOutputOption(typeData data, sds *argv, int argc, const char **err) {
UNUSED(data);
static int setConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config, sds *argv, int argc, const char **err) {
UNUSED(config);
zfree(server.latency_tracking_info_percentiles);
server.latency_tracking_info_percentiles = NULL;
server.latency_tracking_info_percentiles_len = argc;
......@@ -2694,8 +2805,8 @@ configerr:
return 0;
}
static sds getConfigLatencyTrackingInfoPercentilesOutputOption(typeData data) {
UNUSED(data);
static sds getConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config) {
UNUSED(config);
sds buf = sdsempty();
for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) {
char fbuf[128];
......@@ -2709,8 +2820,8 @@ static sds getConfigLatencyTrackingInfoPercentilesOutputOption(typeData data) {
}
/* Rewrite the latency-tracking-info-percentiles option. */
void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(typeData data, const char *name, struct rewriteConfigState *state) {
UNUSED(data);
void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config, const char *name, struct rewriteConfigState *state) {
UNUSED(config);
sds line = sdsnew(name);
/* Rewrite latency-tracking-info-percentiles parameters,
* or an empty 'latency-tracking-info-percentiles ""' line to avoid the
......@@ -2729,7 +2840,7 @@ void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(typeData data, cons
rewriteConfigRewriteLine(state,name,line,1);
}
standardConfig configs[] = {
standardConfig static_configs[] = {
/* Bool configs */
createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),
......@@ -2771,7 +2882,7 @@ standardConfig configs[] = {
createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, server.crashlog_enabled, 1, NULL, updateSighandlerEnabled),
createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, server.memcheck_enabled, 1, NULL, NULL),
createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.use_exit_on_panic, 0, NULL, NULL),
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, server.disable_thp, 1, NULL, NULL),
createBoolConfig("disable-thp", NULL, IMMUTABLE_CONFIG, server.disable_thp, 1, NULL, NULL),
createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL),
createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, NULL, NULL),
createBoolConfig("latency-tracking", NULL, MODIFIABLE_CONFIG, server.latency_tracking_enabled, 1, NULL, NULL),
......@@ -2930,10 +3041,105 @@ standardConfig configs[] = {
createSpecialConfig("replicaof", "slaveof", IMMUTABLE_CONFIG | MULTI_ARG_CONFIG, setConfigReplicaOfOption, getConfigReplicaOfOption, rewriteConfigReplicaOfOption, NULL),
createSpecialConfig("latency-tracking-info-percentiles", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigLatencyTrackingInfoPercentilesOutputOption, getConfigLatencyTrackingInfoPercentilesOutputOption, rewriteConfigLatencyTrackingInfoPercentilesOutputOption, NULL),
/* NULL Terminator */
/* NULL Terminator, this is dropped when we convert to the runtime array. */
{NULL}
};
/* Create a new config by copying the passed in config. Returns 1 on success
* or 0 when their was already a config with the same name.. */
int registerConfigValue(const char *name, const standardConfig *config, int alias) {
standardConfig *new = zmalloc(sizeof(standardConfig));
memcpy(new, config, sizeof(standardConfig));
if (alias) {
new->flags |= ALIAS_CONFIG;
new->name = config->alias;
new->alias = config->name;
}
return dictAdd(configs, sdsnew(name), new) == DICT_OK;
}
/* Initialize configs to their default values and create and populate the
* runtime configuration dictionary. */
void initConfigValues() {
configs = dictCreate(&sdsHashDictType);
dictExpand(configs, sizeof(static_configs) / sizeof(standardConfig));
for (standardConfig *config = static_configs; config->name != NULL; config++) {
if (config->interface.init) config->interface.init(config);
/* Add the primary config to the dictionary. */
int ret = registerConfigValue(config->name, config, 0);
serverAssert(ret);
/* Aliases are the same as their primary counter parts, but they
* also have a flag indicating they are the alias. */
if (config->alias) {
int ret = registerConfigValue(config->alias, config, ALIAS_CONFIG);
serverAssert(ret);
}
}
}
/* Remove a config by name from the configs dict. */
void removeConfig(sds name) {
standardConfig *config = lookupConfig(name);
if (!config) return;
if (config->flags & MODULE_CONFIG) {
sdsfree((sds) config->name);
if (config->type == ENUM_CONFIG) {
configEnum *enumNode = config->data.enumd.enum_value;
while(enumNode->name != NULL) {
zfree(enumNode->name);
enumNode++;
}
zfree(config->data.enumd.enum_value);
} else if (config->type == SDS_CONFIG) {
if (config->data.sds.default_value) sdsfree((sds)config->data.sds.default_value);
}
}
dictDelete(configs, name);
}
/*-----------------------------------------------------------------------------
* Module Config
*----------------------------------------------------------------------------*/
/* Create a bool/string/enum/numeric standardConfig for a module config in the configs dictionary */
void addModuleBoolConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val) {
sds config_name = sdscatfmt(sdsempty(), "%s.%s", module_name, name);
int config_dummy_address;
standardConfig module_config = createBoolConfig(config_name, NULL, flags | MODULE_CONFIG, config_dummy_address, default_val, NULL, NULL);
module_config.data.yesno.config = NULL;
module_config.privdata = privdata;
registerConfigValue(config_name, &module_config, 0);
}
void addModuleStringConfig(const char *module_name, const char *name, int flags, void *privdata, sds default_val) {
sds config_name = sdscatfmt(sdsempty(), "%s.%s", module_name, name);
sds config_dummy_address;
standardConfig module_config = createSDSConfig(config_name, NULL, flags | MODULE_CONFIG, 0, config_dummy_address, default_val, NULL, NULL);
module_config.data.sds.config = NULL;
module_config.privdata = privdata;
registerConfigValue(config_name, &module_config, 0);
}
void addModuleEnumConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val, configEnum *enum_vals) {
sds config_name = sdscatfmt(sdsempty(), "%s.%s", module_name, name);
int config_dummy_address;
standardConfig module_config = createEnumConfig(config_name, NULL, flags | MODULE_CONFIG, enum_vals, config_dummy_address, default_val, NULL, NULL);
module_config.data.enumd.config = NULL;
module_config.privdata = privdata;
registerConfigValue(config_name, &module_config, 0);
}
void addModuleNumericConfig(const char *module_name, const char *name, int flags, void *privdata, long long default_val, int conf_flags, long long lower, long long upper) {
sds config_name = sdscatfmt(sdsempty(), "%s.%s", module_name, name);
long long config_dummy_address;
standardConfig module_config = createLongLongConfig(config_name, NULL, flags | MODULE_CONFIG, lower, upper, config_dummy_address, default_val, conf_flags, NULL, NULL);
module_config.data.numeric.config.ll = NULL;
module_config.privdata = privdata;
registerConfigValue(config_name, &module_config, 0);
}
/*-----------------------------------------------------------------------------
* CONFIG HELP
*----------------------------------------------------------------------------*/
......@@ -2975,8 +3181,10 @@ void configRewriteCommand(client *c) {
return;
}
if (rewriteConfig(server.configfile, 0) == -1) {
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
/* save errno in case of being tainted. */
int err = errno;
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(err));
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(err));
} else {
serverLog(LL_WARNING,"CONFIG REWRITE executed with success.");
addReply(c,shared.ok);
......
......@@ -218,9 +218,12 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
val->lru = old->lru;
}
/* Although the key is not really deleted from the database, we regard
overwrite as two steps of unlink+add, so we still need to call the unlink
callback of the module. */
* overwrite as two steps of unlink+add, so we still need to call the unlink
* callback of the module. */
moduleNotifyKeyUnlink(key,old,db->id);
/* We want to try to unblock any client using a blocking XREADGROUP */
if (old->type == OBJ_STREAM)
signalKeyAsReady(db,key,old->type);
dictSetVal(db->dict, de, val);
if (server.lazyfree_lazy_server_del) {
......@@ -311,6 +314,9 @@ static int dbGenericDelete(redisDb *db, robj *key, int async) {
robj *val = dictGetVal(de);
/* Tells the module that the key has been unlinked from the database. */
moduleNotifyKeyUnlink(key,val,db->id);
/* We want to try to unblock any client using a blocking XREADGROUP */
if (val->type == OBJ_STREAM)
signalKeyAsReady(db,key,val->type);
if (async) {
freeObjAsync(key, val, db->id);
dictSetVal(db->dict, de, NULL);
......@@ -551,6 +557,7 @@ void signalFlushedDb(int dbid, int async) {
}
for (int j = startdb; j <= enddb; j++) {
scanDatabaseForDeletedStreams(&server.db[j], NULL);
touchAllWatchedKeysInDb(&server.db[j], NULL);
}
......@@ -1311,7 +1318,7 @@ void copyCommand(client *c) {
* one or more blocked clients for B[LR]POP or other blocking commands
* and signal the keys as ready if they are of the right type. See the comment
* where the function is used for more info. */
void scanDatabaseForReadyLists(redisDb *db) {
void scanDatabaseForReadyKeys(redisDb *db) {
dictEntry *de;
dictIterator *di = dictGetSafeIterator(db->blocking_keys);
while((de = dictNext(di)) != NULL) {
......@@ -1325,6 +1332,39 @@ void scanDatabaseForReadyLists(redisDb *db) {
dictReleaseIterator(di);
}
/* Since we are unblocking XREADGROUP clients in the event the
* key was deleted/overwritten we must do the same in case the
* database was flushed/swapped. */
void scanDatabaseForDeletedStreams(redisDb *emptied, redisDb *replaced_with) {
/* Optimization: If no clients are in type BLOCKED_STREAM,
* we can skip this loop. */
if (!server.blocked_clients_by_type[BLOCKED_STREAM]) return;
dictEntry *de;
dictIterator *di = dictGetSafeIterator(emptied->blocking_keys);
while((de = dictNext(di)) != NULL) {
robj *key = dictGetKey(de);
int was_stream = 0, is_stream = 0;
dictEntry *kde = dictFind(emptied->dict, key->ptr);
if (kde) {
robj *value = dictGetVal(kde);
was_stream = value->type == OBJ_STREAM;
}
if (replaced_with) {
dictEntry *kde = dictFind(replaced_with->dict, key->ptr);
if (kde) {
robj *value = dictGetVal(kde);
is_stream = value->type == OBJ_STREAM;
}
}
/* We want to try to unblock any client using a blocking XREADGROUP */
if (was_stream && !is_stream)
signalKeyAsReady(emptied, key, OBJ_STREAM);
}
dictReleaseIterator(di);
}
/* Swap two databases at runtime so that all clients will magically see
* the new database even if already connected. Note that the client
* structure c->db points to a given DB, so we need to be smarter and
......@@ -1345,6 +1385,10 @@ int dbSwapDatabases(int id1, int id2) {
touchAllWatchedKeysInDb(db1, db2);
touchAllWatchedKeysInDb(db2, db1);
/* Try to unblock any XREADGROUP clients if the key no longer exists. */
scanDatabaseForDeletedStreams(db1, db2);
scanDatabaseForDeletedStreams(db2, db1);
/* Swap hash tables. Note that we don't swap blocking_keys,
* ready_keys and watched_keys, since we want clients to
* remain in the same DB they were. */
......@@ -1367,8 +1411,8 @@ int dbSwapDatabases(int id1, int id2) {
* in dbAdd() when a list is created. So here we need to rescan
* the list of clients blocked on lists and signal lists as ready
* if needed. */
scanDatabaseForReadyLists(db1);
scanDatabaseForReadyLists(db2);
scanDatabaseForReadyKeys(db1);
scanDatabaseForReadyKeys(db2);
return C_OK;
}
......@@ -1391,6 +1435,9 @@ void swapMainDbWithTempDb(redisDb *tempDb) {
* client watching keys. */
touchAllWatchedKeysInDb(activedb, newdb);
/* Try to unblock any XREADGROUP clients if the key no longer exists. */
scanDatabaseForDeletedStreams(activedb, newdb);
/* Swap hash tables. Note that we don't swap blocking_keys,
* ready_keys and watched_keys, since clients
* remain in the same DB they were. */
......@@ -1413,7 +1460,7 @@ void swapMainDbWithTempDb(redisDb *tempDb) {
* in dbAdd() when a list is created. So here we need to rescan
* the list of clients blocked on lists and signal lists as ready
* if needed. */
scanDatabaseForReadyLists(activedb);
scanDatabaseForReadyKeys(activedb);
}
trackingInvalidateKeysOnFlush(1);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment