Unverified Commit 4a45386e authored by Madelyn Olson's avatar Madelyn Olson Committed by GitHub
Browse files

Move most of the configuration to a hashtable (#10323)

* Moved configuration storage from a list to a hash table
* Configs are returned in a non-deterministic order. It's possible that a client was relying on order (hopefully not).
* Fixed an esoteric bug where if you did a set with an alias with an error, it would throw an error indicating a bug with the preferred name for that config.
parent 21aabab4
...@@ -263,7 +263,7 @@ typedef struct typeInterface { ...@@ -263,7 +263,7 @@ typedef struct typeInterface {
typedef struct standardConfig { typedef struct standardConfig {
const char *name; /* The user visible name of this config */ const char *name; /* The user visible name of this config */
const char *alias; /* An alias that can also be used for 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 */ typeInterface interface; /* The function pointers that define the type interface */
typeData data; /* The type specific data exposed used by the interface */ typeData data; /* The type specific data exposed used by the interface */
} standardConfig; } standardConfig;
...@@ -277,8 +277,16 @@ typedef struct standardConfig { ...@@ -277,8 +277,16 @@ typedef struct standardConfig {
#define HIDDEN_CONFIG (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */ #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 PROTECTED_CONFIG (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#define DENY_LOADING_CONFIG (1ULL<<6) /* This config is forbidden during loading. */ #define DENY_LOADING_CONFIG (1ULL<<6) /* This config is forbidden during loading. */
#define ALIAS_CONFIG (1ULL<<7) /* For configs with multiple names, this flag is set on the alias. */
standardConfig configs[]; dict *configs = NULL; /* Runtime config values */
/* 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 * Enum access functions
...@@ -407,12 +415,6 @@ static int updateClientOutputBufferLimit(sds *args, int arg_len, const char **er ...@@ -407,12 +415,6 @@ static int updateClientOutputBufferLimit(sds *args, int arg_len, const char **er
return 1; 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 /* 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 * within conf file parsing. This is only needed to support the deprecated
* abnormal aggregate `save T C` functionality. Remove in the future. */ * abnormal aggregate `save T C` functionality. Remove in the future. */
...@@ -458,29 +460,23 @@ void loadServerConfigFromString(char *config) { ...@@ -458,29 +460,23 @@ void loadServerConfigFromString(char *config) {
sdstolower(argv[0]); sdstolower(argv[0]);
/* Iterate the configs that are standard */ /* Iterate the configs that are standard */
int match = 0; standardConfig *config = lookupConfig(argv[0]);
for (standardConfig *config = configs; config->name != NULL; config++) { if (config) {
if ((!strcasecmp(argv[0],config->name) || /* For normal single arg configs enforce we have a single argument.
(config->alias && !strcasecmp(argv[0],config->alias)))) * Note that MULTI_ARG_CONFIGs need to validate arg count on their own */
{ if (!(config->flags & MULTI_ARG_CONFIG) && argc != 2) {
/* For normal single arg configs enforce we have a single argument. err = "wrong number of arguments";
* Note that MULTI_ARG_CONFIGs need to validate arg count on their own */ goto loaderr;
if (!(config->flags & MULTI_ARG_CONFIG) && argc != 2) { }
err = "wrong number of arguments"; /* Set config using all arguments that follows */
goto loaderr; if (!config->interface.set(config->data, &argv[1], argc-1, &err)) {
} 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;
} }
}
/* If there's no matching above, we try matching them with deprecated configs */ sdsfreesplitres(argv,argc);
if (!match) { continue;
} else {
int match = 0;
for (deprecatedConfig *config = deprecated_configs; config->name != NULL; config++) { for (deprecatedConfig *config = deprecated_configs; config->name != NULL; config++) {
if (!strcasecmp(argv[0], config->name) && if (!strcasecmp(argv[0], config->name) &&
config->argc_min <= argc && config->argc_min <= argc &&
...@@ -490,11 +486,10 @@ void loadServerConfigFromString(char *config) { ...@@ -490,11 +486,10 @@ void loadServerConfigFromString(char *config) {
break; break;
} }
} }
} if (match) {
sdsfreesplitres(argv,argc);
if (match) { continue;
sdsfreesplitres(argv,argc); }
continue;
} }
/* Execute config directives */ /* Execute config directives */
...@@ -736,55 +731,55 @@ void configSetCommand(client *c) { ...@@ -736,55 +731,55 @@ void configSetCommand(client *c) {
/* Find all relevant configs */ /* Find all relevant configs */
for (i = 0; i < config_count; i++) { for (i = 0; i < config_count; i++) {
for (standardConfig *config = configs; config->name != NULL; config++) { standardConfig *config = lookupConfig(c->argv[2+i*2]->ptr);
if ((!strcasecmp(c->argv[2+i*2]->ptr,config->name) || /* Fail if we couldn't find this config */
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias)))) { if (!config) {
if (!invalid_args) {
/* Note: it's important we run over ALL passed configs and check if we need to call `redactClientCommandArgument()`. invalid_arg_name = c->argv[2+i*2]->ptr;
* This is in order to avoid anyone using this command for a log/slowlog/monitor/etc. displaying sensitive info. invalid_args = 1;
* So even if we encounter an error we still continue running over the remaining arguments. */ }
if (config->flags & SENSITIVE_CONFIG) { continue;
redactClientCommandArgument(c,2+i*2+1); }
}
if (!invalid_args) { /* Note: it's important we run over ALL passed configs and check if we need to call `redactClientCommandArgument()`.
if (config->flags & IMMUTABLE_CONFIG || * This is in order to avoid anyone using this command for a log/slowlog/monitor/etc. displaying sensitive info.
(config->flags & PROTECTED_CONFIG && !allowProtectedAction(server.enable_protected_configs, c))) * So even if we encounter an error we still continue running over the remaining arguments. */
{ if (config->flags & SENSITIVE_CONFIG) {
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ redactClientCommandArgument(c,2+i*2+1);
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;
}
if (server.loading && config->flags & DENY_LOADING_CONFIG) { /* We continue to make sure we redact all the configs */
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ if (invalid_args) continue;
deny_loading_error = 1;
invalid_args = 1;
}
/* If this config appears twice then fail */ if (config->flags & IMMUTABLE_CONFIG ||
for (j = 0; j < i; j++) { (config->flags & PROTECTED_CONFIG && !allowProtectedAction(server.enable_protected_configs, c)))
if (set_configs[j] == config) { {
/* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */
errstr = "duplicate parameter"; errstr = (config->flags & IMMUTABLE_CONFIG) ? "can't set immutable config" : "can't set protected config";
err_arg_name = c->argv[2+i*2]->ptr; err_arg_name = c->argv[2+i*2]->ptr;
invalid_args = 1; invalid_args = 1;
break; continue;
}
}
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 (server.loading && config->flags & DENY_LOADING_CONFIG) {
if (!invalid_args && !set_configs[i]) { /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */
invalid_arg_name = c->argv[2+i*2]->ptr; deny_loading_error = 1;
invalid_args = 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;
new_values[i] = c->argv[2+i*2+1]->ptr;
} }
if (invalid_args) goto err; if (invalid_args) goto err;
...@@ -858,38 +853,51 @@ end: ...@@ -858,38 +853,51 @@ end:
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
void configGetCommand(client *c) { void configGetCommand(client *c) {
void *replylen = addReplyDeferredLen(c);
int matches = 0;
int i; 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++) { /* Otherwise, do a match against all items in the dictionary. */
int matched_conf = 0; di = dictGetIterator(configs);
int matched_alias = 0;
for (i = 0; i < c->argc - 2 && (!matched_conf || !matched_alias); i++) { while ((de = dictNext(di)) != NULL) {
robj *o = c->argv[2+i]; standardConfig *config = dictGetVal(de);
char *pattern = o->ptr;
/* Note that hidden configs require an exact match (not a pattern) */ /* Note that hidden configs require an exact match (not a pattern) */
if (!matched_conf && if (config->flags & HIDDEN_CONFIG) continue;
(((config->flags & HIDDEN_CONFIG) && !strcasecmp(pattern, config->name)) || if (dictFind(matches, config->name)) continue;
(!(config->flags & HIDDEN_CONFIG) && stringmatch(pattern, config->name, 1)))) { if (stringmatch(name, de->key, 1)) {
addReplyBulkCString(c, config->name); dictAdd(matches, de->key, config);
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;
} }
} }
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->data));
}
dictReleaseIterator(di);
dictRelease(matches);
} }
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
...@@ -1508,10 +1516,14 @@ sds getConfigDebugInfo() { ...@@ -1508,10 +1516,14 @@ sds getConfigDebugInfo() {
/* Iterate the configs and "rewrite" the ones that have /* Iterate the configs and "rewrite" the ones that have
* the debug flag. */ * 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; if (!(config->flags & DEBUG_CONFIG)) continue;
config->interface.rewrite(config->data, config->name, state); config->interface.rewrite(config->data, config->name, state);
} }
dictReleaseIterator(di);
sds info = rewriteConfigGetContentFromState(state); sds info = rewriteConfigGetContentFromState(state);
rewriteConfigReleaseState(state); rewriteConfigReleaseState(state);
return info; return info;
...@@ -1599,9 +1611,15 @@ int rewriteConfig(char *path, int force_write) { ...@@ -1599,9 +1611,15 @@ int rewriteConfig(char *path, int force_write) {
* the rewrite state. */ * the rewrite state. */
/* Iterate the configs that are standard */ /* Iterate the configs that are standard */
for (standardConfig *config = configs; config->name != NULL; config++) { dictIterator *di = dictGetIterator(configs);
if (config->interface.rewrite) config->interface.rewrite(config->data, config->name, state); 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->data, de->key, state);
} }
dictReleaseIterator(di);
rewriteConfigUserOption(state); rewriteConfigUserOption(state);
rewriteConfigLoadmoduleOption(state); rewriteConfigLoadmoduleOption(state);
...@@ -2729,7 +2747,7 @@ void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(typeData data, cons ...@@ -2729,7 +2747,7 @@ void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(typeData data, cons
rewriteConfigRewriteLine(state,name,line,1); rewriteConfigRewriteLine(state,name,line,1);
} }
standardConfig configs[] = { standardConfig static_configs[] = {
/* Bool configs */ /* Bool configs */
createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL), createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL), createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),
...@@ -2934,6 +2952,40 @@ standardConfig configs[] = { ...@@ -2934,6 +2952,40 @@ standardConfig configs[] = {
{NULL} {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->data);
/* 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);
}
}
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* CONFIG HELP * CONFIG HELP
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
......
...@@ -526,6 +526,30 @@ dictType stringSetDictType = { ...@@ -526,6 +526,30 @@ dictType stringSetDictType = {
NULL /* allow to expand */ NULL /* allow to expand */
}; };
/* Dict for for case-insensitive search using null terminated C strings.
* The key and value do not have a destructor. */
dictType externalStringType = {
distCStrCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
distCStrKeyCaseCompare, /* key compare */
NULL, /* key destructor */
NULL, /* val destructor */
NULL /* allow to expand */
};
/* Dict for case-insensitive search using sds objects with a zmalloc
* allocated object as the value. */
dictType sdsHashDictType = {
dictSdsCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */
dictVanillaFree, /* val destructor */
NULL /* allow to expand */
};
int htNeedsResize(dict *dict) { int htNeedsResize(dict *dict) {
long long size, used; long long size, used;
......
...@@ -2326,6 +2326,8 @@ extern dictType dbDictType; ...@@ -2326,6 +2326,8 @@ extern dictType dbDictType;
extern double R_Zero, R_PosInf, R_NegInf, R_Nan; extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
extern dictType hashDictType; extern dictType hashDictType;
extern dictType stringSetDictType; extern dictType stringSetDictType;
extern dictType externalStringType;
extern dictType sdsHashDictType;
extern dictType dbExpiresDictType; extern dictType dbExpiresDictType;
extern dictType modulesDictType; extern dictType modulesDictType;
extern dictType sdsReplyDictType; extern dictType sdsReplyDictType;
......
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