diff options
Diffstat (limited to 'libdm')
-rw-r--r-- | libdm/Makefile.in | 1 | ||||
-rw-r--r-- | libdm/libdevmapper.h | 143 | ||||
-rw-r--r-- | libdm/libdm-config.c | 1237 | ||||
-rw-r--r-- | libdm/libdm-string.c | 222 |
4 files changed, 1603 insertions, 0 deletions
diff --git a/libdm/Makefile.in b/libdm/Makefile.in index 70be3095..b4f137a1 100644 --- a/libdm/Makefile.in +++ b/libdm/Makefile.in @@ -25,6 +25,7 @@ SOURCES =\ libdm-deptree.c \ libdm-string.c \ libdm-report.c \ + libdm-config.c \ mm/dbg_malloc.c \ mm/pool.c \ regex/matcher.c \ diff --git a/libdm/libdevmapper.h b/libdm/libdevmapper.h index ea75376b..62f56ae9 100644 --- a/libdm/libdevmapper.h +++ b/libdm/libdevmapper.h @@ -19,6 +19,7 @@ #include <inttypes.h> #include <stdarg.h> #include <sys/types.h> +#include <sys/stat.h> #ifdef linux # include <linux/types.h> @@ -1014,6 +1015,46 @@ int dm_snprintf(char *buf, size_t bufsize, const char *format, ...) */ const char *dm_basename(const char *path); +/* + * Count occurences of 'c' in 'str' of length 'size'. + * + * Returns: + * Number of occurrences of 'c' + */ +unsigned dm_count_chars(const char *str, size_t len, const int c); + +/* + * Length of string after escaping double quotes and backslashes. + */ +size_t dm_escaped_len(const char *str); + +/* + * <vg>-<lv>-<layer> or if !layer just <vg>-<lv>. + */ +char *dm_build_dm_name(struct dm_pool *mem, const char *vgname, + const char *lvname, const char *layer); +char *dm_build_dm_uuid(struct dm_pool *mem, const char *prefix, const char *lvid, const char *layer); + +/* + * Copies a string, quoting double quotes with backslashes. + */ +char *dm_escape_double_quotes(char *out, const char *src); + +/* + * Undo quoting in situ. + */ +void dm_unescape_double_quotes(char *src); + +/* + * Unescape colons and "at" signs in situ and save the substrings + * starting at the position of the first unescaped colon and the + * first unescaped "at" sign. This is normally used to unescape + * device names used as PVs. + */ +void dm_unescape_colons_and_at_signs(char *src, + char **substr_first_unquoted_colon, + char **substr_first_unquoted_at_sign); + /************************** * file/stream manipulation **************************/ @@ -1181,6 +1222,108 @@ int dm_report_field_uint64(struct dm_report *rh, struct dm_report_field *field, void dm_report_field_set_value(struct dm_report_field *field, const void *value, const void *sortvalue); + +/********************************* + * config file parse/prettyprint + *********************************/ +enum { + DM_CFG_STRING, + DM_CFG_FLOAT, + DM_CFG_INT, + DM_CFG_EMPTY_ARRAY +}; + +struct dm_config_value { + int type; + union { + int64_t i; + float r; + const char *str; + } v; + struct dm_config_value *next; /* for arrays */ +}; + +struct dm_config_node { + const char *key; + struct dm_config_node *parent, *sib, *child; + struct dm_config_value *v; +}; + +struct dm_config_tree { + struct dm_config_node *root; + struct dm_config_tree *cascade; +}; + +struct dm_config_tree *dm_config_create(const char *filename, int keep_open); +struct dm_config_tree *dm_config_from_string(const char *config_settings); +int dm_config_parse(struct dm_config_tree *cft, const char *start, const char *end); + +void *dm_config_get_custom(struct dm_config_tree *cft); +int dm_config_check_file(struct dm_config_tree *cft, const char **filename, struct stat *info); +int dm_config_keep_open(struct dm_config_tree *ctf); + +void dm_config_set_custom(struct dm_config_tree *cft, void *custom); + +void dm_config_destroy(struct dm_config_tree *cft); + +int dm_config_write(struct dm_config_tree *cft, const char *file, + int argc, char **argv); + +typedef int (*dm_putline_fn)(const char *line, void *baton); +int dm_config_write_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton); + +time_t dm_config_timestamp(struct dm_config_tree *cft); +int dm_config_changed(struct dm_config_tree *cft); + +const struct dm_config_node *dm_config_find_node(const struct dm_config_node *cn, const char *path); +const char *dm_config_find_str(const struct dm_config_node *cn, const char *path, const char *fail); +int dm_config_find_int(const struct dm_config_node *cn, const char *path, int fail); +float dm_config_find_float(const struct dm_config_node *cn, const char *path, float fail); + +const struct dm_config_node *dm_config_tree_find_node( + const struct dm_config_tree *cft, const char *path); +const char *dm_config_tree_find_str(const struct dm_config_tree *cft, + const char *path, const char *fail); +int dm_config_tree_find_int(const struct dm_config_tree *cft, + const char *path, int fail); +int64_t dm_config_tree_find_int64(const struct dm_config_tree *cft, + const char *path, int64_t fail); +float dm_config_tree_find_float(const struct dm_config_tree *cft, + const char *path, float fail); +int dm_config_tree_find_bool(const struct dm_config_tree *cft, + const char *path, int fail); + +/* + * Understands (0, ~0), (y, n), (yes, no), (on, + * off), (true, false). + */ +int dm_config_find_bool(const struct dm_config_node *cn, const char *path, int fail); + +int dm_config_get_uint32(const struct dm_config_node *cn, const char *path, + uint32_t *result); + +int dm_config_get_uint64(const struct dm_config_node *cn, const char *path, + uint64_t *result); + +int dm_config_get_str(const struct dm_config_node *cn, const char *path, + const char **result); + +unsigned dm_config_maybe_section(const char *str, unsigned len); + +const char *dm_config_parent_name(const struct dm_config_node *n); + +struct dm_config_node *dm_config_clone_node_with_mem(struct dm_pool *mem, + const struct dm_config_node *node, + int siblings); +struct dm_config_node *dm_config_create_node(struct dm_config_tree *cft, const char *key); +struct dm_config_value *dm_config_create_value(struct dm_config_tree *cft); +struct dm_config_node *dm_config_clone_node(struct dm_config_tree *cft, + const struct dm_config_node *cn, + int siblings); + +struct dm_pool *dm_config_memory(struct dm_config_tree *cft); + + /* Cookie prefixes. * The cookie value consists of a prefix (16 bits) and a base (16 bits). * We can use the prefix to store the flags. These flags are sent to diff --git a/libdm/libdm-config.c b/libdm/libdm-config.c new file mode 100644 index 00000000..688d9a7c --- /dev/null +++ b/libdm/libdm-config.c @@ -0,0 +1,1237 @@ +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "dmlib.h" + +#include <sys/stat.h> +#include <sys/mman.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#define SECTION_B_CHAR '{' +#define SECTION_E_CHAR '}' + +enum { + TOK_INT, + TOK_FLOAT, + TOK_STRING, /* Single quotes */ + TOK_STRING_ESCAPED, /* Double 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; +}; + +struct cs { + struct dm_config_tree cft; + struct dm_pool *mem; + time_t timestamp; + off_t st_size; + char *filename; + int exists; + int keep_open; + void *custom; /* LVM uses this for a device pointer */ +}; + +struct output_line { + FILE *fp; + struct dm_pool *mem; + dm_putline_fn putline; + void *putline_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); +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 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)); +} + +/* + * public interface + */ +struct dm_config_tree *dm_config_create(const char *filename, int keep_open) +{ + struct cs *c; + struct dm_pool *mem = dm_pool_create("config", 10 * 1024); + + if (!mem) { + log_error("Failed to allocate config pool."); + return 0; + } + + if (!(c = dm_pool_zalloc(mem, sizeof(*c)))) { + log_error("Failed to allocate config tree."); + dm_pool_destroy(mem); + return 0; + } + + c->mem = mem; + c->cft.root = (struct dm_config_node *) NULL; + c->cft.cascade = NULL; + c->timestamp = 0; + c->exists = 0; + c->keep_open = keep_open; + c->custom = NULL; + if (filename) + c->filename = dm_pool_strdup(c->mem, filename); + return &c->cft; +} + +void dm_config_set_custom(struct dm_config_tree *cft, void *custom) +{ + struct cs *c = (struct cs *) cft; + c->custom = custom; +} + +void *dm_config_get_custom(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + return c->custom; +} + +int dm_config_keep_open(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + return c->keep_open; +} + +void dm_config_destroy(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + dm_pool_destroy(c->mem); +} + +int dm_config_parse(struct dm_config_tree *cft, const char *start, const char *end) +{ + /* TODO? if (start == end) return 1; */ + + struct cs *c = (struct cs *) cft; + struct parser *p; + if (!(p = dm_pool_alloc(c->mem, sizeof(*p)))) + return_0; + + p->mem = c->mem; + p->fb = start; + p->fe = end; + p->tb = p->te = p->fb; + p->line = 1; + + _get_token(p, TOK_SECTION_E); + if (!(cft->root = _file(p))) + return_0; + + return 1; +} + +struct dm_config_tree *dm_config_from_string(const char *config_settings) +{ + struct dm_config_tree *cft; + + if (!(cft = dm_config_create(NULL, 0))) + return_NULL; + + if (!dm_config_parse(cft, config_settings, config_settings + strlen(config_settings))) { + dm_config_destroy(cft); + return_NULL; + } + + return cft; +} + +int dm_config_check_file(struct dm_config_tree *cft, const char **filename, struct stat *info) +{ + struct cs *c = (struct cs *) cft; + struct stat _info; + if (!info) + info = &_info; + if (filename) + *filename = c->filename; + + if (stat(c->filename, info)) { + log_sys_error("stat", c->filename); + c->exists = 0; + return 0; + } + + if (!S_ISREG(info->st_mode)) { + log_error("%s is not a regular file", c->filename); + c->exists = 0; + return 0; + } + + c->exists = 1; + + if (info->st_size == 0) + log_verbose("%s is empty", c->filename); + + c->timestamp = info->st_ctime; + c->st_size = info->st_size; + + return 1; +} + +time_t dm_config_timestamp(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + return c->timestamp; +} + +/* + * Return 1 if config files ought to be reloaded + */ +int dm_config_changed(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + struct stat info; + + if (!c->filename) + return 0; + + if (stat(c->filename, &info) == -1) { + /* Ignore a deleted config file: still use original data */ + if (errno == ENOENT) { + if (!c->exists) + return 0; + log_very_verbose("Config file %s has disappeared!", + c->filename); + goto reload; + } + log_sys_error("stat", c->filename); + log_error("Failed to reload configuration files"); + return 0; + } + + if (!S_ISREG(info.st_mode)) { + log_error("Configuration file %s is not a regular file", + c->filename); + goto reload; + } + + /* Unchanged? */ + if (c->timestamp == info.st_ctime && c->st_size == info.st_size) + return 0; + + reload: + log_verbose("Detected config file change to %s", c->filename); + return 1; +} + +static int _line_start(struct output_line *outline) +{ + if (!dm_pool_begin_object(outline->mem, 128)) { + log_error("dm_pool_begin_object failed for config line"); + return 0; + } + + return 1; +} + +static int _line_append(struct output_line *outline, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +static int _line_append(struct output_line *outline, const char *fmt, ...) +{ + char buf[4096]; + va_list ap; + int n; + + va_start(ap, fmt); + n = vsnprintf(&buf[0], sizeof buf - 1, fmt, ap); + va_end(ap); + + if (n < 0 || n > (int) sizeof buf - 1) { + log_error("vsnprintf failed for config line"); + return 0; + } + + if (!dm_pool_grow_object(outline->mem, &buf[0], strlen(buf))) { + log_error("dm_pool_grow_object failed for config line"); + return 0; + } + + return 1; +} + +#define line_append(args...) do {if (!_line_append(outline, args)) {return_0;}} while (0) + +static int _line_end(struct output_line *outline) +{ + const char *line; + + if (!dm_pool_grow_object(outline->mem, "\0", 1)) { + log_error("dm_pool_grow_object failed for config line"); + return 0; + } + + line = dm_pool_end_object(outline->mem); + if (outline->putline) + outline->putline(line, outline->putline_baton); + else { + if (!outline->fp) + log_print("%s", line); + else + fprintf(outline->fp, "%s\n", line); + } + + return 1; +} + +static int _write_value(struct output_line *outline, const struct dm_config_value *v) +{ + char *buf; + + switch (v->type) { + case DM_CFG_STRING: + if (!(buf = alloca(dm_escaped_len(v->v.str)))) { + log_error("temporary stack allocation for a config " + "string failed"); + return 0; + } + line_append("\"%s\"", dm_escape_double_quotes(buf, v->v.str)); + break; + + case DM_CFG_FLOAT: + line_append("%f", v->v.r); + break; + + case DM_CFG_INT: + line_append("%" PRId64, v->v.i); + break; + + case DM_CFG_EMPTY_ARRAY: + line_append("[]"); + 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 output_line *outline, int level) +{ + char space[MAX_INDENT + 1]; + int l = (level < MAX_INDENT) ? level : MAX_INDENT; + int i; + + if (!n) + return 1; + + for (i = 0; i < l; i++) + space[i] = '\t'; + space[i] = '\0'; + + do { + if (!_line_start(outline)) + return_0; + line_append("%s%s", space, n->key); + if (!n->v) { + /* it's a sub section */ + line_append(" {"); + if (!_line_end(outline)) + return_0; + _write_config(n->child, 0, outline, level + 1); + if (!_line_start(outline)) + return_0; + line_append("%s}", space); + } else { + /* it's a value */ + const struct dm_config_value *v = n->v; + line_append("="); + if (v->next) { + line_append("["); + while (v && v->type != DM_CFG_EMPTY_ARRAY) { + if (!_write_value(outline, v)) + return_0; + v = v->next; + if (v && v->type != DM_CFG_EMPTY_ARRAY) + line_append(", "); + } + line_append("]"); + } else + if (!_write_value(outline, v)) + return_0; + } + if (!_line_end(outline)) + return_0; + n = n->sib; + } while (n && !only_one); + /* FIXME: add error checking */ + return 1; +} + +int dm_config_write_node(const struct dm_config_node *cn, dm_putline_fn putline, void *baton) +{ + struct output_line outline; + outline.fp = NULL; + if (!(outline.mem = dm_pool_create("config_line", 1024))) + return_0; + outline.putline = putline; + outline.putline_baton = baton; + if (!_write_config(cn, 0, &outline, 0)) { + dm_pool_destroy(outline.mem); + return_0; + } + dm_pool_destroy(outline.mem); + return 1; +} + +int dm_config_write(struct dm_config_tree *cft, const char *file, + int argc, char **argv) +{ + const struct dm_config_node *cn; + int r = 1; + struct output_line outline; + outline.fp = NULL; + outline.putline = NULL; + + if (!file) + file = "stdout"; + else if (!(outline.fp = fopen(file, "w"))) { + log_sys_error("open", file); + return 0; + } + + if (!(outline.mem = dm_pool_create("config_line", 1024))) { + r = 0; + goto_out; + } + + log_verbose("Dumping configuration to %s", file); + if (!argc) { + if (!_write_config(cft->root, 0, &outline, 0)) { + log_error("Failure while writing to %s", file); + r = 0; + } + } else while (argc--) { + if ((cn = dm_config_find_node(cft->root, *argv))) { + if (!_write_config(cn, 1, &outline, 0)) { + log_error("Failure while writing to %s", file); + r = 0; + } + } else { + log_error("Configuration node %s not found", *argv); + r = 0; + } + argv++; + } + + dm_pool_destroy(outline.mem); + +out: + if (outline.fp && dm_fclose(outline.fp)) { + stack; + r = 0; + } + + return r; +} + +/* + * parser + */ +static struct dm_config_node *_file(struct parser *p) +{ + struct dm_config_node *root = NULL, *n, *l = NULL; + while (p->t != TOK_EOF) { + if (!(n = _section(p))) + return_0; + + if (!root) + root = n; + else + l->sib = n; + n->parent = root; + l = n; + } + return root; +} + +static struct dm_config_node *_section(struct parser *p) +{ + /* IDENTIFIER SECTION_B_CHAR VALUE* SECTION_E_CHAR */ + struct dm_config_node *root, *n, *l = NULL; + if (!(root = _create_node(p->mem))) + return_0; + + if (!(root->key = _dup_tok(p))) + return_0; + + match(TOK_IDENTIFIER); + + if (p->t == TOK_SECTION_B) { + match(TOK_SECTION_B); + while (p->t != TOK_SECTION_E) { + if (!(n = _section(p))) + return_0; + + if (!root->child) + root->child = n; + else + l->sib = n; + n->parent = root; + l = n; + } + match(TOK_SECTION_E); + } else { + match(TOK_EQ); + if (!(root->v = _value(p))) + return_0; + } + + 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_0; + + 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))) + return NULL; + + h->type = DM_CFG_EMPTY_ARRAY; + } + + } else + h = _type(p); + + 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) + return NULL; + + switch (p->t) { + case TOK_INT: + v->type = DM_CFG_INT; + v->v.i = strtoll(p->tb, NULL, 0); /* FIXME: check error */ + match(TOK_INT); + break; + + case TOK_FLOAT: + v->type = DM_CFG_FLOAT; + v->v.r = strtod(p->tb, NULL); /* FIXME: check error */ + match(TOK_FLOAT); + break; + + case TOK_STRING: + v->type = DM_CFG_STRING; + + p->tb++, p->te--; /* strip "'s */ + if (!(v->v.str = _dup_tok(p))) + return_0; + p->te++; + match(TOK_STRING); + break; + + case TOK_STRING_ESCAPED: + v->type = DM_CFG_STRING; + + p->tb++, p->te--; /* strip "'s */ + if (!(str = _dup_tok(p))) + return_0; + dm_unescape_double_quotes(str); + v->v.str = str; + p->te++; + 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 0; + } + 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++; + 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_tok(struct parser *p) +{ + size_t len = p->te - p->tb; + char *str = dm_pool_alloc(p->mem, len + 1); + if (!str) { + log_error("Failed to duplicate token."); + return 0; + } + memcpy(str, p->tb, len); + str[len] = '\0'; + return str; +} + +/* + * utility functions + */ +static const struct dm_config_node *_find_config_node(const void *start, + const char *path) +{ + const char *e; + const struct dm_config_node *cn = start; + const struct dm_config_node *cn_found = NULL; + + while (cn) { + /* 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; + 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 && *e) + cn = cn_found->child; + else + break; /* don't move into the last node */ + + path = e; + } + + return cn_found; +} + +typedef const struct dm_config_node *_node_lookup_fn(const void *start, const char *path); + +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, + const char *path, const char *fail) +{ + const struct dm_config_node *n = find(start, path); + + /* Empty strings are ignored */ + if ((n && n->v && n->v->type == DM_CFG_STRING) && (*n->v->v.str)) { + log_very_verbose("Setting %s to %s", path, n->v->v.str); + return n->v->v.str; + } + + 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); +} + +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.r); + return n->v->v.r; + } + + 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; + + if (!n) + return fail; + + v = n->v; + + switch (v->type) { + case DM_CFG_INT: + return v->v.i ? 1 : 0; + + case DM_CFG_STRING: + return _str_to_bool(v->v.str, fail); + } + + return fail; +} + +/*********************************** + * node-based lookup + **/ + +const struct dm_config_node *dm_config_find_node(const struct dm_config_node *cn, + const char *path) +{ + return _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); +} + +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); +} + +/*********************************** + * 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); +} + +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 = dm_config_find_node(cn, path); + + if (!n || !n->v || n->v->type != DM_CFG_INT) + return 0; + + *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 = dm_config_find_node(cn, path); + + if (!n || !n->v || n->v->type != DM_CFG_INT) + return 0; + + *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 = dm_config_find_node(cn, path); + + if (!n || !n->v || n->v->type != DM_CFG_STRING) + return 0; + + *result = n->v->v.str; + return 1; +} + +/* + * 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; +} + +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 (!v) + return NULL; + + 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) + 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; + } + + 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) +{ + struct cs *c = (struct cs *) cft; + return dm_config_clone_node_with_mem(c->mem, node, sib); +} + +struct dm_config_node *dm_config_create_node(struct dm_config_tree *cft, const char *key) +{ + struct cs *c = (struct cs *) cft; + struct dm_config_node *cn; + + if (!(cn = _create_node(c->mem))) { + log_error("Failed to create config node."); + return NULL; + } + if (!(cn->key = dm_pool_strdup(c->mem, key))) { + log_error("Failed to create config node's key."); + return NULL; + } + if (!(cn->v = _create_value(c->mem))) { + log_error("Failed to create config node's value."); + return NULL; + } + cn->parent = NULL; + cn->v->type = DM_CFG_INT; + cn->v->v.i = 0; + cn->v->next = NULL; + return cn; +} + +struct dm_config_value *dm_config_create_value(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + return _create_value(c->mem); +} + +struct dm_pool *dm_config_memory(struct dm_config_tree *cft) +{ + struct cs *c = (struct cs *) cft; + return c->mem; +} diff --git a/libdm/libdm-string.c b/libdm/libdm-string.c index 365e7ec0..25080c04 100644 --- a/libdm/libdm-string.c +++ b/libdm/libdm-string.c @@ -161,3 +161,225 @@ int dm_asprintf(char **result, const char *format, ...) dm_free(buf); return n + 1; } + +/* + * Count occurences of 'c' in 'str' until we reach a null char. + * + * Returns: + * len - incremented for each char we encounter. + * count - number of occurrences of 'c' and 'c2'. + */ +static void _count_chars(const char *str, size_t *len, int *count, + const int c1, const int c2) +{ + const char *ptr; + + for (ptr = str; *ptr; ptr++, (*len)++) + if (*ptr == c1 || *ptr == c2) + (*count)++; +} + +unsigned dm_count_chars(const char *str, size_t len, const int c) +{ + size_t i; + unsigned count = 0; + + for (i = 0; i < len; i++) + if (str[i] == c) + count++; + + return count; +} + +size_t dm_escaped_len(const char *str) +{ + size_t len = 1; + int count = 0; + + _count_chars(str, &len, &count, '\"', '\\'); + + return count + len; +} + +/* + * Copies a string, quoting orig_char with quote_char. + * Optionally also quote quote_char. + */ +static void _quote_characters(char **out, const char *src, + const int orig_char, const int quote_char, + int quote_quote_char) +{ + while (*src) { + if (*src == orig_char || + (*src == quote_char && quote_quote_char)) + *(*out)++ = quote_char; + + *(*out)++ = *src++; + } +} + +static void _unquote_one_character(char *src, const char orig_char, + const char quote_char) +{ + char *out; + char s, n; + + /* Optimise for the common case where no changes are needed. */ + while ((s = *src++)) { + if (s == quote_char && + ((n = *src) == orig_char || n == quote_char)) { + out = src++; + *(out - 1) = n; + + while ((s = *src++)) { + if (s == quote_char && + ((n = *src) == orig_char || n == quote_char)) { + s = n; + src++; + } + *out = s; + out++; + } + + *out = '\0'; + return; + } + } +} + +/* + * Unquote each character given in orig_char array and unquote quote_char + * as well. Also save the first occurrence of each character from orig_char + * that was found unquoted in arr_substr_first_unquoted array. This way we can + * process several characters in one go. + */ +static void _unquote_characters(char *src, const char *orig_chars, + size_t num_orig_chars, + const char quote_char, + char *arr_substr_first_unquoted[]) +{ + char *out = src; + char c, s, n; + unsigned i; + + while ((s = *src++)) { + for (i = 0; i < num_orig_chars; i++) { + c = orig_chars[i]; + if (s == quote_char && + ((n = *src) == c || n == quote_char)) { + s = n; + src++; + break; + } + if (arr_substr_first_unquoted && (s == c) && + !arr_substr_first_unquoted[i]) + arr_substr_first_unquoted[i] = out; + }; + *out++ = s; + } + + *out = '\0'; +} + +/* + * Copies a string, quoting hyphens with hyphens. + */ +static void _quote_hyphens(char **out, const char *src) +{ + _quote_characters(out, src, '-', '-', 0); +} + +char *dm_build_dm_name(struct dm_pool *mem, const char *vgname, + const char *lvname, const char *layer) +{ + size_t len = 1; + int hyphens = 1; + char *r, *out; + + _count_chars(vgname, &len, &hyphens, '-', 0); + _count_chars(lvname, &len, &hyphens, '-', 0); + + if (layer && *layer) { + _count_chars(layer, &len, &hyphens, '-', 0); + hyphens++; + } + + len += hyphens; + + if (!(r = dm_pool_alloc(mem, len))) { + log_error("build_dm_name: Allocation failed for %" PRIsize_t + " for %s %s %s.", len, vgname, lvname, layer); + return NULL; + } + + out = r; + _quote_hyphens(&out, vgname); + *out++ = '-'; + _quote_hyphens(&out, lvname); + + if (layer && *layer) { + /* No hyphen if the layer begins with _ e.g. _mlog */ + if (*layer != '_') + *out++ = '-'; + _quote_hyphens(&out, layer); + } + *out = '\0'; + + return r; +} + +char *dm_build_dm_uuid(struct dm_pool *mem, const char *prefix, const char *lvid, const char *layer) +{ + char *dmuuid; + size_t len; + + if (!layer) + layer = ""; + + len = strlen(prefix) + strlen(lvid) + strlen(layer) + 1; + + if (!(dmuuid = dm_pool_alloc(mem, len))) { + log_error("build_dm_name: Allocation failed for %" PRIsize_t + " %s %s.", len, lvid, layer); + return NULL; + } + + sprintf(dmuuid, "%s%s%s%s", prefix, lvid, (*layer) ? "-" : "", layer); + + return dmuuid; +} + +/* + * Copies a string, quoting double quotes with backslashes. + */ +char *dm_escape_double_quotes(char *out, const char *src) +{ + char *buf = out; + + _quote_characters(&buf, src, '\"', '\\', 1); + *buf = '\0'; + + return out; +} + +void dm_unescape_double_quotes(char *src) +{ + _unquote_one_character(src, '\"', '\\'); +} + +void dm_unescape_colons_and_at_signs(char *src, + char **substr_first_unquoted_colon, + char **substr_first_unquoted_at_sign) +{ + const char *orig_chars = ":@"; + char *arr_substr_first_unquoted[] = {NULL, NULL, NULL}; + + _unquote_characters(src, orig_chars, 2, '\\', arr_substr_first_unquoted); + + if (substr_first_unquoted_colon) + *substr_first_unquoted_colon = arr_substr_first_unquoted[0]; + + if (substr_first_unquoted_at_sign) + *substr_first_unquoted_at_sign = arr_substr_first_unquoted[1]; +} + |