/* 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 .
*
* 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
#include "cfg.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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)));
}