diff options
Diffstat (limited to 'lib/cfg.c')
-rw-r--r-- | lib/cfg.c | 1182 |
1 files changed, 1182 insertions, 0 deletions
diff --git a/lib/cfg.c b/lib/cfg.c new file mode 100644 index 000000000..b4d1d591a --- /dev/null +++ b/lib/cfg.c @@ -0,0 +1,1182 @@ +/* Copyright (c) 2008, 2009 Nicira Networks + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, Nicira Networks gives permission + * to link the code of its release of vswitchd with the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the same + * license as the "OpenSSL" library), and distribute the linked + * executables. You must obey the GNU General Public License in all + * respects for all of the code used other than "OpenSSL". If you modify + * this file, you may extend this exception to your version of the file, + * but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. + */ + +#include <config.h> +#include "cfg.h" +#include <arpa/inet.h> +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <inttypes.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include "coverage.h" +#include "dynamic-string.h" +#include "ofpbuf.h" +#include "packets.h" +#include "svec.h" +#include "timeval.h" +#include "util.h" + +#define THIS_MODULE VLM_cfg +#include "vlog.h" + +/* XXX This file really needs a unit test! For a while, cfg_get_string(0, + * "bridge.a.controller") would return the value of + * "bridge.a.controller.in-band", if it existed, and I'm really not certain + * that the fix didn't break other things. */ + +/* Configuration file name. */ +static char *cfg_name; + +/* Put the temporary file in the same directory as cfg_name, so that + * they are guaranteed to be in the same file system and therefore we can + * rename() tmp_name over cfg_name. */ +static char *tmp_name; + +/* Lock information. */ +static char *lock_name; +static int lock_fd = -1; + +/* Flag to indicate whether local modifications have been made. */ +static bool dirty; + +static uint8_t cfg_cookie[CFG_COOKIE_LEN]; + +/* Current configuration. Maintained in sorted order. */ +static struct svec cfg = SVEC_EMPTY_INITIALIZER; + +static bool has_double_dot(const char *key, size_t len); +static bool is_valid_key(const char *key, size_t len, + const char *file_name, int line_number, + const char *id); +static char *parse_section(const char *file_name, int line_number, + const char *); +static void parse_setting(const char *file_name, int line_number, + const char *section, const char *); +static int compare_key(const char *a, const char *b); +static char **find_key_le(const char *key); +static char **find_key_ge(const char *key); +static char *find_key(const char *); +static bool parse_mac(const char *, uint8_t mac[6]); +static bool parse_dpid(const char *, uint64_t *); +static bool is_key(const char *); +static bool is_int(const char *); +static bool is_bool(const char *); +static const char *extract_value(const char *key); +static const char *get_nth_value(int idx, const char *key); +static bool is_type(const char *s, enum cfg_flags); + +#define CC_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define CC_DIGIT "0123456789" +#define CC_ALNUM CC_ALPHA CC_DIGIT +#define CC_SPACE " \t\r\n\v" + +#define CC_FILE_NAME CC_ALNUM "._-" +#define CC_KEY CC_ALNUM "._-@$:+" + +/* Sets 'file_name' as the configuration file read by cfg_read(). Returns 0 on + * success, otherwise a positive errno value if 'file_name' cannot be opened. + * + * This function does not actually read the named file or directory. Use + * cfg_read() to (re)read all the configuration files. */ +int +cfg_set_file(const char *file_name) +{ + const char *slash; + int fd; + + if (cfg_name) { + assert(lock_fd < 0); + free(cfg_name); + free(lock_name); + free(tmp_name); + cfg_name = lock_name = tmp_name = NULL; + } + + /* Make sure that we can open this file for reading. */ + fd = open(file_name, O_RDONLY); + if (fd < 0) { + return errno; + } + close(fd); + + cfg_name = xstrdup(file_name); + + /* Put the temporary file in the same directory as cfg_name, so that they + * are guaranteed to be in the same file system, to guarantee that + * rename(tmp_name, cfg_name) will work. */ + tmp_name = xasprintf("%s.~tmp~", file_name); + + /* Put the lock file in the same directory as cfg_name, but prefixed by + * a dot so as not to garner administrator interest. */ + slash = strrchr(file_name, '/'); + if (slash) { + lock_name = xasprintf("%.*s/.%s.~lock~", + slash - file_name, file_name, slash + 1); + } else { + lock_name = xasprintf(".%s.~lock~", file_name); + } + + VLOG_INFO("using \"%s\" as configuration file, \"%s\" as lock file", + file_name, lock_name); + return 0; +} + +static int +update_cookie(void) +{ + int i; + SHA1Context context; + + if (SHA1Reset(&context) != shaSuccess) { + return -1; + } + for (i = 0; i < cfg.n; i++) { + if (SHA1Input(&context, (uint8_t *)cfg.names[i], + strlen(cfg.names[i])) != shaSuccess) { + return -1; + } + SHA1Input(&context, (uint8_t *)"\n", 1); + } + if (SHA1Result(&context, cfg_cookie) != shaSuccess) { + return -1; + } + + return 0; +} + +/* Reads all of the configuration files or directories that have been added + * with cfg_add_file(), merges their content. Any previous configuration is + * replaced. Returns 0 if successful, otherwise a positive errno value. */ +int +cfg_read(void) +{ + struct svec old_cfg; + struct ds ds; + FILE *file; + char *section; + int line_number; + + + if (!cfg_name) { + return ENODEV; + } + + /* Save old configuration data and clear the active configuration. */ + svec_init(&old_cfg); + svec_swap(&old_cfg, &cfg); + + /* Read new configuration. */ + VLOG_DBG("reading configuration from %s", cfg_name); + + file = fopen(cfg_name, "r"); + if (!file) { + VLOG_ERR("failed to open \"%s\": %s", cfg_name, strerror(errno)); + return errno; + } + + ds_init(&ds); + section = NULL; + line_number = 0; + while (!ds_get_line(&ds, file)) { + const char *s = ds_cstr(&ds); + size_t indent = strspn(s, CC_SPACE); + + line_number++; + s += indent; + if (*s == '#' || *s == '\0') { + /* Ignore comments and lines that contain only white space. */ + } else if (*s == '[') { + if (!indent) { + free(section); + section = parse_section(cfg_name, line_number, s); + } else { + VLOG_ERR("%s:%d: ignoring indented section header", + cfg_name, line_number); + } + } else if (indent && !section) { + VLOG_ERR("%s:%d: ignoring indented line outside any section", + cfg_name, line_number); + } else { + if (!indent) { + free(section); + section = NULL; + } + parse_setting(cfg_name, line_number, section, s); + } + } + ds_destroy(&ds); + free(section); + + svec_sort(&cfg); + svec_terminate(&cfg); + update_cookie(); + + fclose(file); + + if (VLOG_IS_DBG_ENABLED()) { + struct svec removed, added; + size_t i; + + svec_diff(&old_cfg, &cfg, &removed, NULL, &added); + if (removed.n || added.n) { + VLOG_DBG("configuration changes:"); + for (i = 0; i < removed.n; i++) { + VLOG_DBG("-%s", removed.names[i]); + } + for (i = 0; i < added.n; i++) { + VLOG_DBG("+%s", added.names[i]); + } + } else { + VLOG_DBG("configuration unchanged"); + } + svec_destroy(&added); + svec_destroy(&removed); + } + svec_destroy(&old_cfg); + + dirty = false; + + return 0; +} + +/* Fills 'svec' with the entire configuration file. */ +void +cfg_get_all(struct svec *svec) +{ + svec_clear(svec); + svec_append(svec, &cfg); +} + +int +cfg_get_cookie(uint8_t *cookie) +{ + if (dirty) { + update_cookie(); + } + + memcpy(cookie, cfg_cookie, sizeof(cfg_cookie)); + return 0; +} + +void +cfg_unlock(void) +{ + if (lock_fd != -1) { + COVERAGE_INC(cfg_unlock); + close(lock_fd); + lock_fd = -1; + } +} + +static int +open_lockfile(const char *name) +{ + for (;;) { + /* Try to open an existing lock file. */ + int fd = open(name, O_RDWR); + if (fd >= 0) { + return fd; + } else if (errno != ENOENT) { + VLOG_WARN("%s: failed to open lock file: %s", + name, strerror(errno)); + return -errno; + } + + /* Try to create a new lock file. */ + VLOG_INFO("%s: lock file does not exist, creating", name); + fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + return fd; + } else if (errno != EEXIST) { + VLOG_WARN("%s: failed to create lock file: %s", + name, strerror(errno)); + return -errno; + } + + /* Someone else created the lock file. Try again. */ + } +} + +static int +try_lock(int fd, bool block) +{ + struct flock l; + memset(&l, 0, sizeof l); + l.l_type = F_WRLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + return fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0; +} + +/* Locks the configuration file against modification by other processes and + * re-reads it from disk. + * + * The 'timeout' specifies the maximum number of milliseconds to wait for the + * config file to become free. Use 0 to avoid waiting or INT_MAX to wait + * forever. + * + * Returns 0 on success, otherwise a positive errno value. */ +int +cfg_lock(uint8_t *cookie, int timeout) +{ + long long int start = time_msec(); + long long int elapsed = 0; + int fd; + uint8_t curr_cookie[CFG_COOKIE_LEN]; + + assert(lock_fd < 0); + COVERAGE_INC(cfg_lock); + for (;;) { + int error; + + /* Open lock file. */ + fd = open_lockfile(lock_name); + if (fd < 0) { + return -fd; + } + + /* Try to lock it. This will block (if 'timeout' > 0). */ + error = try_lock(fd, timeout > 0); + time_refresh(); + elapsed = time_msec() - start; + if (!error) { + /* Success! */ + break; + } + + /* Lock failed. Close the lock file and reopen it on the next + * iteration, just in case someone deletes it underneath us (even + * though that should not happen). */ + close(fd); + if (error != EINTR) { + /* Hard error, give up. */ + COVERAGE_INC(cfg_lock_error); + VLOG_WARN("%s: failed to lock file " + "(after %lld ms, with %d-ms timeout): %s", + lock_name, elapsed, timeout, strerror(error)); + return error; + } + + /* Probably, the periodic timer set up by time_init() woke up us. Just + * check whether it's time to give up. */ + if (timeout != INT_MAX && elapsed >= timeout) { + COVERAGE_INC(cfg_lock_timeout); + VLOG_WARN("%s: giving up on lock file after %lld ms", + lock_name, elapsed); + return ETIMEDOUT; + } + COVERAGE_INC(cfg_lock_retry); + } + if (elapsed) { + VLOG_WARN("%s: waited %lld ms for lock file", lock_name, elapsed); + } + lock_fd = fd; + + cfg_read(); + + if (cookie) { + cfg_get_cookie(curr_cookie); + + if (memcmp(curr_cookie, cookie, sizeof *curr_cookie)) { + /* Configuration has changed, so reject. */ + cfg_unlock(); + return EINVAL; + } + } + + return 0; +} + +static int +do_write_config(const void *data, size_t len) +{ + FILE *file; + int error; + + file = fopen(tmp_name, "w"); + if (file == NULL) { + VLOG_WARN("could not open %s for writing: %s", + tmp_name, strerror(errno)); + return errno; + } + + fwrite(data, 1, len, file); + + /* This is essentially equivalent to: + * error = ferror(file) || fflush(file) || fclose(file); + * but it doesn't short-circuit, so that it always closes 'file'. */ + error = ferror(file); + error = fflush(file) || error; + error = fclose(file) || error; + if (error) { + VLOG_WARN("problem writing to %s: %s", tmp_name, strerror(errno)); + return errno; + } + + if (rename(tmp_name, cfg_name) < 0) { + VLOG_WARN("could not rename %s to %s: %s", + tmp_name, cfg_name, strerror(errno)); + return errno; + } + + dirty = false; + + return 0; +} + +/* Write the current configuration into the configuration file. Returns 0 if + * successful, otherwise a negative errno value. */ +int +cfg_write(void) +{ + char *content; + int retval; + + svec_sort(&cfg); + content = (cfg.n + ? svec_join(&cfg, "\n", "\n") + : xstrdup("# This file intentionally left blank.\n")); + retval = do_write_config(content, strlen(content)); + free(content); + + return retval; +} + +int +cfg_write_data(uint8_t *data, size_t len) +{ + int retval = do_write_config(data, len); + if (!retval) { + cfg_read(); + } + return retval; +} + +/* Returns true if the configuration has changed since the last time it was + * read or written. */ +bool +cfg_is_dirty(void) +{ + return dirty; +} + +void +cfg_buf_put(struct ofpbuf *buffer) +{ + int i; + + for (i = 0; i < cfg.n; i++) { + ofpbuf_put(buffer, cfg.names[i], strlen(cfg.names[i])); + ofpbuf_put(buffer, "\n", 1); + } +} + +/* Formats the printf()-style format string in the parameter 'format', which + * must be the function's last parameter, into string variable 'dst'. The + * function is responsible for freeing 'dst'. */ +#define FORMAT_KEY(FORMAT, DST) \ + do { \ + va_list args__; \ + va_start(args__, FORMAT); \ + (DST) = xvasprintf(FORMAT, args__); \ + va_end(args__); \ + } while (0) + +/* Returns true if the configuration includes a key named 'key'. */ +bool +cfg_has(const char *key_, ...) +{ + char *key; + bool retval; + + FORMAT_KEY(key_, key); + retval = find_key(key) != NULL; + free(key); + return retval; +} + +bool +cfg_is_valid(enum cfg_flags flags, const char *key_, ...) +{ + char *key, **first, **last, **p; + size_t n; + bool retval; + + FORMAT_KEY(key_, key); + first = find_key_le(key); + last = find_key_ge(key); + n = last - first; + retval = ((!(flags & CFG_REQUIRED) || n) + && (!(flags & CFG_MULTIPLE) || n <= 1)); + for (p = first; retval && p < last; p++) { + retval = is_type(strchr(*p, '=') + 1, flags); + } + free(key); + return retval; +} + +/* Returns true if the configuration includes at least one key whose name + * begins with 'section' followed by a dot. */ +bool +cfg_has_section(const char *section_, ...) +{ + struct ds section; + bool retval = false; + va_list args; + char **p; + + ds_init(§ion); + va_start(args, section_); + ds_put_format_valist(§ion, section_, args); + ds_put_char(§ion, '.'); + va_end(args); + + for (p = cfg.names; *p; p++) { /* XXX this is inefficient */ + if (!strncmp(section.string, *p, section.length)) { + retval = true; + break; + } + } + + ds_destroy(§ion); + return retval; +} + +/* Returns the number of values for the given 'key'. The return value is 0 if + * no values exist for 'key'. */ +int +cfg_count(const char *key_, ...) +{ + char *key; + int retval; + + FORMAT_KEY(key_, key); + retval = find_key_ge(key) - find_key_le(key); + free(key); + return retval; +} + +/* Fills 'svec' with all of the immediate subsections of 'section'. For + * example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c, + * and bridge.c.x.y.z exist, then 'svec' would be initialized to a, b, and + * c. The caller must first initialize 'svec'. */ +void +cfg_get_subsections(struct svec *svec, const char *section_, ...) +{ + struct ds section; + va_list args; + char **p; + + ds_init(§ion); + va_start(args, section_); + ds_put_format_valist(§ion, section_, args); + ds_put_char(§ion, '.'); + va_end(args); + + svec_clear(svec); + for (p = cfg.names; *p; p++) { /* XXX this is inefficient */ + if (!strncmp(section.string, *p, section.length)) { + const char *ss = *p + section.length; + size_t ss_len = strcspn(ss, ".="); + svec_add_nocopy(svec, xmemdup0(ss, ss_len)); + } + } + svec_unique(svec); + ds_destroy(§ion); +} + +void +cfg_add_entry(const char *entry_, ...) +{ + char *entry; + + FORMAT_KEY(entry_, entry); + svec_add_nocopy(&cfg, entry); + svec_sort(&cfg); + svec_terminate(&cfg); + dirty = true; +} + +void +cfg_del_entry(const char *entry_, ...) +{ + char *entry; + + FORMAT_KEY(entry_, entry); + svec_del(&cfg, entry); + svec_terminate(&cfg); + free(entry); + dirty = true; +} + +void +cfg_del_section(const char *section_, ...) +{ + struct ds section; + va_list args; + char **p; + + ds_init(§ion); + va_start(args, section_); + ds_put_format_valist(§ion, section_, args); + ds_put_char(§ion, '.'); + va_end(args); + + for (p = cfg.names; *p; p++) { + if (!strncmp(section.string, *p, section.length)) { + free(*p); + *p = NULL; + } + } + svec_compact(&cfg); + svec_terminate(&cfg); + + ds_destroy(§ion); + dirty = true; +} + +void +cfg_del_match(const char *pattern_, ...) +{ + bool matched = false; + char *pattern; + char **p; + + FORMAT_KEY(pattern_, pattern); + + for (p = cfg.names; *p; p++) { + if (!fnmatch(pattern, *p, 0)) { + free(*p); + *p = NULL; + matched = true; + } + } + if (matched) { + svec_compact(&cfg); + svec_terminate(&cfg); + dirty = true; + } + + free(pattern); +} + +/* Fills 'svec' with all of the key-value pairs that have sections that + * begin with 'section'. The caller must first initialize 'svec'. */ +void +cfg_get_section(struct svec *svec, const char *section_, ...) +{ + struct ds section; + va_list args; + char **p; + + ds_init(§ion); + va_start(args, section_); + ds_put_format_valist(§ion, section_, args); + ds_put_char(§ion, '.'); + va_end(args); + + for (p = cfg.names; *p; p++) { /* XXX this is inefficient */ + if (!strncmp(section.string, *p, section.length)) { + svec_add(svec, *p); + } + } + ds_destroy(§ion); +} + +/* Returns the value numbered 'idx' of 'key'. Returns a null pointer if 'idx' + * is greater than or equal to cfg_count(key). The caller must not modify or + * free the returned string or retain its value beyond the next call to + * cfg_read(). */ +const char * +cfg_get_string(int idx, const char *key_, ...) +{ + const char *retval; + char *key; + + FORMAT_KEY(key_, key); + retval = get_nth_value(idx, key); + free(key); + return retval; +} + +/* Returns the value numbered 'idx' of 'key'. Returns a null pointer if 'idx' + * is greater than or equal to cfg_count(key) or if the value 'idx' of 'key' is + * not a valid key. The caller must not modify or free the returned string or + * retain its value beyond the next call to cfg_read(). */ +const char * +cfg_get_key(int idx, const char *key_, ...) +{ + const char *value, *retval; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + retval = value && is_key(value) ? value : NULL; + free(key); + return retval; +} + +/* Returns the value numbered 'idx' of 'key', converted to an integer. Returns + * 0 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx' + * of 'key' is not a valid integer. */ +int +cfg_get_int(int idx, const char *key_, ...) +{ + const char *value; + int retval; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + retval = value && is_int(value) ? atoi(value) : 0; + free(key); + return retval; +} + +/* Returns the value numbered 'idx' of 'key', converted to a boolean value. + * Returns false if 'idx' is greater than or equal to cfg_count(key) or if the + * value 'idx' of 'key' is not a valid boolean. */ +bool +cfg_get_bool(int idx, const char *key_, ...) +{ + const char *value; + bool retval; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + retval = value && is_bool(value) ? !strcmp(value, "true") : false; + free(key); + return retval; +} + +/* Returns the value numbered 'idx' of 'key', converted to an IP address in + * network byte order. Returns 0 if 'idx' is greater than or equal to + * cfg_count(key) or if the value 'idx' of 'key' is not a valid IP address (as + * determined by inet_aton()). */ +uint32_t +cfg_get_ip(int idx, const char *key_, ...) +{ + struct in_addr addr; + const char *value; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + if (!value || !inet_aton(value, &addr)) { + addr.s_addr = htonl(0); + } + free(key); + return addr.s_addr; +} + +/* Returns the value numbered 'idx' of 'key', converted to an MAC address in + * host byte order. Returns 0 if 'idx' is greater than or equal to + * cfg_count(key) or if the value 'idx' of 'key' is not a valid MAC address in + * the format "##:##:##:##:##:##". */ +uint64_t +cfg_get_mac(int idx, const char *key_, ...) +{ + uint8_t mac[ETH_ADDR_LEN]; + const char *value; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + if (!value || !parse_mac(value, mac)) { + memset(mac, 0, sizeof mac); + } + free(key); + return eth_addr_to_uint64(mac); +} + +/* Returns the value numbered 'idx' of 'key', parsed as an datapath ID. + * Returns 0 if 'idx' is greater than or equal to cfg_count(key) or if the + * value 'idx' of 'key' is not a valid datapath ID consisting of exactly 12 + * hexadecimal digits. */ +uint64_t +cfg_get_dpid(int idx, const char *key_, ...) +{ + uint64_t dpid; + const char *value; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + if (!value || !parse_dpid(value, &dpid)) { + dpid = 0; + } + free(key); + return dpid; +} + +/* Returns the value numbered 'idx' of 'key', converted to an integer. Returns + * -1 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx' + * of 'key' is not a valid integer between 0 and 4095. */ +int +cfg_get_vlan(int idx, const char *key_, ...) +{ + const char *value; + int retval; + char *key; + + FORMAT_KEY(key_, key); + value = get_nth_value(idx, key); + if (value && is_int(value)) { + retval = atoi(value); + if (retval < 0 || retval > 4095) { + retval = -1; + } + } else { + retval = -1; + } + free(key); + return retval; +} + +/* Fills 'svec' with all of the string values of 'key'. The caller must + * first initialize 'svec'. */ +void +cfg_get_all_strings(struct svec *svec, const char *key_, ...) +{ + char **p, **q; + char *key; + + FORMAT_KEY(key_, key); + svec_clear(svec); + for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) { + svec_add(svec, extract_value(*p)); + } + free(key); +} + +/* Fills 'svec' with all of the values of 'key' that are valid keys. + * Values of 'key' that are not valid keys are omitted. The caller + * must first initialize 'svec'. */ +void +cfg_get_all_keys(struct svec *svec, const char *key_, ...) +{ + char **p, **q; + char *key; + + FORMAT_KEY(key_, key); + svec_clear(svec); + for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) { + const char *value = extract_value(*p); + if (is_key(value)) { + svec_add(svec, value); + } + } + free(key); +} + +static bool +has_double_dot(const char *key, size_t len) +{ + if (len >= 2) { + size_t i; + + for (i = 0; i < len - 1; i++) { + if (key[i] == '.' && key[i + 1] == '.') { + return true; + } + } + } + return false; +} + +static bool +is_valid_key(const char *key, size_t len, + const char *file_name, int line_number, const char *id) +{ + if (!len) { + VLOG_ERR("%s:%d: missing %s name", file_name, line_number, id); + return false; + } else if (key[0] == '.') { + VLOG_ERR("%s:%d: %s name \"%.*s\" begins with invalid character '.'", + file_name, line_number, id, (int) len, key); + return false; + } else if (key[len - 1] == '.') { + VLOG_ERR("%s:%d: %s name \"%.*s\" ends with invalid character '.'", + file_name, line_number, id, (int) len, key); + return false; + } else if (has_double_dot(key, len)) { + VLOG_ERR("%s:%d: %s name \"%.*s\" contains '..', which is not allowed", + file_name, line_number, id, (int) len, key); + return false; + } else { + return true; + } +} + +static char * +parse_section(const char *file_name, int line_number, const char *s) +{ + struct ds section; + size_t len; + + ds_init(§ion); + + /* Skip [ and any white space. */ + s++; + s += strspn(s, CC_SPACE); + + /* Obtain the section name. */ + len = strspn(s, CC_KEY); + if (!is_valid_key(s, len, file_name, line_number, "section")) { + goto error; + } + ds_put_buffer(§ion, s, len); + s += len; + + /* Obtain the subsection name, if any. */ + s += strspn(s, CC_SPACE); + if (*s == '"') { + s++; + len = strspn(s, CC_KEY); + if (!is_valid_key(s, len, file_name, line_number, "subsection")) { + goto error; + } + ds_put_char(§ion, '.'); + ds_put_buffer(§ion, s, len); + s += len; + if (*s != '"') { + VLOG_ERR("%s:%d: missing '\"' following subsection name", + file_name, line_number); + goto error; + } + s++; + s += strspn(s, CC_SPACE); + } + + /* Check for ]. */ + if (*s != ']') { + VLOG_ERR("%s:%d: missing ']' following section name", + file_name, line_number); + goto error; + } + s++; + s += strspn(s, CC_SPACE); + if (*s != '\0') { + VLOG_ERR("%s:%d: trailing garbage following ']'", + file_name, line_number); + goto error; + } + + return ds_cstr(§ion); + +error: + ds_destroy(§ion); + return NULL; +} + +static void +parse_setting(const char *file_name, int line_number, const char *section, + const char *s) +{ + struct ds key = DS_EMPTY_INITIALIZER; + struct ds value = DS_EMPTY_INITIALIZER; + size_t len; + + if (section) { + ds_put_format(&key, "%s.", section); + } + + /* Obtain the key. */ + len = strspn(s, CC_KEY); + if (!len) { + VLOG_ERR("%s:%d: missing key name", file_name, line_number); + goto done; + } + if (!is_valid_key(s, len, file_name, line_number, "key")) { + goto done; + } + ds_put_buffer(&key, s, len); + s += len; + + /* Skip the '='. */ + s += strspn(s, CC_SPACE); + if (*s != '=') { + VLOG_ERR("%s:%d: missing '=' following key", file_name, line_number); + goto done; + } + s++; + s += strspn(s, CC_SPACE); + + /* Obtain the value. */ + ds_put_cstr(&value, s); + while (value.length > 0 && strchr(CC_SPACE, ds_last(&value))) { + value.length--; + } + + /* Add the setting. */ + svec_add_nocopy(&cfg, xasprintf("%s=%s", ds_cstr(&key), ds_cstr(&value))); + +done: + ds_destroy(&key); + ds_destroy(&value); +} + +static int +compare_key(const char *a, const char *b) +{ + for (;;) { + int ac = *a == '\0' || *a == '=' ? INT_MAX : *a; + int bc = *b == '\0' || *b == '=' ? INT_MAX : *b; + if (ac != bc) { + return ac < bc ? -1 : 1; + } else if (ac == INT_MAX) { + return 0; + } + a++; + b++; + } +} + +/* Returns the address of the greatest configuration string with a key less + * than or equal to 'key'. Returns the address of the null terminator if all + * configuration strings are greater than 'key'. */ +static char ** +find_key_le(const char *key) +{ + int low = 0; + int len = cfg.n; + while (len > 0) { + int half = len >> 1; + int middle = low + half; + if (compare_key(cfg.names[middle], key) < 0) { + low = middle + 1; + len -= half + 1; + } else { + len = half; + } + } + return &cfg.names[low]; +} + +/* Returns the address of the least configuration string with a key greater + * than or equal to 'key'. Returns the address of the null terminator if all + * configuration strings are less than 'key'. */ +static char ** +find_key_ge(const char *key) +{ + int low = 0; + int len = cfg.n; + while (len > 0) { + int half = len >> 1; + int middle = low + half; + if (compare_key(cfg.names[middle], key) > 0) { + len = half; + } else { + low = middle + 1; + len -= half + 1; + } + } + return &cfg.names[low]; +} + +static char * +find_key(const char *key) +{ + char **p = find_key_le(key); + return p < &cfg.names[cfg.n] && !compare_key(*p, key) ? *p : NULL; +} + +static bool +parse_mac(const char *s, uint8_t mac[6]) +{ + return sscanf(s, "%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8, + &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6; +} + +static bool +parse_dpid(const char *s, uint64_t *dpid) +{ + if (strlen(s) == 12 && strspn(s, "0123456789abcdefABCDEF") == 12) { + *dpid = strtoll(s, NULL, 16); + return true; + } else { + return false; + } +} + +static bool +is_key(const char *s) +{ + /* XXX needs to check the same things as is_valid_key() too. */ + return *s && s[strspn(s, CC_KEY)] == '\0'; +} + +static bool +is_int(const char *s) +{ + return *s && s[strspn(s, CC_DIGIT)] == '\0'; +} + +static bool +is_bool(const char *s) +{ + return !strcmp(s, "true") || !strcmp(s, "false"); +} + +static const char * +extract_value(const char *key) +{ + const char *p = strchr(key, '='); + return p ? p + 1 : NULL; +} + +static const char * +get_nth_value(int idx, const char *key) +{ + char **p = find_key_le(key); + char **q = find_key_ge(key); + return idx < q - p ? extract_value(p[idx]) : NULL; +} + +static bool +is_type(const char *s, enum cfg_flags flags) +{ + uint8_t mac[ETH_ADDR_LEN]; + struct in_addr addr; + uint64_t dpid; + + return (flags & CFG_STRING + || (flags & CFG_KEY && is_key(s)) + || (flags & CFG_INT && is_int(s)) + || (flags & CFG_BOOL && is_bool(s)) + || (flags & CFG_IP && inet_aton(s, &addr)) + || (flags & CFG_MAC && parse_mac(s, mac)) + || (flags & CFG_DPID && parse_dpid(s, &dpid))); +} |