From 4aa3fd762e2190a4e418581cf24446484d0b7df5 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 30 Jan 2008 00:14:54 +0100 Subject: [PATCH] implement history parsing --- cli.c | 5 +- err.h | 12 +++- file.c | 220 +++++++++++++++++++++++++++++++++++++++++++++++---------- list.c | 49 ++++++++----- uci.h | 25 +++++-- 5 files changed, 246 insertions(+), 65 deletions(-) diff --git a/cli.c b/cli.c index 410d7e8..e17acb5 100644 --- 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 --- 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) @@ -40,10 +41,12 @@ * 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; \ @@ -70,6 +73,11 @@ 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 --- 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, §ion, &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 --- 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 --- a/uci.h +++ b/uci.h @@ -34,6 +34,7 @@ #include #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) -- 2.30.2