X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fuci.git;a=blobdiff_plain;f=history.c;h=a31dbfaf1cb23d5ed196e4f6cbc66daf0a4ea042;hp=5b652b92f63ffc00e5f9b20ebaae24879674ae01;hb=c7430fd3578af03d9fe998f3195756603295ce2f;hpb=818f7b8433ae369c20fc56cccbb55b22043b9024 diff --git a/history.c b/history.c index 5b652b9..a31dbfa 100644 --- a/history.c +++ b/history.c @@ -26,6 +26,42 @@ #include #include +/* record a change that was done to a package */ +void +uci_add_history(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const char *value) +{ + struct uci_history *h; + int size = strlen(section) + 1; + char *ptr; + + if (value) + size += strlen(value) + 1; + + h = uci_alloc_element(ctx, history, option, size); + ptr = uci_dataptr(h); + h->cmd = cmd; + h->section = strcpy(ptr, section); + if (value) { + ptr += strlen(ptr) + 1; + h->value = strcpy(ptr, value); + } + uci_list_add(list, &h->e.list); +} + +void +uci_free_history(struct uci_history *h) +{ + if (!h) + return; + if ((h->section != NULL) && + (h->section != uci_dataptr(h))) { + free(h->section); + free(h->value); + } + uci_free_element(&h->e); +} + + int uci_set_savedir(struct uci_context *ctx, const char *dir) { char *sdir; @@ -52,51 +88,96 @@ int uci_add_history_path(struct uci_context *ctx, const char *dir) return 0; } -static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf) +static inline int uci_parse_history_tuple(struct uci_context *ctx, char **buf, struct uci_ptr *ptr) { - bool delete = false; - bool rename = false; - char *package = NULL; - char *section = NULL; - char *option = NULL; - char *value = NULL; - - if (buf[0] == '-') { - delete = true; - buf++; - } else if (buf[0] == '@') { - rename = true; - buf++; + int c = UCI_CMD_CHANGE; + + switch(**buf) { + case '-': + c = UCI_CMD_REMOVE; + break; + case '@': + c = UCI_CMD_RENAME; + break; + case '+': + /* UCI_CMD_ADD is used for anonymous sections or list values */ + c = UCI_CMD_ADD; + break; + case '|': + c = UCI_CMD_LIST_ADD; + break; } - UCI_INTERNAL(uci_parse_tuple, ctx, buf, &package, §ion, &option, &value); - if (!package || (strcmp(package, p->e.name) != 0)) - goto error; - if (!uci_validate_name(section)) - goto error; - if (option && !uci_validate_name(option)) + if (c != UCI_CMD_CHANGE) + *buf += 1; + + UCI_INTERNAL(uci_parse_ptr, ctx, ptr, *buf); + + if (!ptr->section) goto error; - if ((rename || !delete) && !uci_validate_name(value)) + if (ptr->flags & UCI_LOOKUP_EXTENDED) goto error; - if (rename) - UCI_INTERNAL(uci_rename, ctx, p, section, option, value); - else if (delete) - UCI_INTERNAL(uci_delete, ctx, p, section, option); - else - UCI_INTERNAL(uci_set, ctx, p, section, option, value); + switch(c) { + case UCI_CMD_RENAME: + if (!ptr->value || !uci_validate_name(ptr->value)) + goto error; + break; + case UCI_CMD_LIST_ADD: + if (!ptr->option) + goto error; + } + + return c; +error: + UCI_THROW(ctx, UCI_ERR_INVAL); + return 0; +} + +static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf) +{ + struct uci_element *e = NULL; + struct uci_ptr ptr; + int cmd; + + cmd = uci_parse_history_tuple(ctx, &buf, &ptr); + if (strcmp(ptr.package, p->e.name) != 0) + goto error; + + if (ctx->flags & UCI_FLAG_SAVED_HISTORY) + uci_add_history(ctx, &p->saved_history, cmd, ptr.section, ptr.option, ptr.value); + + switch(cmd) { + case UCI_CMD_RENAME: + UCI_INTERNAL(uci_rename, ctx, &ptr); + break; + case UCI_CMD_REMOVE: + UCI_INTERNAL(uci_delete, ctx, &ptr); + break; + case UCI_CMD_LIST_ADD: + UCI_INTERNAL(uci_add_list, ctx, &ptr); + break; + case UCI_CMD_ADD: + case UCI_CMD_CHANGE: + UCI_INTERNAL(uci_set, ctx, &ptr); + e = ptr.last; + if (!ptr.option && e && (cmd == UCI_CMD_ADD)) + uci_to_section(e)->anonymous = true; + break; + } return; error: UCI_THROW(ctx, UCI_ERR_PARSE); } -static void uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p) +/* returns the number of changes that were successfully parsed */ +static int uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p) { struct uci_parse_context *pctx; + int changes = 0; /* make sure no memory from previous parse attempts is leaked */ - ctx->internal = true; uci_cleanup(ctx); pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context)); @@ -115,39 +196,45 @@ static void uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_ UCI_TRAP_SAVE(ctx, error); uci_parse_history_line(ctx, p, pctx->buf); UCI_TRAP_RESTORE(ctx); + changes++; error: continue; } /* no error happened, we can get rid of the parser context now */ - ctx->internal = true; uci_cleanup(ctx); + return changes; } -static void uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush) +/* returns the number of changes that were successfully parsed */ +static int uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush) { FILE *stream = NULL; + int changes = 0; UCI_TRAP_SAVE(ctx, done); stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false); if (p) - uci_parse_history(ctx, stream, p); + changes = uci_parse_history(ctx, stream, p); UCI_TRAP_RESTORE(ctx); done: if (f) *f = stream; else if (stream) uci_close_stream(stream); + return changes; } -static void uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush) +/* returns the number of changes that were successfully parsed */ +static int uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush) { struct uci_element *e; char *filename = NULL; FILE *f = NULL; + int changes = 0; - if (!p->confdir) - return; + if (!p->has_history) + return 0; uci_foreach_element(&ctx->history_path, e) { if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename) @@ -160,15 +247,129 @@ static void uci_load_history(struct uci_context *ctx, struct uci_package *p, boo if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename) UCI_THROW(ctx, UCI_ERR_MEM); - uci_load_history_file(ctx, p, filename, &f, flush); - if (flush && f) { + changes = uci_load_history_file(ctx, p, filename, &f, flush); + if (flush && f && (changes > 0)) { rewind(f); ftruncate(fileno(f), 0); } if (filename) free(filename); uci_close_stream(f); - ctx->errno = 0; + ctx->err = 0; + return changes; +} + +static void uci_filter_history(struct uci_context *ctx, const char *name, const char *section, const char *option) +{ + struct uci_parse_context *pctx; + struct uci_element *e, *tmp; + struct uci_list list; + char *filename = NULL; + struct uci_ptr ptr; + FILE *f = NULL; + + uci_list_init(&list); + uci_alloc_parse_context(ctx); + pctx = ctx->pctx; + + if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename) + UCI_THROW(ctx, UCI_ERR_MEM); + + UCI_TRAP_SAVE(ctx, done); + f = uci_open_stream(ctx, filename, SEEK_SET, true, false); + pctx->file = f; + while (!feof(f)) { + struct uci_element *e; + char *buf; + + uci_getln(ctx, 0); + buf = pctx->buf; + if (!buf[0]) + continue; + + /* NB: need to allocate the element before the call to + * uci_parse_history_tuple, otherwise the original string + * gets modified before it is saved */ + e = uci_alloc_generic(ctx, UCI_TYPE_HISTORY, pctx->buf, sizeof(struct uci_element)); + uci_list_add(&list, &e->list); + + uci_parse_history_tuple(ctx, &buf, &ptr); + if (section) { + if (!ptr.section || (strcmp(section, ptr.section) != 0)) + continue; + } + if (option) { + if (!ptr.option || (strcmp(option, ptr.option) != 0)) + continue; + } + /* match, drop this element again */ + uci_free_element(e); + } + + /* rebuild the history file */ + rewind(f); + ftruncate(fileno(f), 0); + uci_foreach_element_safe(&list, tmp, e) { + fprintf(f, "%s\n", e->name); + uci_free_element(e); + } + UCI_TRAP_RESTORE(ctx); + +done: + if (filename) + free(filename); + uci_close_stream(pctx->file); + uci_foreach_element_safe(&list, tmp, e) { + uci_free_element(e); + } + uci_cleanup(ctx); +} + +int uci_revert(struct uci_context *ctx, struct uci_ptr *ptr) +{ + char *package = NULL; + char *section = NULL; + char *option = NULL; + + UCI_HANDLE_ERR(ctx); + expand_ptr(ctx, ptr, false); + UCI_ASSERT(ctx, ptr->p->has_history); + + /* + * - flush unwritten changes + * - save the package name + * - unload the package + * - filter the history + * - reload the package + */ + UCI_TRAP_SAVE(ctx, error); + UCI_INTERNAL(uci_save, ctx, ptr->p); + + /* NB: need to clone package, section and option names, + * as they may get freed on uci_free_package() */ + package = uci_strdup(ctx, ptr->p->e.name); + if (ptr->section) + section = uci_strdup(ctx, ptr->section); + if (ptr->option) + option = uci_strdup(ctx, ptr->option); + + uci_free_package(&ptr->p); + uci_filter_history(ctx, package, section, option); + + UCI_INTERNAL(uci_load, ctx, package, &ptr->p); + UCI_TRAP_RESTORE(ctx); + ctx->err = 0; + +error: + if (package) + free(package); + if (section) + free(section); + if (option) + free(option); + if (ctx->err) + UCI_THROW(ctx, ctx->err); + return 0; } int uci_save(struct uci_context *ctx, struct uci_package *p) @@ -176,6 +377,7 @@ int uci_save(struct uci_context *ctx, struct uci_package *p) FILE *f = NULL; char *filename = NULL; struct uci_element *e, *tmp; + struct stat statbuf; UCI_HANDLE_ERR(ctx); UCI_ASSERT(ctx, p != NULL); @@ -186,29 +388,47 @@ int uci_save(struct uci_context *ctx, struct uci_package *p) * directly. * does not modify the uci_package pointer */ - if (!p->confdir) + if (!p->has_history) return uci_commit(ctx, &p, false); if (uci_list_empty(&p->history)) return 0; + if (stat(ctx->savedir, &statbuf) < 0) + mkdir(ctx->savedir, UCI_DIRMODE); + else if ((statbuf.st_mode & S_IFMT) != S_IFDIR) + UCI_THROW(ctx, UCI_ERR_IO); + if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename) UCI_THROW(ctx, UCI_ERR_MEM); - ctx->errno = 0; + ctx->err = 0; UCI_TRAP_SAVE(ctx, done); f = uci_open_stream(ctx, filename, SEEK_END, true, 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, "-"); - else if (h->cmd == UCI_CMD_RENAME) - fprintf(f, "@"); - - fprintf(f, "%s.%s", p->e.name, h->section); + char *prefix = ""; + + switch(h->cmd) { + case UCI_CMD_REMOVE: + prefix = "-"; + break; + case UCI_CMD_RENAME: + prefix = "@"; + break; + case UCI_CMD_ADD: + prefix = "+"; + break; + case UCI_CMD_LIST_ADD: + prefix = "|"; + break; + default: + break; + } + + fprintf(f, "%s%s.%s", prefix, p->e.name, h->section); if (e->name) fprintf(f, ".%s", e->name); @@ -223,8 +443,8 @@ done: uci_close_stream(f); if (filename) free(filename); - if (ctx->errno) - UCI_THROW(ctx, ctx->errno); + if (ctx->err) + UCI_THROW(ctx, ctx->err); return 0; }