lua: allow uci.set() to create/update a list value
[project/uci.git] / file.c
diff --git a/file.c b/file.c
index 9f59637..d551208 100644 (file)
--- a/file.c
+++ b/file.c
@@ -24,6 +24,7 @@
 #include <fcntl.h>
 #include <stdio.h>
 #include <ctype.h>
+#include <glob.h>
 
 static struct uci_backend uci_file_backend;
 
@@ -35,6 +36,7 @@ static void assert_eol(struct uci_context *ctx, char **str)
 {
        char *tmp;
 
+       skip_whitespace(ctx, str);
        tmp = next_arg(ctx, str, false, false);
        if (*tmp && (ctx->flags & UCI_FLAG_STRICT))
                uci_parse_error(ctx, *str, "too many arguments");
@@ -69,7 +71,7 @@ static void uci_switch_config(struct uci_context *ctx)
         * if an older config under the same name exists, unload it
         * ignore errors here, e.g. if the config was not found
         */
-       e = uci_lookup_list(ctx, &ctx->root, name);
+       e = uci_lookup_list(&ctx->root, name);
        if (e)
                UCI_THROW(ctx, UCI_ERR_DUPLICATE);
        pctx->package = uci_alloc_package(ctx, name);
@@ -122,11 +124,12 @@ static void uci_parse_config(struct uci_context *ctx, char **str)
 
        if (pctx->merge) {
                UCI_TRAP_SAVE(ctx, error);
-               uci_set(ctx, pctx->package, name, NULL, type, NULL);
+               if (uci_set(ctx, pctx->package, name, NULL, type, NULL) != UCI_OK)
+                       goto error;
                UCI_TRAP_RESTORE(ctx);
                return;
 error:
-               UCI_THROW(ctx, ctx->errno);
+               UCI_THROW(ctx, ctx->err);
        } else
                pctx->section = uci_alloc_section(pctx->package, type, name);
 }
@@ -156,11 +159,50 @@ static void uci_parse_option(struct uci_context *ctx, char **str)
                UCI_TRAP_RESTORE(ctx);
                return;
 error:
-               UCI_THROW(ctx, ctx->errno);
+               UCI_THROW(ctx, ctx->err);
        } else
                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 {
+               struct uci_option *o;
+               struct uci_element *e;
+
+               e = uci_lookup_list(&pctx->section->options, name);
+               o = uci_to_option(e);
+               if (!o) {
+                       o = uci_alloc_list(pctx->section, name);
+               } else {
+                       if (o->type != UCI_TYPE_LIST)
+                               uci_parse_error(ctx, *str, "conflicting list/option keywords");
+               }
+               UCI_INTERNAL(uci_add_element_list, ctx, o, value);
+       }
+}
+
 
 /*
  * parse a complete input line, split up combined commands by ';'
@@ -168,38 +210,50 @@ error:
 static void uci_parse_line(struct uci_context *ctx, bool single)
 {
        struct uci_parse_context *pctx = ctx->pctx;
-       char *word, *brk = NULL;
-
-       for (word = strtok_r(pctx->buf, ";", &brk);
-                word;
-                word = strtok_r(NULL, ";", &brk)) {
-
-               char *pbrk = NULL;
-               word = strtok_r(word, " \t", &pbrk);
+       char *word, *brk;
 
+       word = pctx->buf;
+       do {
+               brk = NULL;
+               word = strtok_r(word, " \t", &brk);
                if (!word)
-                       continue;
+                       return;
 
                switch(word[0]) {
+                       case 0:
                        case '#':
                                return;
                        case 'p':
                                if ((word[1] == 0) || !strcmp(word + 1, "ackage"))
                                        uci_parse_package(ctx, &word, single);
+                               else
+                                       goto invalid;
                                break;
                        case 'c':
                                if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
                                        uci_parse_config(ctx, &word);
+                               else
+                                       goto invalid;
                                break;
                        case 'o':
                                if ((word[1] == 0) || !strcmp(word + 1, "ption"))
                                        uci_parse_option(ctx, &word);
+                               else
+                                       goto invalid;
                                break;
-                       default:
-                               uci_parse_error(ctx, word, "unterminated command");
+                       case 'l':
+                               if ((word[1] == 0) || !strcmp(word + 1, "ist"))
+                                       uci_parse_list(ctx, &word);
+                               else
+                                       goto invalid;
                                break;
+                       default:
+                               goto invalid;
                }
-       }
+               continue;
+invalid:
+               uci_parse_error(ctx, word, "invalid command");
+       } while (1);
 }
 
 /* max number of characters that escaping adds to the string */
@@ -208,49 +262,54 @@ static void uci_parse_line(struct uci_context *ctx, bool single)
 /*
  * escape an uci string for export
  */
-static char *uci_escape(struct uci_context *ctx, char *str)
+static char *uci_escape(struct uci_context *ctx, const char *str)
 {
-       char *s, *p;
-       int pos = 0;
+       const char *end;
+       int ofs = 0;
 
        if (!ctx->buf) {
                ctx->bufsz = LINEBUF;
                ctx->buf = malloc(LINEBUF);
        }
 
-       s = str;
-       p = strchr(str, '\'');
-       if (!p)
-               return str;
+       while (1) {
+               int len;
 
-       do {
-               int len = p - s;
-               if (len > 0) {
-                       if (p + sizeof(UCI_QUOTE_ESCAPE) - str >= ctx->bufsz) {
-                               ctx->bufsz *= 2;
-                               ctx->buf = realloc(ctx->buf, ctx->bufsz);
-                               if (!ctx->buf)
-                                       UCI_THROW(ctx, UCI_ERR_MEM);
-                       }
-                       memcpy(&ctx->buf[pos], s, len);
-                       pos += len;
+               end = strchr(str, '\'');
+               if (!end)
+                       end = str + strlen(str);
+               len = end - str;
+
+               /* make sure that we have enough room in the buffer */
+               while (ofs + len + sizeof(UCI_QUOTE_ESCAPE) + 1 > ctx->bufsz) {
+                       ctx->bufsz *= 2;
+                       ctx->buf = uci_realloc(ctx, ctx->buf, ctx->bufsz);
                }
-               strcpy(&ctx->buf[pos], UCI_QUOTE_ESCAPE);
-               pos += sizeof(UCI_QUOTE_ESCAPE);
-               s = p + 1;
-       } while ((p = strchr(s, '\'')));
 
+               /* copy the string until the character before the quote */
+               memcpy(&ctx->buf[ofs], str, len);
+               ofs += len;
+
+               /* end of string? return the buffer */
+               if (*end == 0)
+                       break;
+
+               memcpy(&ctx->buf[ofs], UCI_QUOTE_ESCAPE, sizeof(UCI_QUOTE_ESCAPE));
+               ofs += strlen(&ctx->buf[ofs]);
+               str = end + 1;
+       }
+
+       ctx->buf[ofs] = 0;
        return ctx->buf;
 }
 
-
 /*
  * export a single config package to a file stream
  */
 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));
@@ -262,8 +321,21 @@ 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);
-                       fprintf(stream, "\toption '%s'", uci_escape(ctx, opt->e.name));
-                       fprintf(stream, " '%s'\n", uci_escape(ctx, opt->value));
+                       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;
+                       }
                }
        }
        fprintf(stream, "\n");
@@ -293,7 +365,6 @@ int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct u
        UCI_HANDLE_ERR(ctx);
 
        /* make sure no memory from previous parse attempts is leaked */
-       ctx->internal = true;
        uci_cleanup(ctx);
 
        uci_alloc_parse_context(ctx);
@@ -310,7 +381,7 @@ int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct u
         * NB: the config file can still override the package name
         */
        if (name) {
-               UCI_ASSERT(ctx, uci_validate_name(name));
+               UCI_ASSERT(ctx, uci_validate_str(name, false));
                pctx->name = name;
        }
 
@@ -324,12 +395,14 @@ int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct u
 error:
                if (ctx->flags & UCI_FLAG_PERROR)
                        uci_perror(ctx, NULL);
-               if ((ctx->errno != UCI_ERR_PARSE) ||
+               if ((ctx->err != UCI_ERR_PARSE) ||
                        (ctx->flags & UCI_FLAG_STRICT))
-                       UCI_THROW(ctx, ctx->errno);
+                       UCI_THROW(ctx, ctx->err);
        }
 
        uci_fixup_section(ctx, ctx->pctx->section);
+       if (!pctx->package && name)
+               uci_switch_config(ctx);
        if (package)
                *package = pctx->package;
        if (pctx->merge)
@@ -349,7 +422,7 @@ static char *uci_config_path(struct uci_context *ctx, const char *name)
 {
        char *filename;
 
-       UCI_ASSERT(ctx, uci_validate_name(name));
+       UCI_ASSERT(ctx, uci_validate_str(name, false));
        filename = uci_malloc(ctx, strlen(name) + strlen(ctx->confdir) + 2);
        sprintf(filename, "%s/%s", ctx->confdir, name);
 
@@ -375,7 +448,7 @@ void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool
 
        /* flush unsaved changes and reload from history file */
        UCI_TRAP_SAVE(ctx, done);
-       if (p->confdir) {
+       if (p->has_history) {
                if (!overwrite) {
                        name = uci_strdup(ctx, p->e.name);
                        path = uci_strdup(ctx, p->path);
@@ -392,7 +465,7 @@ void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool
                        UCI_INTERNAL(uci_import, ctx, f, name, &p, true);
 
                        p->path = path;
-                       p->confdir = true;
+                       p->has_history = true;
                        *package = p;
 
                        /* freed together with the uci_package */
@@ -420,8 +493,8 @@ done:
        if (path)
                free(path);
        uci_close_stream(f);
-       if (ctx->errno)
-               UCI_THROW(ctx, ctx->errno);
+       if (ctx->err)
+               UCI_THROW(ctx, ctx->err);
 }
 
 
@@ -475,6 +548,9 @@ static char **uci_list_config_files(struct uci_context *ctx)
                if (!p)
                        continue;
 
+               if (!uci_validate_name(p))
+                       continue;
+
                configs[i] = buf;
                strcpy(buf, p);
                buf += strlen(buf) + 1;
@@ -510,27 +586,26 @@ static struct uci_package *uci_file_load(struct uci_context *ctx, const char *na
        }
 
        file = uci_open_stream(ctx, filename, SEEK_SET, false, false);
-       ctx->errno = 0;
+       ctx->err = 0;
        UCI_TRAP_SAVE(ctx, done);
        UCI_INTERNAL(uci_import, ctx, file, name, &package, true);
        UCI_TRAP_RESTORE(ctx);
 
        if (package) {
                package->path = filename;
-               package->confdir = confdir;
+               package->has_history = confdir;
                uci_load_history(ctx, package, false);
        }
 
 done:
        uci_close_stream(file);
-       if (ctx->errno)
-               UCI_THROW(ctx, ctx->errno);
+       if (ctx->err)
+               UCI_THROW(ctx, ctx->err);
        return package;
 }
 
-static struct uci_backend uci_file_backend = {
-       .name = "file",
+static UCI_BACKEND(uci_file_backend, "file",
        .load = uci_file_load,
        .commit = uci_file_commit,
        .list_configs = uci_list_config_files,
-};
+);