implement history parsing
authorFelix Fietkau <nbd@openwrt.org>
Tue, 29 Jan 2008 23:14:54 +0000 (00:14 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Tue, 29 Jan 2008 23:14:54 +0000 (00:14 +0100)
cli.c
err.h
file.c
list.c
uci.h

diff --git a/cli.c b/cli.c
index 410d7e8..e17acb5 100644 (file)
--- a/cli.c
+++ b/cli.c
@@ -176,7 +176,10 @@ static int uci_do_set(int argc, char **argv)
                uci_perror(ctx, "uci");
                return 1;
        }
-       uci_commit(ctx, p);
+       if (uci_save(ctx, p) != UCI_OK) {
+               uci_perror(ctx, "uci");
+               return 1;
+       }
        return 0;
 }
 
diff --git a/err.h b/err.h
index 2e1e606..078fdbe 100644 (file)
--- a/err.h
+++ b/err.h
@@ -27,6 +27,7 @@
  * in the context.
  */
 #define UCI_THROW(ctx, err) do {       \
+       DPRINTF("Exception: %s in %s, %s:%d\n", #err, __func__, __FILE__, __LINE__); \
        longjmp(ctx->trap, err);        \
 } while (0)
 
  * and UCI_TRAP_RESTORE.
  */
 #define UCI_HANDLE_ERR(ctx) do {       \
-       int __val;                      \
+       int __val = 0;                  \
        if (!ctx)                       \
                return UCI_ERR_INVAL;   \
-       __val = setjmp(ctx->trap);      \
+       if (!ctx->internal)             \
+               __val = setjmp(ctx->trap); \
+       ctx->internal = false;          \
        if (__val) {                    \
                ctx->errno = __val;     \
                return __val;           \
        memcpy(ctx->trap, __old_trap, sizeof(ctx->trap)); \
 } while(0)
 
+#define UCI_INTERNAL(func, ctx, ...) do { \
+       ctx->internal = true;           \
+       func(ctx, __VA_ARGS__);         \
+} while (0);
+
 /*
  * check the specified condition.
  * throw an invalid argument exception if it's false
diff --git a/file.c b/file.c
index e6722b2..0e5992c 100644 (file)
--- a/file.c
+++ b/file.c
@@ -97,8 +97,6 @@ static void uci_file_cleanup(struct uci_context *ctx)
 
        if (pctx->buf)
                free(pctx->buf);
-       if (pctx->file)
-               fclose(pctx->file);
 
        free(pctx);
 }
@@ -520,29 +518,152 @@ int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct u
        return 0;
 }
 
+/*
+ * open a stream and go to the right position
+ *
+ * note: when opening for write and seeking to the beginning of
+ * the stream, truncate the file
+ */
+static FILE *uci_open_stream(struct uci_context *ctx, const char *filename, int pos, bool write)
+{
+       FILE *file = NULL;
+       int fd, ret;
+
+       fd = open(filename, (write ? O_RDWR | O_CREAT : O_RDONLY));
+       if (fd <= 0)
+               goto error;
+
+       if (flock(fd, (write ? LOCK_EX : LOCK_SH)) < 0)
+               goto error;
+
+       if (write && (pos == SEEK_SET))
+               ret = ftruncate(fd, 0);
+       else
+               ret = lseek(fd, 0, pos);
+
+       if (ret < 0)
+               goto error;
+
+       file = fdopen(fd, (write ? "w" : "r"));
+       if (file)
+               goto done;
+
+error:
+       UCI_THROW(ctx, UCI_ERR_IO);
+done:
+       return file;
+}
+
+static void uci_close_stream(FILE *stream)
+{
+       int fd;
+
+       if (!stream)
+               return;
+
+       fd = fileno(stream);
+       flock(fd, LOCK_UN);
+       fclose(stream);
+}
+
+static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf)
+{
+       bool delete = false;
+       char *package = NULL;
+       char *section = NULL;
+       char *option = NULL;
+       char *value = NULL;
+
+       if (buf[0] == '-') {
+               delete = true;
+               buf++;
+       }
+
+       UCI_INTERNAL(uci_parse_tuple, ctx, buf, &package, &section, &option, &value);
+       if (!package || !section || !value)
+               goto error;
+       if (strcmp(package, p->e.name) != 0)
+               goto error;
+       if (!uci_validate_name(section))
+               goto error;
+       if (option && !uci_validate_name(option))
+               goto error;
+       if (!delete)
+               UCI_INTERNAL(uci_set, ctx, package, section, option, value);
+       return;
+
+error:
+       UCI_THROW(ctx, UCI_ERR_PARSE);
+}
+
+static void uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p)
+{
+       struct uci_parse_context *pctx;
+
+       /* make sure no memory from previous parse attempts is leaked */
+       uci_file_cleanup(ctx);
+
+       pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
+       ctx->pctx = pctx;
+       pctx->file = stream;
+
+       rewind(stream);
+       while (!feof(pctx->file)) {
+               uci_getln(ctx, 0);
+               if (!pctx->buf[0])
+                       continue;
+               uci_parse_history_line(ctx, p, pctx->buf);
+       }
+
+       /* no error happened, we can get rid of the parser context now */
+       uci_file_cleanup(ctx);
+}
+
+static void uci_load_history(struct uci_context *ctx, struct uci_package *p)
+{
+       char *filename;
+       FILE *f = NULL;
+
+       if (!p->confdir)
+               return;
+       if ((asprintf(&filename, "%s/%s", UCI_SAVEDIR, p->e.name) < 0) || !filename)
+               UCI_THROW(ctx, UCI_ERR_MEM);
+
+       UCI_TRAP_SAVE(ctx, done);
+       f = uci_open_stream(ctx, filename, SEEK_SET, false);
+       uci_parse_history(ctx, f, p);
+       UCI_TRAP_RESTORE(ctx);
+done:
+       uci_close_stream(f);
+       ctx->errno = 0;
+}
+
 int uci_load(struct uci_context *ctx, const char *name, struct uci_package **package)
 {
        struct stat statbuf;
        char *filename;
        bool confdir;
-       FILE *file;
-       int fd;
+       FILE *file = NULL;
 
        UCI_HANDLE_ERR(ctx);
        UCI_ASSERT(ctx, name != NULL);
 
        switch (name[0]) {
        case '.':
+               /* relative path outside of /etc/config */
                if (name[1] != '/')
                        UCI_THROW(ctx, UCI_ERR_NOTFOUND);
                /* fall through */
        case '/':
-               /* absolute/relative path outside of /etc/config */
+               /* absolute path outside of /etc/config */
                filename = uci_strdup(ctx, name);
                name = strrchr(name, '/') + 1;
                confdir = false;
                break;
        default:
+               /* config in /etc/config */
+               if (strchr(name, '/'))
+                       UCI_THROW(ctx, UCI_ERR_INVAL);
                filename = uci_malloc(ctx, strlen(name) + sizeof(UCI_CONFDIR) + 2);
                sprintf(filename, UCI_CONFDIR "/%s", name);
                confdir = true;
@@ -554,17 +675,7 @@ int uci_load(struct uci_context *ctx, const char *name, struct uci_package **pac
                UCI_THROW(ctx, UCI_ERR_NOTFOUND);
        }
 
-       fd = open(filename, O_RDONLY);
-       if (fd <= 0)
-               UCI_THROW(ctx, UCI_ERR_IO);
-
-       if (flock(fd, LOCK_SH) < 0)
-               UCI_THROW(ctx, UCI_ERR_IO);
-
-       file = fdopen(fd, "r");
-       if (!file)
-               UCI_THROW(ctx, UCI_ERR_IO);
-
+       file = uci_open_stream(ctx, filename, SEEK_SET, false);
        ctx->errno = 0;
        UCI_TRAP_SAVE(ctx, done);
        uci_import(ctx, file, name, package, true);
@@ -573,50 +684,82 @@ int uci_load(struct uci_context *ctx, const char *name, struct uci_package **pac
        if (*package) {
                (*package)->path = filename;
                (*package)->confdir = confdir;
+               uci_load_history(ctx, *package);
        }
 
 done:
-       flock(fd, LOCK_UN);
-       fclose(file);
+       uci_close_stream(file);
        return ctx->errno;
 }
 
-int uci_commit(struct uci_context *ctx, struct uci_package *p)
+int uci_save(struct uci_context *ctx, struct uci_package *p)
 {
        FILE *f = NULL;
-       int fd = 0;
-       int err = UCI_ERR_IO;
+       char *filename = NULL;
+       struct uci_element *e, *tmp;
 
        UCI_HANDLE_ERR(ctx);
        UCI_ASSERT(ctx, p != NULL);
-       UCI_ASSERT(ctx, p->path != NULL);
 
-       fd = open(p->path, O_RDWR);
-       if (fd < 0)
-               goto done;
+       /* 
+        * if the config file was outside of the /etc/config path,
+        * don't save the history to a file, update the real file
+        * directly
+        */
+       if (!p->confdir)
+               return uci_commit(ctx, p);
 
-       if (flock(fd, LOCK_EX) < 0)
-               goto done;
+       if (uci_list_empty(&p->history))
+               return 0;
 
-       ftruncate(fd, 0);
-       f = fdopen(fd, "w");
-       if (!f)
-               goto done;
+       if ((asprintf(&filename, "%s/%s", UCI_SAVEDIR, p->e.name) < 0) || !filename)
+               UCI_THROW(ctx, UCI_ERR_MEM);
 
+       ctx->errno = 0;
        UCI_TRAP_SAVE(ctx, done);
-       uci_export(ctx, f, p, false);
+       f = uci_open_stream(ctx, filename, SEEK_END, true);
        UCI_TRAP_RESTORE(ctx);
 
+       uci_foreach_element_safe(&p->history, tmp, e) {
+               struct uci_history *h = uci_to_history(e);
+               if (h->cmd == UCI_CMD_REMOVE)
+                       fprintf(f, "-");
+               fprintf(f, "%s.%s", p->e.name, h->section);
+               if (e->name)
+                       fprintf(f, ".%s", e->name);
+               fprintf(f, "=%s\n", h->value);
+               uci_list_del(&e->list);
+       }
+
 done:
-       if (f)
-               fclose(f);
-       else if (fd > 0)
-               close(fd);
+       uci_close_stream(f);
+       if (filename)
+               free(filename);
+       if (ctx->errno)
+               UCI_THROW(ctx, ctx->errno);
+
+       return 0;
+}
+
+int uci_commit(struct uci_context *ctx, struct uci_package *p)
+{
+       FILE *f = NULL;
 
+       UCI_HANDLE_ERR(ctx);
+       UCI_ASSERT(ctx, p != NULL);
+       UCI_ASSERT(ctx, p->path != NULL);
+
+       f = uci_open_stream(ctx, p->path, SEEK_SET, true);
+
+       UCI_TRAP_SAVE(ctx, done);
+       uci_export(ctx, f, p, false);
+       UCI_TRAP_RESTORE(ctx);
+
+done:
+       uci_close_stream(f);
        if (ctx->errno)
                UCI_THROW(ctx, ctx->errno);
-       if (err)
-               UCI_THROW(ctx, UCI_ERR_IO);
+
        return 0;
 }
 
@@ -679,4 +822,3 @@ char **uci_list_configs(struct uci_context *ctx)
        return configs;
 }
 
-
diff --git a/list.c b/list.c
index d78970c..19fe8c5 100644 (file)
--- a/list.c
+++ b/list.c
@@ -58,13 +58,18 @@ static struct uci_element *
 uci_alloc_generic(struct uci_context *ctx, int type, const char *name, int size)
 {
        struct uci_element *e;
+       int datalen = size;
        void *ptr;
 
-       ptr = uci_malloc(ctx, size + strlen(name) + 1);
+       if (name)
+               datalen += strlen(name) + 1;
+       ptr = uci_malloc(ctx, datalen);
        e = (struct uci_element *) ptr;
        e->type = type;
-       e->name = (char *) ptr + size;
-       strcpy(e->name, name);
+       if (name) {
+               e->name = (char *) ptr + size;
+               strcpy(e->name, name);
+       }
        uci_list_init(&e->list);
 
        return e;
@@ -165,17 +170,19 @@ uci_free_package(struct uci_package *p)
 static inline void
 uci_add_history(struct uci_context *ctx, struct uci_package *p, int cmd, char *section, char *option, char *value)
 {
-       struct uci_history *h = (struct uci_history *) uci_malloc(ctx, sizeof(struct uci_history));
+       struct uci_history *h;
+       int size = 0;
+       char *ptr;
 
-       uci_list_init(&h->list);
+       h = uci_alloc_element(ctx, history, option, strlen(section) + strlen(value) + 2);
+       ptr = uci_dataptr(h);
        h->cmd = cmd;
-       h->section = section;
-       h->option = option;
-       h->value = value;
-       uci_list_add(&p->history, &h->list);
+       h->section = strcpy(ptr, section);
+       ptr += strlen(ptr) + 1;
+       h->value = strcpy(ptr, value);
+       uci_list_add(&p->history, &h->e.list);
 }
 
-
 static struct uci_element *uci_lookup_list(struct uci_context *ctx, struct uci_list *list, const char *name)
 {
        struct uci_element *e;
@@ -217,14 +224,15 @@ notfound:
 
 int uci_set_element_value(struct uci_context *ctx, struct uci_element **element, char *value)
 {
-       int size;
-       char *str;
+       bool internal = ctx->internal;
        struct uci_list *list;
        struct uci_element *e;
        struct uci_package *p;
        struct uci_section *s;
        char *section;
        char *option;
+       char *str;
+       int size;
 
        UCI_HANDLE_ERR(ctx);
        UCI_ASSERT(ctx, value != NULL);
@@ -260,7 +268,8 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element,
                return 0;
        }
        p = s->package;
-       uci_add_history(ctx, p, UCI_CMD_CHANGE, section, option, value);
+       if (!internal)
+               uci_add_history(ctx, p, UCI_CMD_CHANGE, section, option, value);
 
        uci_list_del(&e->list);
        e = uci_realloc(ctx, e, size);
@@ -270,10 +279,10 @@ int uci_set_element_value(struct uci_context *ctx, struct uci_element **element,
 
        switch(e->type) {
        case UCI_TYPE_SECTION:
-               uci_to_section(e)->type = value;
+               uci_to_section(e)->type = str;
                break;
        case UCI_TYPE_OPTION:
-               uci_to_option(e)->value = value;
+               uci_to_option(e)->value = str;
                break;
        default:
                break;
@@ -289,6 +298,7 @@ int uci_set(struct uci_context *ctx, char *package, char *section, char *option,
        struct uci_section *s = NULL;
        struct uci_option *o = NULL;
        struct uci_history *h;
+       bool internal = ctx->internal;
 
        UCI_HANDLE_ERR(ctx);
        UCI_ASSERT(ctx, package != NULL);
@@ -326,7 +336,11 @@ int uci_set(struct uci_context *ctx, char *package, char *section, char *option,
        else
                e = &s->e;
 
-       return uci_set_element_value(ctx, &e, value);
+       if (!internal)
+               return uci_set_element_value(ctx, &e, value);
+
+       UCI_INTERNAL(uci_set_element_value, ctx, &e, value);
+       return 0;
 
 notfound:
        /* 
@@ -339,7 +353,8 @@ notfound:
                UCI_THROW(ctx, UCI_ERR_NOTFOUND);
 
        /* now add the missing entry */
-       uci_add_history(ctx, p, UCI_CMD_ADD, section, option, value);
+       if (!internal)
+               uci_add_history(ctx, p, UCI_CMD_ADD, section, option, value);
        if (s)
                uci_alloc_option(s, option, value);
        else
diff --git a/uci.h b/uci.h
index 0cb352d..891fb3b 100644 (file)
--- a/uci.h
+++ b/uci.h
@@ -34,6 +34,7 @@
 #include <stdio.h>
 
 #define UCI_CONFDIR "/etc/config"
+#define UCI_SAVEDIR "/tmp/.uci"
 
 enum
 {
@@ -174,6 +175,13 @@ extern int uci_set_element_value(struct uci_context *ctx, struct uci_element **e
  */
 extern int uci_set(struct uci_context *ctx, char *package, char *section, char *option, char *value);
 
+/**
+ * uci_save: save change history for a package
+ * @ctx: uci context
+ * @p: uci_package struct
+ */
+extern int uci_save(struct uci_context *ctx, struct uci_package *p);
+
 /**
  * uci_commit: commit changes to a package
  * @ctx: uci context
@@ -190,9 +198,10 @@ extern char **uci_list_configs(struct uci_context *ctx);
 
 /* UCI data structures */
 enum uci_type {
-       UCI_TYPE_PACKAGE = 0,
-       UCI_TYPE_SECTION = 1,
-       UCI_TYPE_OPTION = 2
+       UCI_TYPE_HISTORY = 0,
+       UCI_TYPE_PACKAGE = 1,
+       UCI_TYPE_SECTION = 2,
+       UCI_TYPE_OPTION = 3
 };
 
 struct uci_element
@@ -214,6 +223,7 @@ struct uci_context
        int errno;
        const char *func;
        jmp_buf trap;
+       bool internal;
        char *buf;
        int bufsz;
 };
@@ -270,10 +280,9 @@ enum uci_command {
 
 struct uci_history
 {
-       struct uci_list list;
+       struct uci_element e;
        enum uci_command cmd;
        char *section;
-       char *option;
        char *value;
 };
 
@@ -342,6 +351,7 @@ struct uci_history
 #define uci_list_empty(list) ((list)->next == (list))
 
 /* wrappers for dynamic type handling */
+#define uci_type_history UCI_TYPE_HISTORY
 #define uci_type_package UCI_TYPE_PACKAGE
 #define uci_type_section UCI_TYPE_SECTION
 #define uci_type_option UCI_TYPE_OPTION
@@ -349,9 +359,10 @@ struct uci_history
 /* element typecasting */
 #ifdef UCI_DEBUG_TYPECAST
 static const char *uci_typestr[] = {
+       [uci_type_history] = "history",
        [uci_type_package] = "package",
        [uci_type_section] = "section",
-       [uci_type_option] = "option"
+       [uci_type_option] = "option",
 };
 
 static void uci_typecast_error(int from, int to)
@@ -368,11 +379,13 @@ static void uci_typecast_error(int from, int to)
                return (struct uci_ ## _type *) e; \
        }
 
+BUILD_CAST(history)
 BUILD_CAST(package)
 BUILD_CAST(section)
 BUILD_CAST(option)
 
 #else
+#define uci_to_history(ptr) container_of(ptr, struct uci_history, e)
 #define uci_to_package(ptr) container_of(ptr, struct uci_package, e)
 #define uci_to_section(ptr) container_of(ptr, struct uci_section, e)
 #define uci_to_option(ptr)  container_of(ptr, struct uci_option, e)