/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. * * This file is part of LVM2. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License v.2.1. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libdm/misc/dmlib.h" #include <sys/stat.h> #include <sys/mman.h> #include <unistd.h> #include <fcntl.h> #include <ctype.h> #include <stdarg.h> #define SECTION_B_CHAR '{' #define SECTION_E_CHAR '}' enum { TOK_INT, TOK_FLOAT, TOK_STRING, /* Single quotes */ TOK_STRING_ESCAPED, /* Double quotes */ TOK_STRING_BARE, /* No quotes */ TOK_EQ, TOK_SECTION_B, TOK_SECTION_E, TOK_ARRAY_B, TOK_ARRAY_E, TOK_IDENTIFIER, TOK_COMMA, TOK_EOF }; struct parser { const char *fb, *fe; /* file limits */ int t; /* token limits and type */ const char *tb, *te; int line; /* line number we are on */ struct dm_pool *mem; int no_dup_node_check; /* whether to disable dup node checking */ const char *key; /* last obtained key */ unsigned ignored_creation_time; }; struct config_output { struct dm_pool *mem; dm_putline_fn putline; const struct dm_config_node_out_spec *spec; void *baton; }; static void _get_token(struct parser *p, int tok_prev); static void _eat_space(struct parser *p); static struct dm_config_node *_file(struct parser *p); static struct dm_config_node *_section(struct parser *p, struct dm_config_node *parent); static struct dm_config_value *_value(struct parser *p); static struct dm_config_value *_type(struct parser *p); static int _match_aux(struct parser *p, int t); static struct dm_config_value *_create_value(struct dm_pool *mem); static struct dm_config_node *_create_node(struct dm_pool *mem); static char *_dup_tok(struct parser *p); static char *_dup_token(struct dm_pool *mem, const char *b, const char *e); static const int _sep = '/'; #define MAX_INDENT 32 #define match(t) do {\ if (!_match_aux(p, (t))) {\ log_error("Parse error at byte %" PRIptrdiff_t " (line %d): unexpected token", \ p->tb - p->fb + 1, p->line); \ return 0;\ } \ } while(0) static int _tok_match(const char *str, const char *b, const char *e) { while (*str && (b != e)) { if (*str++ != *b++) return 0; } return !(*str || (b != e)); } struct dm_config_tree *dm_config_create(void) { struct dm_config_tree *cft; struct dm_pool *mem = dm_pool_create("config", 10 * 1024); if (!mem) { log_error("Failed to allocate config pool."); return 0; } if (!(cft = dm_pool_zalloc(mem, sizeof(*cft)))) { log_error("Failed to allocate config tree."); dm_pool_destroy(mem); return 0; } cft->mem = mem; return cft; } void dm_config_set_custom(struct dm_config_tree *cft, void *custom) { cft->custom = custom; } void *dm_config_get_custom(struct dm_config_tree *cft) { return cft->custom; } void dm_config_destroy(struct dm_config_tree *cft) { dm_pool_destroy(cft->mem); } /* * If there's a cascaded dm_config_tree, remove and return it, otherwise * return NULL. */ struct dm_config_tree *dm_config_remove_cascaded_tree(struct dm_config_tree *cft) { struct dm_config_tree *second_cft; if (!cft) return NULL; second_cft = cft->cascade; cft->cascade = NULL; return second_cft; } /* * When searching, first_cft is checked before second_cft. */ struct dm_config_tree *dm_config_insert_cascaded_tree(struct dm_config_tree *first_cft, struct dm_config_tree *second_cft) { first_cft->cascade = second_cft; return first_cft; } static struct dm_config_node *_config_reverse(struct dm_config_node *head) { struct dm_config_node *left = head, *middle = NULL, *right = NULL; while (left) { right = middle; middle = left; left = left->sib; middle->sib = right; middle->child = _config_reverse(middle->child); } return middle; } static int _do_dm_config_parse(struct dm_config_tree *cft, const char *start, const char *end, int no_dup_node_check) { /* TODO? if (start == end) return 1; */ struct parser *p; if (!(p = dm_pool_zalloc(cft->mem, sizeof(*p)))) return_0; p->mem = cft->mem; p->fb = start; p->fe = end; p->tb = p->te = p->fb; p->line = 1; p->no_dup_node_check = no_dup_node_check; _get_token(p, TOK_SECTION_E); if (!(cft->root = _file(p))) return_0; cft->root = _config_reverse(cft->root); return 1; } int dm_config_parse(struct dm_config_tree *cft, const char *start, const char *end) { return _do_dm_config_parse(cft, start, end, 0); } int dm_config_parse_without_dup_node_check(struct dm_config_tree *cft, const char *start, const char *end) { return _do_dm_config_parse(cft, start, end, 1); } struct dm_config_tree *dm_config_from_string(const char *config_settings) { struct dm_config_tree *cft; if (!(cft = dm_config_create())) return_NULL; if (!dm_config_parse(cft, config_settings, config_settings + strlen(config_settings))) { dm_config_destroy(cft); return_NULL; } return cft; } static int _line_start(struct config_output *out) { if (!dm_pool_begin_object(out->mem, 128)) { log_error("dm_pool_begin_object failed for config line"); return 0; } return 1; } __attribute__ ((format(printf, 2, 3))) static int _line_append(struct config_output *out, const char *fmt, ...) { char buf[4096]; char *dyn_buf = NULL; va_list ap; int n; /* * We should be fine with the 4096 char buffer 99% of the time, * but if we need to go beyond that, allocate the buffer dynamically. */ va_start(ap, fmt); n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (n < 0) { log_error("vsnprintf failed for config line"); return 0; } if (n > (int) sizeof buf - 1) { /* * Fixed size buffer with sizeof buf is not enough, * so try dynamically allocated buffer now... */ va_start(ap, fmt); n = dm_vasprintf(&dyn_buf, fmt, ap); va_end(ap); if (n < 0) { log_error("dm_vasprintf failed for config line"); return 0; } } if (!dm_pool_grow_object(out->mem, dyn_buf ? : buf, 0)) { log_error("dm_pool_grow_object failed for config line"); dm_free(dyn_buf); return 0; } dm_free(dyn_buf); return 1; } #define line_append(args...) do {if (!_line_append(out, args)) {return_0;}} while (0) static int _line_end(const struct dm_config_node *cn, struct config_output *out) { const char *line; if (!dm_pool_grow_object(out->mem, "\0", 1)) { log_error("dm_pool_grow_object failed for config line"); return 0; } line = dm_pool_end_object(out->mem); if (!out->putline && !out->spec) return 0; if (out->putline) out->putline(line, out->baton); if (out->spec && out->spec->line_fn) out->spec->line_fn(cn, line, out->baton); return 1; } static int _write_value(struct config_output *out, const struct dm_config_value *v) { char *buf; const char *s; switch (v->type) { case DM_CFG_STRING: buf = alloca(dm_escaped_len(v->v.str)); s = (v->format_flags & DM_CONFIG_VALUE_FMT_STRING_NO_QUOTES) ? "" : "\""; line_append("%s%s%s", s, dm_escape_double_quotes(buf, v->v.str), s); break; case DM_CFG_FLOAT: line_append("%f", v->v.f); break; case DM_CFG_INT: if (v->format_flags & DM_CONFIG_VALUE_FMT_INT_OCTAL) line_append("0%" PRIo64, v->v.i); else line_append(FMTd64, v->v.i); break; case DM_CFG_EMPTY_ARRAY: s = (v->format_flags & DM_CONFIG_VALUE_FMT_COMMON_EXTRA_SPACES) ? " " : ""; line_append("[%s]", s); break; default: log_error("_write_value: Unknown value type: %d", v->type); } return 1; } static int _write_config(const struct dm_config_node *n, int only_one, struct config_output *out, int level) { const char *extra_space; int format_array; char space[MAX_INDENT + 1]; int l = (level < MAX_INDENT) ? level : MAX_INDENT; int i; char *escaped_key = NULL; if (!n) return 1; for (i = 0; i < l; i++) space[i] = '\t'; space[i] = '\0'; do { extra_space = (n->v && (n->v->format_flags & DM_CONFIG_VALUE_FMT_COMMON_EXTRA_SPACES)) ? " " : ""; format_array = (n->v && (n->v->format_flags & DM_CONFIG_VALUE_FMT_COMMON_ARRAY)); if (out->spec && out->spec->prefix_fn) out->spec->prefix_fn(n, space, out->baton); if (!_line_start(out)) return_0; if (strchr(n->key, '#') || strchr(n->key, '"') || strchr(n->key, '!')) { escaped_key = alloca(dm_escaped_len(n->key) + 2); *escaped_key = '"'; dm_escape_double_quotes(escaped_key + 1, n->key); strcat(escaped_key, "\""); } line_append("%s%s", space, escaped_key ? escaped_key : n->key); escaped_key = NULL; if (!n->v) { /* it's a sub section */ line_append(" {"); if (!_line_end(n, out)) return_0; if (!_write_config(n->child, 0, out, level + 1)) return_0; if (!_line_start(out)) return_0; line_append("%s}", space); } else { /* it's a value */ const struct dm_config_value *v = n->v; line_append("%s=%s", extra_space, extra_space); if (v->next) { line_append("[%s", extra_space); while (v && v->type != DM_CFG_EMPTY_ARRAY) { if (!_write_value(out, v)) return_0; v = v->next; if (v && v->type != DM_CFG_EMPTY_ARRAY) line_append(",%s", extra_space); } line_append("%s]", extra_space); } else { if (format_array && (v->type != DM_CFG_EMPTY_ARRAY)) line_append("[%s", extra_space); if (!_write_value(out, v)) return_0; if (format_array && (v->type != DM_CFG_EMPTY_ARRAY)) line_append("%s]", extra_space); } } if (!_line_end(n, out)) return_0; if (out->spec && out->spec->suffix_fn) out->spec->suffix_fn(n, space, out->baton); n = n->sib; } while (n && !only_one); /* FIXME: add error checking */ return 1; } static int _write_node(const struct dm_config_node *cn, int only_one, dm_putline_fn putline, const struct dm_config_node_out_spec *out_spec, void *baton) { struct config_output out = { .mem = dm_pool_create("config_output", 1024), .putline = putline, .spec = out_spec, .baton = baton }; if (!out.mem) return_0; if (!_write_config(cn, only_one, &out, 0)) { dm_pool_destroy(out.mem); return_0; } dm_pool_destroy(out.mem); return 1; } int dm_config_write_one_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton) { return _write_node(cn, 1, putline, NULL, baton); } int dm_config_write_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton) { return _write_node(cn, 0, putline, NULL, baton); } int dm_config_write_one_node_out(const struct dm_config_node *cn, const struct dm_config_node_out_spec *out_spec, void *baton) { return _write_node(cn, 1, NULL, out_spec, baton); } int dm_config_write_node_out(const struct dm_config_node *cn, const struct dm_config_node_out_spec *out_spec, void *baton) { return _write_node(cn, 0, NULL, out_spec, baton); } /* * parser */ static char *_dup_string_tok(struct parser *p) { char *str; p->tb++, p->te--; /* strip "'s */ if (p->te < p->tb) { log_error("Parse error at byte %" PRIptrdiff_t " (line %d): " "expected a string token.", p->tb - p->fb + 1, p->line); return NULL; } if (!(str = _dup_tok(p))) return_NULL; p->te++; return str; } static struct dm_config_node *_file(struct parser *p) { struct dm_config_node root = { 0 }; root.key = "<root>"; while (p->t != TOK_EOF) if (!_section(p, &root)) return_NULL; return root.child; } static struct dm_config_node *_make_node(struct dm_pool *mem, const char *key_b, const char *key_e, struct dm_config_node *parent) { struct dm_config_node *n; if (!(n = _create_node(mem))) return_NULL; n->key = _dup_token(mem, key_b, key_e); if (parent) { n->parent = parent; n->sib = parent->child; parent->child = n; } return n; } /* when mem is not NULL, we create the path if it doesn't exist yet */ static struct dm_config_node *_find_or_make_node(struct dm_pool *mem, struct dm_config_node *parent, const char *path, int no_dup_node_check) { const char *e; struct dm_config_node *cn = parent ? parent->child : NULL; struct dm_config_node *cn_found = NULL; while (cn || mem) { /* trim any leading slashes */ while (*path && (*path == _sep)) path++; /* find the end of this segment */ for (e = path; *e && (*e != _sep); e++) ; /* hunt for the node */ cn_found = NULL; if (!no_dup_node_check) { while (cn) { if (_tok_match(cn->key, path, e)) { /* Inefficient */ if (!cn_found) cn_found = cn; else log_warn("WARNING: Ignoring duplicate" " config node: %s (" "seeking %s)", cn->key, path); } cn = cn->sib; } } if (!cn_found && mem) { if (!(cn_found = _make_node(mem, path, e, parent))) return_NULL; } if (cn_found && *e) { parent = cn_found; cn = cn_found->child; } else return cn_found; path = e; } return NULL; } static struct dm_config_node *_section(struct parser *p, struct dm_config_node *parent) { /* IDENTIFIER SECTION_B_CHAR VALUE* SECTION_E_CHAR */ struct dm_config_node *root; struct dm_config_value *value; char *str; if (p->t == TOK_STRING_ESCAPED) { if (!(str = _dup_string_tok(p))) return_NULL; dm_unescape_double_quotes(str); match(TOK_STRING_ESCAPED); } else if (p->t == TOK_STRING) { if (!(str = _dup_string_tok(p))) return_NULL; match(TOK_STRING); } else { if (!(str = _dup_tok(p))) return_NULL; match(TOK_IDENTIFIER); } if (!strlen(str)) { log_error("Parse error at byte %" PRIptrdiff_t " (line %d): empty section identifier", p->tb - p->fb + 1, p->line); return NULL; } if (!(root = _find_or_make_node(p->mem, parent, str, p->no_dup_node_check))) return_NULL; if (p->t == TOK_SECTION_B) { match(TOK_SECTION_B); while (p->t != TOK_SECTION_E) { if (!(_section(p, root))) return_NULL; } match(TOK_SECTION_E); } else { match(TOK_EQ); p->key = root->key; if (!(value = _value(p))) return_NULL; if (root->v) log_warn("WARNING: Ignoring duplicate" " config value: %s", str); root->v = value; } return root; } static struct dm_config_value *_value(struct parser *p) { /* '[' TYPE* ']' | TYPE */ struct dm_config_value *h = NULL, *l, *ll = NULL; if (p->t == TOK_ARRAY_B) { match(TOK_ARRAY_B); while (p->t != TOK_ARRAY_E) { if (!(l = _type(p))) return_NULL; if (!h) h = l; else ll->next = l; ll = l; if (p->t == TOK_COMMA) match(TOK_COMMA); } match(TOK_ARRAY_E); /* * Special case for an empty array. */ if (!h) { if (!(h = _create_value(p->mem))) { log_error("Failed to allocate value"); return NULL; } h->type = DM_CFG_EMPTY_ARRAY; } } else if (!(h = _type(p))) return_NULL; return h; } static struct dm_config_value *_type(struct parser *p) { /* [+-]{0,1}[0-9]+ | [0-9]*\.[0-9]* | ".*" */ struct dm_config_value *v = _create_value(p->mem); char *str; if (!v) { log_error("Failed to allocate type value"); return NULL; } switch (p->t) { case TOK_INT: v->type = DM_CFG_INT; errno = 0; v->v.i = strtoll(p->tb, NULL, 0); /* FIXME: check error */ if (errno) { if (errno == ERANGE && p->key && strcmp("creation_time", p->key) == 0) { /* Due to a bug in some older 32bit builds (<2.02.169), * lvm was able to produce invalid creation_time string */ v->v.i = 1527120000; /* Pick 2018-05-24 day instead */ if (!p->ignored_creation_time++) log_warn("WARNING: Invalid creation_time found in metadata (repaired with next metadata update)."); } else { log_error("Failed to read int token."); return NULL; } } match(TOK_INT); break; case TOK_FLOAT: v->type = DM_CFG_FLOAT; errno = 0; v->v.f = strtod(p->tb, NULL); /* FIXME: check error */ if (errno) { log_error("Failed to read float token."); return NULL; } match(TOK_FLOAT); break; case TOK_STRING: v->type = DM_CFG_STRING; if (!(v->v.str = _dup_string_tok(p))) return_NULL; match(TOK_STRING); break; case TOK_STRING_BARE: v->type = DM_CFG_STRING; if (!(v->v.str = _dup_tok(p))) return_NULL; match(TOK_STRING_BARE); break; case TOK_STRING_ESCAPED: v->type = DM_CFG_STRING; if (!(str = _dup_string_tok(p))) return_NULL; dm_unescape_double_quotes(str); v->v.str = str; match(TOK_STRING_ESCAPED); break; default: log_error("Parse error at byte %" PRIptrdiff_t " (line %d): expected a value", p->tb - p->fb + 1, p->line); return NULL; } return v; } static int _match_aux(struct parser *p, int t) { if (p->t != t) return 0; _get_token(p, t); return 1; } /* * tokeniser */ static void _get_token(struct parser *p, int tok_prev) { int values_allowed = 0; const char *te; p->tb = p->te; _eat_space(p); if (p->tb == p->fe || !*p->tb) { p->t = TOK_EOF; return; } /* Should next token be interpreted as value instead of identifier? */ if (tok_prev == TOK_EQ || tok_prev == TOK_ARRAY_B || tok_prev == TOK_COMMA) values_allowed = 1; p->t = TOK_INT; /* fudge so the fall through for floats works */ te = p->te; switch (*te) { case SECTION_B_CHAR: p->t = TOK_SECTION_B; te++; break; case SECTION_E_CHAR: p->t = TOK_SECTION_E; te++; break; case '[': p->t = TOK_ARRAY_B; te++; break; case ']': p->t = TOK_ARRAY_E; te++; break; case ',': p->t = TOK_COMMA; te++; break; case '=': p->t = TOK_EQ; te++; break; case '"': p->t = TOK_STRING_ESCAPED; te++; while ((te != p->fe) && (*te) && (*te != '"')) { if ((*te == '\\') && (te + 1 != p->fe) && *(te + 1)) te++; te++; } if ((te != p->fe) && (*te)) te++; break; case '\'': p->t = TOK_STRING; te++; while ((te != p->fe) && (*te) && (*te != '\'')) te++; if ((te != p->fe) && (*te)) te++; break; case '.': p->t = TOK_FLOAT; /* Fall through */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': case '-': if (values_allowed) { while (++te != p->fe) { if (!isdigit((int) *te)) { if (*te == '.') { if (p->t != TOK_FLOAT) { p->t = TOK_FLOAT; continue; } } break; } } break; } /* fall through */ default: p->t = TOK_IDENTIFIER; while ((te != p->fe) && (*te) && !isspace(*te) && (*te != '#') && (*te != '=') && (*te != SECTION_B_CHAR) && (*te != SECTION_E_CHAR)) te++; if (values_allowed) p->t = TOK_STRING_BARE; break; } p->te = te; } static void _eat_space(struct parser *p) { while (p->tb != p->fe) { if (*p->te == '#') while ((p->te != p->fe) && (*p->te != '\n') && (*p->te)) ++p->te; else if (!isspace(*p->te)) break; while ((p->te != p->fe) && isspace(*p->te)) { if (*p->te == '\n') ++p->line; ++p->te; } p->tb = p->te; } } /* * memory management */ static struct dm_config_value *_create_value(struct dm_pool *mem) { return dm_pool_zalloc(mem, sizeof(struct dm_config_value)); } static struct dm_config_node *_create_node(struct dm_pool *mem) { return dm_pool_zalloc(mem, sizeof(struct dm_config_node)); } static char *_dup_token(struct dm_pool *mem, const char *b, const char *e) { size_t len = e - b; char *str = dm_pool_alloc(mem, len + 1); if (!str) { log_error("Failed to duplicate token."); return 0; } memcpy(str, b, len); str[len] = '\0'; return str; } static char *_dup_tok(struct parser *p) { return _dup_token(p->mem, p->tb, p->te); } /* * Utility functions */ /* * node_lookup_fn is either: * _find_config_node to perform a lookup starting from a given config_node * in a config_tree; * or * _find_first_config_node to find the first config_node in a set of * cascaded trees. */ typedef const struct dm_config_node *node_lookup_fn(const void *start, const char *path); static const struct dm_config_node *_find_config_node(const void *start, const char *path) { struct dm_config_node dummy = { .child = (void *) start }; return _find_or_make_node(NULL, &dummy, path, 0); } static const struct dm_config_node *_find_first_config_node(const void *start, const char *path) { const struct dm_config_tree *cft = start; const struct dm_config_node *cn = NULL; while (cft) { if ((cn = _find_config_node(cft->root, path))) return cn; cft = cft->cascade; } return NULL; } static const char *_find_config_str(const void *start, node_lookup_fn find_fn, const char *path, const char *fail, int allow_empty) { const struct dm_config_node *n = find_fn(start, path); /* Empty strings are ignored if allow_empty is set */ if (n && n->v) { if ((n->v->type == DM_CFG_STRING) && (allow_empty || (*n->v->v.str))) { /* log_very_verbose("Setting %s to %s", path, n->v->v.str); */ return n->v->v.str; } if ((n->v->type != DM_CFG_STRING) || (!allow_empty && fail)) log_warn("WARNING: Ignoring unsupported value for %s.", path); } if (fail) log_very_verbose("%s not found in config: defaulting to %s", path, fail); return fail; } const char *dm_config_find_str(const struct dm_config_node *cn, const char *path, const char *fail) { return _find_config_str(cn, _find_config_node, path, fail, 0); } const char *dm_config_find_str_allow_empty(const struct dm_config_node *cn, const char *path, const char *fail) { return _find_config_str(cn, _find_config_node, path, fail, 1); } static int64_t _find_config_int64(const void *start, node_lookup_fn find, const char *path, int64_t fail) { const struct dm_config_node *n = find(start, path); if (n && n->v && n->v->type == DM_CFG_INT) { /* log_very_verbose("Setting %s to %" PRId64, path, n->v->v.i); */ return n->v->v.i; } log_very_verbose("%s not found in config: defaulting to %" PRId64, path, fail); return fail; } static float _find_config_float(const void *start, node_lookup_fn find, const char *path, float fail) { const struct dm_config_node *n = find(start, path); if (n && n->v && n->v->type == DM_CFG_FLOAT) { /* log_very_verbose("Setting %s to %f", path, n->v->v.f); */ return n->v->v.f; } log_very_verbose("%s not found in config: defaulting to %f", path, fail); return fail; } static int _str_in_array(const char *str, const char * const values[]) { int i; for (i = 0; values[i]; i++) if (!strcasecmp(str, values[i])) return 1; return 0; } static int _str_to_bool(const char *str, int fail) { const char * const _true_values[] = { "y", "yes", "on", "true", NULL }; const char * const _false_values[] = { "n", "no", "off", "false", NULL }; if (_str_in_array(str, _true_values)) return 1; if (_str_in_array(str, _false_values)) return 0; return fail; } static int _find_config_bool(const void *start, node_lookup_fn find, const char *path, int fail) { const struct dm_config_node *n = find(start, path); const struct dm_config_value *v; int b; if (n) { v = n->v; switch (v->type) { case DM_CFG_INT: b = v->v.i ? 1 : 0; /* log_very_verbose("Setting %s to %d", path, b); */ return b; case DM_CFG_STRING: b = _str_to_bool(v->v.str, fail); /* log_very_verbose("Setting %s to %d", path, b); */ return b; default: ; } } log_very_verbose("%s not found in config: defaulting to %d", path, fail); return fail; } /*********************************** * node-based lookup **/ struct dm_config_node *dm_config_find_node(const struct dm_config_node *cn, const char *path) { return (struct dm_config_node *) _find_config_node(cn, path); } int dm_config_find_int(const struct dm_config_node *cn, const char *path, int fail) { /* FIXME Add log_error message on overflow */ return (int) _find_config_int64(cn, _find_config_node, path, (int64_t) fail); } int64_t dm_config_find_int64(const struct dm_config_node *cn, const char *path, int64_t fail) { return _find_config_int64(cn, _find_config_node, path, fail); } float dm_config_find_float(const struct dm_config_node *cn, const char *path, float fail) { return _find_config_float(cn, _find_config_node, path, fail); } int dm_config_find_bool(const struct dm_config_node *cn, const char *path, int fail) { return _find_config_bool(cn, _find_config_node, path, fail); } int dm_config_value_is_bool(const struct dm_config_value *v) { if (!v) return 0; switch(v->type) { case DM_CFG_INT: return 1; case DM_CFG_STRING: return _str_to_bool(v->v.str, -1) != -1; default: return 0; } } /*********************************** * tree-based lookup **/ const struct dm_config_node *dm_config_tree_find_node(const struct dm_config_tree *cft, const char *path) { return _find_first_config_node(cft, path); } const char *dm_config_tree_find_str(const struct dm_config_tree *cft, const char *path, const char *fail) { return _find_config_str(cft, _find_first_config_node, path, fail, 0); } const char *dm_config_tree_find_str_allow_empty(const struct dm_config_tree *cft, const char *path, const char *fail) { return _find_config_str(cft, _find_first_config_node, path, fail, 1); } int dm_config_tree_find_int(const struct dm_config_tree *cft, const char *path, int fail) { /* FIXME Add log_error message on overflow */ return (int) _find_config_int64(cft, _find_first_config_node, path, (int64_t) fail); } int64_t dm_config_tree_find_int64(const struct dm_config_tree *cft, const char *path, int64_t fail) { return _find_config_int64(cft, _find_first_config_node, path, fail); } float dm_config_tree_find_float(const struct dm_config_tree *cft, const char *path, float fail) { return _find_config_float(cft, _find_first_config_node, path, fail); } int dm_config_tree_find_bool(const struct dm_config_tree *cft, const char *path, int fail) { return _find_config_bool(cft, _find_first_config_node, path, fail); } /************************************/ int dm_config_get_uint32(const struct dm_config_node *cn, const char *path, uint32_t *result) { const struct dm_config_node *n; n = _find_config_node(cn, path); if (!n || !n->v || n->v->type != DM_CFG_INT) return 0; if (result) *result = n->v->v.i; return 1; } int dm_config_get_uint64(const struct dm_config_node *cn, const char *path, uint64_t *result) { const struct dm_config_node *n; n = _find_config_node(cn, path); if (!n || !n->v || n->v->type != DM_CFG_INT) return 0; if (result) *result = (uint64_t) n->v->v.i; return 1; } int dm_config_get_str(const struct dm_config_node *cn, const char *path, const char **result) { const struct dm_config_node *n; n = _find_config_node(cn, path); if (!n || !n->v || n->v->type != DM_CFG_STRING) return 0; if (result) *result = n->v->v.str; return 1; } int dm_config_get_list(const struct dm_config_node *cn, const char *path, const struct dm_config_value **result) { const struct dm_config_node *n; n = _find_config_node(cn, path); /* TODO when we represent single-item lists consistently, add a check * for n->v->next != NULL */ if (!n || !n->v) return 0; if (result) *result = n->v; return 1; } int dm_config_get_section(const struct dm_config_node *cn, const char *path, const struct dm_config_node **result) { const struct dm_config_node *n; n = _find_config_node(cn, path); if (!n || n->v) return 0; if (result) *result = n; return 1; } int dm_config_has_node(const struct dm_config_node *cn, const char *path) { return _find_config_node(cn, path) ? 1 : 0; } /* * Convert a token type to the char it represents. */ static char _token_type_to_char(int type) { switch (type) { case TOK_SECTION_B: return SECTION_B_CHAR; case TOK_SECTION_E: return SECTION_E_CHAR; default: return 0; } } /* * Returns: * # of 'type' tokens in 'str'. */ static unsigned _count_tokens(const char *str, unsigned len, int type) { char c; c = _token_type_to_char(type); return dm_count_chars(str, len, c); } const char *dm_config_parent_name(const struct dm_config_node *n) { return (n->parent ? n->parent->key : "(root)"); } /* * Heuristic function to make a quick guess as to whether a text * region probably contains a valid config "section". (Useful for * scanning areas of the disk for old metadata.) * Config sections contain various tokens, may contain other sections * and strings, and are delimited by begin (type 'TOK_SECTION_B') and * end (type 'TOK_SECTION_E') tokens. As a quick heuristic, we just * count the number of begin and end tokens, and see if they are * non-zero and the counts match. * Full validation of the section should be done with another function * (for example, read_config_fd). * * Returns: * 0 - probably is not a valid config section * 1 - probably _is_ a valid config section */ unsigned dm_config_maybe_section(const char *str, unsigned len) { int begin_count; int end_count; begin_count = _count_tokens(str, len, TOK_SECTION_B); end_count = _count_tokens(str, len, TOK_SECTION_E); if (begin_count && end_count && (begin_count == end_count)) return 1; else return 0; } __attribute__((nonnull(1, 2))) static struct dm_config_value *_clone_config_value(struct dm_pool *mem, const struct dm_config_value *v) { struct dm_config_value *new_cv; if (!(new_cv = _create_value(mem))) { log_error("Failed to clone config value."); return NULL; } new_cv->type = v->type; if (v->type == DM_CFG_STRING) { if (!(new_cv->v.str = dm_pool_strdup(mem, v->v.str))) { log_error("Failed to clone config string value."); return NULL; } } else new_cv->v = v->v; if (v->next && !(new_cv->next = _clone_config_value(mem, v->next))) return_NULL; return new_cv; } struct dm_config_node *dm_config_clone_node_with_mem(struct dm_pool *mem, const struct dm_config_node *cn, int siblings) { struct dm_config_node *new_cn; if (!cn) { log_error("Cannot clone NULL config node."); return NULL; } if (!(new_cn = _create_node(mem))) { log_error("Failed to clone config node."); return NULL; } if ((cn->key && !(new_cn->key = dm_pool_strdup(mem, cn->key)))) { log_error("Failed to clone config node key."); return NULL; } new_cn->id = cn->id; if ((cn->v && !(new_cn->v = _clone_config_value(mem, cn->v))) || (cn->child && !(new_cn->child = dm_config_clone_node_with_mem(mem, cn->child, 1))) || (siblings && cn->sib && !(new_cn->sib = dm_config_clone_node_with_mem(mem, cn->sib, siblings)))) return_NULL; /* 'new_cn' released with mem pool */ return new_cn; } struct dm_config_node *dm_config_clone_node(struct dm_config_tree *cft, const struct dm_config_node *node, int sib) { return dm_config_clone_node_with_mem(cft->mem, node, sib); } struct dm_config_node *dm_config_create_node(struct dm_config_tree *cft, const char *key) { struct dm_config_node *cn; if (!(cn = _create_node(cft->mem))) { log_error("Failed to create config node."); return NULL; } if (!(cn->key = dm_pool_strdup(cft->mem, key))) { log_error("Failed to create config node's key."); return NULL; } cn->parent = NULL; cn->v = NULL; return cn; } struct dm_config_value *dm_config_create_value(struct dm_config_tree *cft) { return _create_value(cft->mem); } void dm_config_value_set_format_flags(struct dm_config_value *cv, uint32_t format_flags) { if (!cv) return; cv->format_flags = format_flags; } uint32_t dm_config_value_get_format_flags(struct dm_config_value *cv) { if (!cv) return 0; return cv->format_flags; } struct dm_pool *dm_config_memory(struct dm_config_tree *cft) { return cft->mem; } static int _override_path(const char *path, struct dm_config_node *node, void *baton) { struct dm_config_tree *cft = baton; struct dm_config_node dummy, *target; dummy.child = cft->root; if (!(target = _find_or_make_node(cft->mem, &dummy, path, 0))) return_0; if (!(target->v = _clone_config_value(cft->mem, node->v))) return_0; cft->root = dummy.child; return 1; } static int _enumerate(const char *path, struct dm_config_node *cn, int (*cb)(const char *, struct dm_config_node *, void *), void *baton) { char *sub = NULL; while (cn) { if (dm_asprintf(&sub, "%s/%s", path, cn->key) < 0) return_0; if (cn->child) { if (!_enumerate(sub, cn->child, cb, baton)) goto_bad; } else if (!cb(sub, cn, baton)) goto_bad; dm_free(sub); cn = cn->sib; } return 1; bad: dm_free(sub); return 0; } struct dm_config_tree *dm_config_flatten(struct dm_config_tree *cft) { struct dm_config_tree *res = dm_config_create(), *done = NULL, *current = NULL; if (!res) return_NULL; while (done != cft) { current = cft; while (current->cascade != done) current = current->cascade; _enumerate("", current->root, _override_path, res); done = current; } return res; } int dm_config_remove_node(struct dm_config_node *parent, struct dm_config_node *rem_node) { struct dm_config_node *cn = parent->child, *last = NULL; while (cn) { if (cn == rem_node) { if (last) last->sib = cn->sib; else parent->child = cn->sib; return 1; } last = cn; cn = cn->sib; } return 0; }