diff options
Diffstat (limited to 'src/third_party/wiredtiger/src/config/config_check.c')
-rw-r--r-- | src/third_party/wiredtiger/src/config/config_check.c | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/third_party/wiredtiger/src/config/config_check.c b/src/third_party/wiredtiger/src/config/config_check.c new file mode 100644 index 00000000000..310e54c3349 --- /dev/null +++ b/src/third_party/wiredtiger/src/config/config_check.c @@ -0,0 +1,370 @@ +/*- + * Copyright (c) 2008-2014 WiredTiger, Inc. + * All rights reserved. + * + * See the file LICENSE for redistribution information. + */ + +#include "wt_internal.h" + +static int config_check( + WT_SESSION_IMPL *, const WT_CONFIG_CHECK *, const char *, size_t); + +/* + * __conn_foc_add -- + * Add a new entry into the connection's free-on-close list. + */ +static int +__conn_foc_add(WT_SESSION_IMPL *session, const void *p) +{ + WT_CONNECTION_IMPL *conn; + + conn = S2C(session); + + /* + * Our caller is expected to be holding any locks we need. + */ + WT_RET(__wt_realloc_def( + session, &conn->foc_size, conn->foc_cnt + 1, &conn->foc)); + + conn->foc[conn->foc_cnt++] = (void *)p; + return (0); +} + +/* + * __wt_conn_foc_discard -- + * Discard any memory the connection accumulated. + */ +void +__wt_conn_foc_discard(WT_SESSION_IMPL *session) +{ + WT_CONNECTION_IMPL *conn; + size_t i; + + conn = S2C(session); + + /* + * If we have a list of chunks to free, run through the list, then + * free the list itself. + */ + for (i = 0; i < conn->foc_cnt; ++i) + __wt_free(session, conn->foc[i]); + __wt_free(session, conn->foc); +} + +/* + * __wt_configure_method -- + * WT_CONNECTION.configure_method. + */ +int +__wt_configure_method(WT_SESSION_IMPL *session, + const char *method, const char *uri, + const char *config, const char *type, const char *check) +{ + const WT_CONFIG_CHECK *cp; + WT_CONFIG_CHECK *checks, *newcheck; + const WT_CONFIG_ENTRY **epp; + WT_CONFIG_ENTRY *entry; + WT_CONNECTION_IMPL *conn; + WT_DECL_RET; + size_t cnt; + char *newcheck_name, *p; + + /* + * !!! + * We ignore the specified uri, that is, all new configuration options + * will be valid for all data sources. That's shouldn't be too bad + * as the worst that can happen is an application might specify some + * configuration option and not get an error -- the option should be + * ignored by the underlying implementation since it's unexpected, so + * there shouldn't be any real problems. Eventually I expect we will + * get the whole data-source thing sorted, at which time there may be + * configuration arrays for each data source, and that's when the uri + * will matter. + */ + WT_UNUSED(uri); + + conn = S2C(session); + checks = newcheck = NULL; + entry = NULL; + newcheck_name = NULL; + + /* Argument checking; we only support a limited number of types. */ + if (config == NULL) + WT_RET_MSG(session, EINVAL, "no configuration specified"); + if (type == NULL) + WT_RET_MSG(session, EINVAL, "no configuration type specified"); + if (strcmp(type, "boolean") != 0 && strcmp(type, "int") != 0 && + strcmp(type, "list") != 0 && strcmp(type, "string") != 0) + WT_RET_MSG(session, EINVAL, + "type must be one of \"boolean\", \"int\", \"list\" or " + "\"string\""); + + /* Find a match for the method name. */ + for (epp = conn->config_entries; (*epp)->method != NULL; ++epp) + if (strcmp((*epp)->method, method) == 0) + break; + if ((*epp)->method == NULL) + WT_RET_MSG(session, + WT_NOTFOUND, "no method matching %s found", method); + + /* + * Technically possible for threads to race, lock the connection while + * adding the new configuration information. We're holding the lock + * for an extended period of time, but configuration changes should be + * rare and only happen during startup. + */ + __wt_spin_lock(session, &conn->api_lock); + + /* + * Allocate new configuration entry and fill it in. + * + * The new base value is the previous base value, a separator and the + * new configuration string. + */ + WT_ERR(__wt_calloc_def(session, 1, &entry)); + entry->method = (*epp)->method; + WT_ERR(__wt_calloc_def(session, + strlen((*epp)->base) + strlen(",") + strlen(config) + 1, &p)); + (void)strcpy(p, (*epp)->base); + (void)strcat(p, ","); + (void)strcat(p, config); + entry->base = p; + + /* + * There may be a default value in the config argument passed in (for + * example, (kvs_parallelism=64"). The default value isn't part of the + * name, build a new one. + */ + WT_ERR(__wt_strdup(session, config, &newcheck_name)); + if ((p = strchr(newcheck_name, '=')) != NULL) + *p = '\0'; + + /* + * The new configuration name may replace an existing check with new + * information, in that case skip the old version. + */ + cnt = 0; + if ((*epp)->checks != NULL) + for (cp = (*epp)->checks; cp->name != NULL; ++cp) + ++cnt; + WT_ERR(__wt_calloc_def(session, cnt + 2, &checks)); + cnt = 0; + if ((*epp)->checks != NULL) + for (cp = (*epp)->checks; cp->name != NULL; ++cp) + if (strcmp(newcheck_name, cp->name) != 0) + checks[cnt++] = *cp; + newcheck = &checks[cnt]; + newcheck->name = newcheck_name; + WT_ERR(__wt_strdup(session, type, &newcheck->type)); + if (check != NULL) + WT_ERR(__wt_strdup(session, check, &newcheck->checks)); + entry->checks = checks; + + /* + * Confirm the configuration string passes the new set of + * checks. + */ + WT_ERR(config_check(session, entry->checks, config, 0)); + + /* + * The next time this configuration is updated, we don't want to figure + * out which of these pieces of memory were allocated and will need to + * be free'd on close (this isn't a heavily used API and it's too much + * work); add them all to the free-on-close list now. We don't check + * for errors deliberately, we'd have to figure out which elements have + * already been added to the free-on-close array and which have not in + * order to avoid freeing chunks of memory twice. Again, this isn't a + * commonly used API and it shouldn't ever happen, just leak it. + */ + (void)__conn_foc_add(session, entry->base); + (void)__conn_foc_add(session, entry); + (void)__conn_foc_add(session, checks); + (void)__conn_foc_add(session, newcheck->type); + (void)__conn_foc_add(session, newcheck->checks); + (void)__conn_foc_add(session, newcheck_name); + + /* + * Instead of using locks to protect configuration information, assume + * we can atomically update a pointer to a chunk of memory, and because + * a pointer is never partially written, readers will correctly see the + * original or new versions of the memory. Readers might be using the + * old version as it's being updated, though, which means we cannot free + * the old chunk of memory until all possible readers have finished. + * Currently, that's on connection close: in other words, we can use + * this because it's small amounts of memory, and we really, really do + * not want to acquire locks every time we access configuration strings, + * since that's done on every API call. + */ + WT_PUBLISH(*epp, entry); + + if (0) { +err: if (entry != NULL) { + __wt_free(session, entry->base); + __wt_free(session, entry); + } + __wt_free(session, checks); + if (newcheck != NULL) { + __wt_free(session, newcheck->type); + __wt_free(session, newcheck->checks); + } + __wt_free(session, newcheck_name); + } + + __wt_spin_unlock(session, &conn->api_lock); + return (ret); +} + +/* + * __wt_config_check -- + * Check the keys in an application-supplied config string match what is + * specified in an array of check strings. + */ +int +__wt_config_check(WT_SESSION_IMPL *session, + const WT_CONFIG_ENTRY *entry, const char *config, size_t config_len) +{ + /* + * Callers don't check, it's a fast call without a configuration or + * check array. + */ + return (config == NULL || entry->checks == NULL ? + 0 : config_check(session, entry->checks, config, config_len)); +} + +/* + * config_check -- + * Check the keys in an application-supplied config string match what is + * specified in an array of check strings. + */ +static int +config_check(WT_SESSION_IMPL *session, + const WT_CONFIG_CHECK *checks, const char *config, size_t config_len) +{ + WT_CONFIG parser, cparser, sparser; + WT_CONFIG_ITEM k, v, ck, cv, dummy; + WT_DECL_RET; + int badtype, found, i; + + /* + * The config_len parameter is optional, and allows passing in strings + * that are not nul-terminated. + */ + if (config_len == 0) + WT_RET(__wt_config_init(session, &parser, config)); + else + WT_RET(__wt_config_initn(session, &parser, config, config_len)); + while ((ret = __wt_config_next(&parser, &k, &v)) == 0) { + if (k.type != WT_CONFIG_ITEM_STRING && + k.type != WT_CONFIG_ITEM_ID) + WT_RET_MSG(session, EINVAL, + "Invalid configuration key found: '%.*s'", + (int)k.len, k.str); + + /* Search for a matching entry. */ + for (i = 0; checks[i].name != NULL; i++) + if (WT_STRING_MATCH(checks[i].name, k.str, k.len)) + break; + if (checks[i].name == NULL) + WT_RET_MSG(session, EINVAL, + "unknown configuration key: '%.*s'", + (int)k.len, k.str); + + if (strcmp(checks[i].type, "boolean") == 0) { + badtype = (v.type != WT_CONFIG_ITEM_BOOL && + (v.type != WT_CONFIG_ITEM_NUM || + (v.val != 0 && v.val != 1))); + } else if (strcmp(checks[i].type, "category") == 0) { + /* Deal with categories of the form: XXX=(XXX=blah). */ + ret = config_check(session, + checks[i].subconfigs, + k.str + strlen(checks[i].name) + 1, v.len); + if (ret != EINVAL) + badtype = 0; + else + badtype = 1; + } else if (strcmp(checks[i].type, "format") == 0) { + badtype = 0; + } else if (strcmp(checks[i].type, "int") == 0) { + badtype = (v.type != WT_CONFIG_ITEM_NUM); + } else if (strcmp(checks[i].type, "list") == 0) { + badtype = (v.len > 0 && + v.type != WT_CONFIG_ITEM_STRUCT); + } else if (strcmp(checks[i].type, "string") == 0) { + badtype = 0; + } else + WT_RET_MSG(session, EINVAL, + "unknown configuration type: '%s'", + checks[i].type); + + if (badtype) + WT_RET_MSG(session, EINVAL, + "Invalid value for key '%.*s': expected a %s", + (int)k.len, k.str, checks[i].type); + + if (checks[i].checks == NULL) + continue; + + /* Setup an iterator for the check string. */ + WT_RET(__wt_config_init(session, &cparser, checks[i].checks)); + while ((ret = __wt_config_next(&cparser, &ck, &cv)) == 0) { + if (WT_STRING_MATCH("min", ck.str, ck.len)) { + if (v.val < cv.val) + WT_RET_MSG(session, EINVAL, + "Value too small for key '%.*s' " + "the minimum is %.*s", + (int)k.len, k.str, + (int)cv.len, cv.str); + } else if (WT_STRING_MATCH("max", ck.str, ck.len)) { + if (v.val > cv.val) + WT_RET_MSG(session, EINVAL, + "Value too large for key '%.*s' " + "the maximum is %.*s", + (int)k.len, k.str, + (int)cv.len, cv.str); + } else if (WT_STRING_MATCH("choices", ck.str, ck.len)) { + if (v.len == 0) + WT_RET_MSG(session, EINVAL, + "Key '%.*s' requires a value", + (int)k.len, k.str); + if (v.type == WT_CONFIG_ITEM_STRUCT) { + /* + * Handle the 'verbose' case of a list + * containing restricted choices. + */ + WT_RET(__wt_config_subinit(session, + &sparser, &v)); + found = 1; + while (found && + (ret = __wt_config_next(&sparser, + &v, &dummy)) == 0) { + ret = __wt_config_subgetraw( + session, &cv, &v, &dummy); + found = (ret == 0); + } + } else { + ret = __wt_config_subgetraw(session, + &cv, &v, &dummy); + found = (ret == 0); + } + + if (ret != 0 && ret != WT_NOTFOUND) + return (ret); + if (!found) + WT_RET_MSG(session, EINVAL, + "Value '%.*s' not a " + "permitted choice for key '%.*s'", + (int)v.len, v.str, + (int)k.len, k.str); + } else + WT_RET_MSG(session, EINVAL, + "unexpected configuration description " + "keyword %.*s", (int)ck.len, ck.str); + } + } + + if (ret == WT_NOTFOUND) + ret = 0; + + return (ret); +} |