diff options
author | Felix Fietkau <nbd@openwrt.org> | 2008-08-18 16:54:57 +0200 |
---|---|---|
committer | Felix Fietkau <nbd@openwrt.org> | 2008-08-18 16:54:57 +0200 |
commit | 3bff697cca125a6a4147e3bd1c469b36504e8237 (patch) | |
tree | efe3b96d0e101d1cd8d53f842b45824c582d47eb | |
parent | cac9b1d765338466c1c77719c4119c565689b4c4 (diff) | |
download | uci-3bff697cca125a6a4147e3bd1c469b36504e8237.tar.gz |
add list support
-rw-r--r-- | cli.c | 60 | ||||
-rw-r--r-- | file.c | 44 | ||||
-rw-r--r-- | history.c | 37 | ||||
-rw-r--r-- | list.c | 131 | ||||
-rw-r--r-- | uci.h | 29 |
5 files changed, 260 insertions, 41 deletions
@@ -19,6 +19,7 @@ #define MAX_ARGS 4 /* max command line arguments for batch mode */ +static const char *delimiter = " "; static const char *appname; static enum { CLI_FLAG_MERGE = (1 << 0), @@ -34,6 +35,7 @@ enum { /* section cmds */ CMD_GET, CMD_SET, + CMD_ADD_LIST, CMD_DEL, CMD_RENAME, CMD_REVERT, @@ -61,14 +63,17 @@ static void uci_usage(void) "\tchanges [<config>]\n" "\tcommit [<config>]\n" "\tadd <config> <section-type>\n" + "\tadd_list <config>.<section>.<option>=<string>\n" "\tshow [<config>[.<section>[.<option>]]]\n" "\tget <config>.<section>[.<option>]\n" "\tset <config>.<section>[.<option>]=<value>\n" + "\tdelete <config>[.<section[.<option>]]\n" "\trename <config>.<section>[.<option>]=<name>\n" "\trevert <config>[.<section>[.<option>]]\n" "\n" "Options:\n" "\t-c <path> set the search path for config files (default: /etc/config)\n" + "\t-d <str> set the delimiter for list values in uci show\n" "\t-f <file> use <file> as input instead of stdin\n" "\t-m when importing, merge data into an existing package\n" "\t-n name unnamed sections on export (default)\n" @@ -91,23 +96,37 @@ static void cli_perror(void) uci_perror(ctx, appname); } -static void uci_show_option(struct uci_option *o) +static void uci_show_value(struct uci_option *o) { - printf("%s.%s.%s=", - o->section->package->e.name, - o->section->e.name, - o->e.name); + struct uci_element *e; + bool sep = false; switch(o->type) { case UCI_TYPE_STRING: printf("%s\n", o->v.string); break; + case UCI_TYPE_LIST: + uci_foreach_element(&o->v.list, e) { + printf("%s%s", (sep ? delimiter : ""), e->name); + sep = true; + } + printf("\n"); + break; default: printf("<unknown>\n"); break; } } +static void uci_show_option(struct uci_option *o) +{ + printf("%s.%s.%s=", + o->section->package->e.name, + o->section->e.name, + o->e.name); + uci_show_value(o); +} + static void uci_show_section(struct uci_section *p) { struct uci_element *e; @@ -326,7 +345,7 @@ static int uci_do_section_cmd(int cmd, int argc, char **argv) return 1; } - if (value && (cmd != CMD_SET) && (cmd != CMD_RENAME)) + if (value && (cmd != CMD_SET) && (cmd != CMD_ADD_LIST) && (cmd != CMD_RENAME)) return 1; if (uci_lookup_ext(ctx, &e, argv[1]) != UCI_OK) { @@ -335,6 +354,9 @@ static int uci_do_section_cmd(int cmd, int argc, char **argv) } switch(e->type) { + case UCI_TYPE_PACKAGE: + p = uci_to_package(e); + break; case UCI_TYPE_SECTION: s = uci_to_section(e); break; @@ -345,8 +367,10 @@ static int uci_do_section_cmd(int cmd, int argc, char **argv) default: return 1; } - section = s->e.name; - p = s->package; + if (s) { + section = s->e.name; + p = s->package; + } switch(cmd) { case CMD_GET: @@ -356,14 +380,7 @@ static int uci_do_section_cmd(int cmd, int argc, char **argv) break; case UCI_TYPE_OPTION: o = uci_to_option(e); - switch(o->type) { - case UCI_TYPE_STRING: - printf("%s\n", o->v.string); - break; - default: - printf("<unknown>\n"); - break; - } + uci_show_value(o); break; default: break; @@ -379,6 +396,9 @@ static int uci_do_section_cmd(int cmd, int argc, char **argv) case CMD_SET: ret = uci_set(ctx, p, section, option, value, NULL); break; + case CMD_ADD_LIST: + ret = uci_add_list(ctx, p, section, option, value, NULL); + break; case CMD_DEL: ret = uci_delete(ctx, p, section, option); break; @@ -495,10 +515,13 @@ static int uci_cmd(int argc, char **argv) cmd = CMD_HELP; else if (!strcasecmp(argv[0], "add")) cmd = CMD_ADD; + else if (!strcasecmp(argv[0], "add_list")) + cmd = CMD_ADD_LIST; else cmd = -1; switch(cmd) { + case CMD_ADD_LIST: case CMD_GET: case CMD_SET: case CMD_DEL: @@ -535,11 +558,14 @@ int main(int argc, char **argv) return 1; } - while((c = getopt(argc, argv, "c:f:mnNp:P:sSq")) != -1) { + while((c = getopt(argc, argv, "c:d:f:mnNp:P:sSq")) != -1) { switch(c) { case 'c': uci_set_confdir(ctx, optarg); break; + case 'd': + delimiter = optarg; + break; case 'f': input = fopen(optarg, "r"); if (!input) { @@ -24,6 +24,7 @@ #include <fcntl.h> #include <stdio.h> #include <ctype.h> +#include <glob.h> static struct uci_backend uci_file_backend; @@ -163,6 +164,33 @@ error: uci_alloc_option(pctx->section, name, value); } +static void uci_parse_list(struct uci_context *ctx, char **str) +{ + struct uci_parse_context *pctx = ctx->pctx; + char *name = NULL; + char *value = NULL; + + if (!pctx->section) + uci_parse_error(ctx, *str, "list command found before the first section"); + + /* command string null-terminated by strtok */ + *str += strlen(*str) + 1; + + name = next_arg(ctx, str, true, true); + value = next_arg(ctx, str, false, false); + assert_eol(ctx, str); + + if (pctx->merge) { + UCI_TRAP_SAVE(ctx, error); + uci_add_list(ctx, pctx->package, pctx->section->e.name, name, value, NULL); + UCI_TRAP_RESTORE(ctx); + return; +error: + UCI_THROW(ctx, ctx->err); + } else + UCI_INTERNAL(uci_add_list, ctx, pctx->package, pctx->section->e.name, name, value, NULL); +} + /* * parse a complete input line, split up combined commands by ';' @@ -201,6 +229,12 @@ static void uci_parse_line(struct uci_context *ctx, bool single) else goto invalid; break; + case 'l': + if ((word[1] == 0) || !strcmp(word + 1, "ist")) + uci_parse_list(ctx, &word); + else + goto invalid; + break; default: goto invalid; } @@ -263,7 +297,7 @@ static char *uci_escape(struct uci_context *ctx, const char *str) static void uci_export_package(struct uci_package *p, FILE *stream, bool header) { struct uci_context *ctx = p->ctx; - struct uci_element *s, *o; + struct uci_element *s, *o, *i; if (header) fprintf(stream, "package '%s'\n", uci_escape(ctx, p->e.name)); @@ -275,11 +309,17 @@ static void uci_export_package(struct uci_package *p, FILE *stream, bool header) fprintf(stream, "\n"); uci_foreach_element(&sec->options, o) { struct uci_option *opt = uci_to_option(o); - switch(o->type) { + switch(opt->type) { case UCI_TYPE_STRING: fprintf(stream, "\toption '%s'", uci_escape(ctx, opt->e.name)); fprintf(stream, " '%s'\n", uci_escape(ctx, opt->v.string)); break; + case UCI_TYPE_LIST: + uci_foreach_element(&opt->v.list, i) { + fprintf(stream, "\tlist '%s'", uci_escape(ctx, opt->e.name)); + fprintf(stream, " '%s'\n", uci_escape(ctx, i->name)); + } + break; default: fprintf(stream, "\t# unknown type for option '%s'\n", uci_escape(ctx, opt->e.name)); break; @@ -92,17 +92,25 @@ static inline void uci_parse_history_tuple(struct uci_context *ctx, char **buf, { int c = UCI_CMD_CHANGE; - if (**buf == '-') { + switch(**buf) { + case '-': c = UCI_CMD_REMOVE; - *buf += 1; - } else if (**buf == '@') { + break; + case '@': c = UCI_CMD_RENAME; - *buf += 1; - } else if (**buf == '+') { - /* UCI_CMD_ADD is used for anonymous sections */ + break; + case '+': + /* UCI_CMD_ADD is used for anonymous sections or list values */ c = UCI_CMD_ADD; - *buf += 1; + break; + case '|': + c = UCI_CMD_LIST_ADD; + break; } + + if (c != UCI_CMD_CHANGE) + *buf += 1; + if (cmd) *cmd = c; @@ -143,6 +151,9 @@ static void uci_parse_history_line(struct uci_context *ctx, struct uci_package * case UCI_CMD_REMOVE: UCI_INTERNAL(uci_delete, ctx, p, section, option); break; + case UCI_CMD_LIST_ADD: + UCI_INTERNAL(uci_add_list, ctx, p, section, option, value, NULL); + break; case UCI_CMD_ADD: case UCI_CMD_CHANGE: UCI_INTERNAL(uci_set, ctx, p, section, option, value, &e); @@ -387,22 +398,26 @@ int uci_save(struct uci_context *ctx, struct uci_package *p) uci_foreach_element_safe(&p->history, tmp, e) { struct uci_history *h = uci_to_history(e); + char *prefix = ""; switch(h->cmd) { case UCI_CMD_REMOVE: - fprintf(f, "-"); + prefix = "-"; break; case UCI_CMD_RENAME: - fprintf(f, "@"); + prefix = "@"; break; case UCI_CMD_ADD: - fprintf(f, "+"); + prefix = "+"; + break; + case UCI_CMD_LIST_ADD: + prefix = "|"; break; default: break; } - fprintf(f, "%s.%s", p->e.name, h->section); + fprintf(f, "%s%s.%s", prefix, p->e.name, h->section); if (e->name) fprintf(f, ".%s", e->name); @@ -12,8 +12,6 @@ * GNU General Public License for more details. */ -#include <glob.h> - /* initialize a list head/item */ static inline void uci_list_init(struct uci_list *ptr) { @@ -110,18 +108,41 @@ uci_alloc_option(struct uci_section *s, const char *name, const char *value) static inline void uci_free_option(struct uci_option *o) { + struct uci_element *e, *tmp; + switch(o->type) { case UCI_TYPE_STRING: if ((o->v.string != uci_dataptr(o)) && (o->v.string != NULL)) free(o->v.string); break; + case UCI_TYPE_LIST: + uci_foreach_element_safe(&o->v.list, tmp, e) { + uci_free_element(e); + } + break; default: break; } uci_free_element(&o->e); } +static struct uci_option * +uci_alloc_list(struct uci_section *s, const char *name) +{ + struct uci_package *p = s->package; + struct uci_context *ctx = p->ctx; + struct uci_option *o; + + o = uci_alloc_element(ctx, option, name, 0); + o->type = UCI_TYPE_LIST; + o->section = s; + uci_list_init(&o->v.list); + uci_list_add(&s->options, &o->e.list); + + return o; +} + /* fix up an unnamed section, e.g. after adding options to it */ static void uci_fixup_section(struct uci_context *ctx, struct uci_section *s) { @@ -428,6 +449,29 @@ int uci_del_element(struct uci_context *ctx, struct uci_element *e) default: break; } + + return 0; +} + +int uci_add_element_list(struct uci_context *ctx, struct uci_option *o, const char *value) +{ + struct uci_element *e; + struct uci_package *p; + struct uci_section *s; + bool internal = ctx->internal; + + UCI_HANDLE_ERR(ctx); + UCI_ASSERT(ctx, (o != NULL) && (o->type == UCI_TYPE_LIST) && uci_validate_text(value)); + + s = o->section; + p = s->package; + + if (!internal && p->has_history) + uci_add_history(ctx, &p->history, UCI_CMD_LIST_ADD, s->e.name, o->e.name, value); + + e = uci_alloc_generic(ctx, UCI_TYPE_ITEM, value, 0); + uci_list_add(&o->v.list, &e->list); + return 0; } @@ -443,7 +487,7 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, char *section; char *option; char *str; - int size; + int size = 0; UCI_HANDLE_ERR(ctx); UCI_ASSERT(ctx, (element != NULL) && (*element != NULL)); @@ -459,6 +503,7 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, */ e = *element; list = e->list.prev; + switch(e->type) { case UCI_TYPE_SECTION: UCI_ASSERT(ctx, uci_validate_str(value, false)); @@ -470,19 +515,27 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, if (!strcmp(value, s->type)) return 0; break; + case UCI_TYPE_OPTION: UCI_ASSERT(ctx, value != NULL); - size = sizeof(struct uci_option); o = uci_to_option(e); s = o->section; section = s->e.name; option = o->e.name; - if (o->type != UCI_TYPE_STRING) - UCI_THROW(ctx, UCI_ERR_INVAL); - /* matches the currently set value */ - if (!strcmp(value, o->v.string)) - return 0; + switch(o->type) { + case UCI_TYPE_STRING: + size = sizeof(struct uci_option); + /* matches the currently set value */ + if (!strcmp(value, o->v.string)) + return 0; + break; + default: + /* default action for non-string datatypes is to delete + * the existing entry, then re-create it as a string */ + break; + } break; + default: UCI_THROW(ctx, UCI_ERR_INVAL); return 0; @@ -491,6 +544,13 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, if (!internal && p->has_history) uci_add_history(ctx, &p->history, UCI_CMD_CHANGE, section, option, value); + if ((e->type == UCI_TYPE_OPTION) && (size == 0)) { + o = uci_alloc_option(s, option, value); + UCI_INTERNAL(uci_del_element, ctx, e); + *element = &o->e; + goto done; + } + uci_list_del(&e->list); e = uci_realloc(ctx, e, size); str = uci_strdup(ctx, value); @@ -508,6 +568,7 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, break; } +done: return 0; } @@ -564,6 +625,58 @@ int uci_delete(struct uci_context *ctx, struct uci_package *p, const char *secti return uci_del_element(ctx, e); } +int uci_add_list(struct uci_context *ctx, struct uci_package *p, const char *section, const char *option, const char *value, struct uci_option **result) +{ + /* NB: UCI_INTERNAL use means without history tracking */ + bool internal = ctx->internal; + struct uci_element *e; + struct uci_section *s; + struct uci_option *o; + struct uci_option *prev = NULL; + const char *value2 = NULL; + + UCI_HANDLE_ERR(ctx); + UCI_ASSERT(ctx, p && section && option && value && uci_validate_text(value)); + + /* look up the section first */ + UCI_INTERNAL(uci_lookup, ctx, &e, p, section, NULL); + s = uci_to_section(e); + + e = uci_lookup_list(&s->options, option); + if (e) { + o = uci_to_option(e); + switch (o->type) { + case UCI_TYPE_STRING: + /* we already have a string value, let's convert that to a list */ + prev = o; + value2 = value; + value = o->v.string; + break; + case UCI_TYPE_LIST: + if (result) + *result = o; + + ctx->internal = internal; + return uci_add_element_list(ctx, o, value); + default: + UCI_THROW(ctx, UCI_ERR_INVAL); + break; + } + } + + o = uci_alloc_list(s, option); + if (result) + *result = o; + if (prev) { + UCI_INTERNAL(uci_add_element_list, ctx, o, value); + uci_free_option(prev); + value = value2; + } + + ctx->internal = internal; + return uci_add_element_list(ctx, o, value); +} + int uci_set(struct uci_context *ctx, struct uci_package *p, const char *section, const char *option, const char *value, struct uci_element **result) { /* NB: UCI_INTERNAL use means without history tracking */ @@ -187,6 +187,14 @@ extern int uci_add_section(struct uci_context *ctx, struct uci_package *p, const extern int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, const char *value); /** + * uci_add_element_list: Append a string to a list option + * @ctx: uci context + * @option: pointer to the uci option element + * @value: string to append + */ +extern int uci_add_element_list(struct uci_context *ctx, struct uci_option *o, const char *value); + +/** * uci_set: Set an element's value; create the element if necessary * @ctx: uci context * @package: package name @@ -198,6 +206,20 @@ extern int uci_set_element_value(struct uci_context *ctx, struct uci_element **e extern int uci_set(struct uci_context *ctx, struct uci_package *p, const char *section, const char *option, const char *value, struct uci_element **result); /** + * uci_add_list: Append a string to an element list + * @ctx: uci context + * @package: package name + * @section: section name + * @option: option name + * @value: string value + * @result: store the updated element in this variable (optional) + * + * Note: if the given option already contains a string, convert it to an 1-element-list + * before appending the next element + */ +extern int uci_add_list(struct uci_context *ctx, struct uci_package *p, const char *section, const char *option, const char *value, struct uci_option **result); + +/** * uci_rename: Rename an element * @ctx: uci context * @package: package name @@ -317,10 +339,12 @@ enum uci_type { UCI_TYPE_OPTION = 3, UCI_TYPE_PATH = 4, UCI_TYPE_BACKEND = 5, + UCI_TYPE_ITEM = 6, }; enum uci_option_type { UCI_TYPE_STRING = 0, + UCI_TYPE_LIST = 1, }; enum uci_flags { @@ -410,7 +434,7 @@ struct uci_option struct uci_section *section; enum uci_option_type type; union { - struct uci_element *list; + struct uci_list list; char *string; } v; }; @@ -419,7 +443,8 @@ enum uci_command { UCI_CMD_ADD, UCI_CMD_REMOVE, UCI_CMD_CHANGE, - UCI_CMD_RENAME + UCI_CMD_RENAME, + UCI_CMD_LIST_ADD, }; struct uci_history |