From: Felix Fietkau Date: Sun, 3 Feb 2008 04:10:21 +0000 (+0100) Subject: move history code into history.c X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fuci.git;a=commitdiff_plain;h=818f7b8433ae369c20fc56cccbb55b22043b9024 move history code into history.c --- diff --git a/Makefile b/Makefile index 989f890..db690e7 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,8 @@ else endif SHLIB_FILE=libuci.$(SHLIB_EXT).$(VERSION) +LIBUCI_DEPS=file.c history.c list.c util.c err.h uci.h + all: uci-static uci libuci.$(SHLIB_EXT) cli.o: cli.c uci.h @@ -37,10 +39,10 @@ uci: cli.o libuci.$(SHLIB_EXT) uci-static: cli.o libuci.a $(CC) $(CFLAGS) -o $@ $^ -libuci-static.o: libuci.c file.c uci.h list.c err.h util.c +libuci-static.o: libuci.c $(LIBUCI_DEPS) $(CC) $(CFLAGS) -c -o $@ $< -libuci-shared.o: libuci.c file.c uci.h list.c err.h util.c +libuci-shared.o: libuci.c $(LIBUCI_DEPS) $(CC) $(CFLAGS) $(FPIC) -c -o $@ $< libuci.a: libuci-static.o diff --git a/file.c b/file.c index 152d175..d81bbe8 100644 --- a/file.c +++ b/file.c @@ -18,7 +18,6 @@ #define _GNU_SOURCE #include -#include #include #include #include @@ -26,56 +25,6 @@ #include #include -#define LINEBUF 32 -#define LINEBUF_MAX 4096 - -static void uci_parse_error(struct uci_context *ctx, char *pos, char *reason) -{ - struct uci_parse_context *pctx = ctx->pctx; - - pctx->reason = reason; - pctx->byte = pos - pctx->buf; - UCI_THROW(ctx, UCI_ERR_PARSE); -} - -/* - * Fetch a new line from the input stream and resize buffer if necessary - */ -static void uci_getln(struct uci_context *ctx, int offset) -{ - struct uci_parse_context *pctx = ctx->pctx; - char *p; - int ofs; - - if (pctx->buf == NULL) { - pctx->buf = uci_malloc(ctx, LINEBUF); - pctx->bufsz = LINEBUF; - } - - ofs = offset; - do { - p = &pctx->buf[ofs]; - p[ofs] = 0; - - p = fgets(p, pctx->bufsz - ofs, pctx->file); - if (!p || !*p) - return; - - ofs += strlen(p); - if (pctx->buf[ofs - 1] == '\n') { - pctx->line++; - pctx->buf[ofs - 1] = 0; - return; - } - - if (pctx->bufsz > LINEBUF_MAX/2) - uci_parse_error(ctx, p, "line too long"); - - pctx->bufsz *= 2; - pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz); - } while (1); -} - /* * Clean up all extra memory used by the parser and exporter */ @@ -103,6 +52,7 @@ static void uci_file_cleanup(struct uci_context *ctx) free(pctx); } + /* * parse a character escaped by '\' * returns true if the escaped character is to be parsed @@ -619,178 +569,6 @@ error: 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, bool create) -{ - struct stat statbuf; - FILE *file = NULL; - int fd, ret; - int mode = (write ? O_RDWR : O_RDONLY); - - if (create) - mode |= O_CREAT; - - if (!write && ((stat(filename, &statbuf) < 0) || - ((statbuf.st_mode & S_IFMT) != S_IFREG))) { - UCI_THROW(ctx, UCI_ERR_NOTFOUND); - } - - fd = open(filename, mode, UCI_FILEMODE); - if (fd <= 0) - goto error; - - if (flock(fd, (write ? LOCK_EX : LOCK_SH)) < 0) - goto error; - - 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; - 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++; - } - - 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)) - goto error; - if ((rename || !delete) && !uci_validate_name(value)) - 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); - - 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; - - while (!feof(pctx->file)) { - uci_getln(ctx, 0); - if (!pctx->buf[0]) - continue; - - /* - * ignore parse errors in single lines, we want to preserve as much - * history as possible - */ - UCI_TRAP_SAVE(ctx, error); - uci_parse_history_line(ctx, p, pctx->buf); - UCI_TRAP_RESTORE(ctx); -error: - continue; - } - - /* no error happened, we can get rid of the parser context now */ - uci_file_cleanup(ctx); -} - -static void uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush) -{ - FILE *stream = NULL; - - UCI_TRAP_SAVE(ctx, done); - stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false); - if (p) - uci_parse_history(ctx, stream, p); - UCI_TRAP_RESTORE(ctx); -done: - if (f) - *f = stream; - else if (stream) - uci_close_stream(stream); -} - -static void uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush) -{ - struct uci_element *e; - char *filename = NULL; - FILE *f = NULL; - - if (!p->confdir) - return; - - uci_foreach_element(&ctx->history_path, e) { - if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename) - UCI_THROW(ctx, UCI_ERR_MEM); - - uci_load_history_file(ctx, p, filename, NULL, false); - free(filename); - } - - 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) { - rewind(f); - ftruncate(fileno(f), 0); - } - if (filename) - free(filename); - uci_close_stream(f); - ctx->errno = 0; -} - static char *uci_config_path(struct uci_context *ctx, const char *name) { @@ -847,64 +625,6 @@ done: return ctx->errno; } -int uci_save(struct uci_context *ctx, struct uci_package *p) -{ - FILE *f = NULL; - char *filename = NULL; - struct uci_element *e, *tmp; - - UCI_HANDLE_ERR(ctx); - UCI_ASSERT(ctx, p != NULL); - - /* - * if the config file was outside of the /etc/config path, - * don't save the history to a file, update the real file - * directly. - * does not modify the uci_package pointer - */ - if (!p->confdir) - return uci_commit(ctx, &p, false); - - if (uci_list_empty(&p->history)) - return 0; - - if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename) - UCI_THROW(ctx, UCI_ERR_MEM); - - ctx->errno = 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); - if (e->name) - fprintf(f, ".%s", e->name); - - if (h->cmd == UCI_CMD_REMOVE) - fprintf(f, "\n"); - else - fprintf(f, "=%s\n", h->value); - uci_free_history(h); - } - -done: - 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 **package, bool overwrite) { struct uci_package *p; diff --git a/history.c b/history.c new file mode 100644 index 0000000..5b652b9 --- /dev/null +++ b/history.c @@ -0,0 +1,232 @@ +/* + * libuci - Library for the Unified Configuration Interface + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * This file contains the code for handling uci config history files + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +int uci_set_savedir(struct uci_context *ctx, const char *dir) +{ + char *sdir; + + UCI_HANDLE_ERR(ctx); + UCI_ASSERT(ctx, dir != NULL); + + sdir = uci_strdup(ctx, dir); + if (ctx->savedir != uci_savedir) + free(ctx->savedir); + ctx->savedir = sdir; + return 0; +} + +int uci_add_history_path(struct uci_context *ctx, const char *dir) +{ + struct uci_element *e; + + UCI_HANDLE_ERR(ctx); + UCI_ASSERT(ctx, dir != NULL); + e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element)); + uci_list_add(&ctx->history_path, &e->list); + + return 0; +} + +static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf) +{ + 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++; + } + + 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)) + goto error; + if ((rename || !delete) && !uci_validate_name(value)) + 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); + + 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 */ + ctx->internal = true; + uci_cleanup(ctx); + + pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context)); + ctx->pctx = pctx; + pctx->file = stream; + + while (!feof(pctx->file)) { + uci_getln(ctx, 0); + if (!pctx->buf[0]) + continue; + + /* + * ignore parse errors in single lines, we want to preserve as much + * history as possible + */ + UCI_TRAP_SAVE(ctx, error); + uci_parse_history_line(ctx, p, pctx->buf); + UCI_TRAP_RESTORE(ctx); +error: + continue; + } + + /* no error happened, we can get rid of the parser context now */ + ctx->internal = true; + uci_cleanup(ctx); +} + +static void uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush) +{ + FILE *stream = NULL; + + UCI_TRAP_SAVE(ctx, done); + stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false); + if (p) + uci_parse_history(ctx, stream, p); + UCI_TRAP_RESTORE(ctx); +done: + if (f) + *f = stream; + else if (stream) + uci_close_stream(stream); +} + +static void uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush) +{ + struct uci_element *e; + char *filename = NULL; + FILE *f = NULL; + + if (!p->confdir) + return; + + uci_foreach_element(&ctx->history_path, e) { + if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename) + UCI_THROW(ctx, UCI_ERR_MEM); + + uci_load_history_file(ctx, p, filename, NULL, false); + free(filename); + } + + 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) { + rewind(f); + ftruncate(fileno(f), 0); + } + if (filename) + free(filename); + uci_close_stream(f); + ctx->errno = 0; +} + +int uci_save(struct uci_context *ctx, struct uci_package *p) +{ + FILE *f = NULL; + char *filename = NULL; + struct uci_element *e, *tmp; + + UCI_HANDLE_ERR(ctx); + UCI_ASSERT(ctx, p != NULL); + + /* + * if the config file was outside of the /etc/config path, + * don't save the history to a file, update the real file + * directly. + * does not modify the uci_package pointer + */ + if (!p->confdir) + return uci_commit(ctx, &p, false); + + if (uci_list_empty(&p->history)) + return 0; + + if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename) + UCI_THROW(ctx, UCI_ERR_MEM); + + ctx->errno = 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); + if (e->name) + fprintf(f, ".%s", e->name); + + if (h->cmd == UCI_CMD_REMOVE) + fprintf(f, "\n"); + else + fprintf(f, "=%s\n", h->value); + uci_free_history(h); + } + +done: + uci_close_stream(f); + if (filename) + free(filename); + if (ctx->errno) + UCI_THROW(ctx, ctx->errno); + + return 0; +} + + diff --git a/libuci.c b/libuci.c index 569a676..fd8f30c 100644 --- a/libuci.c +++ b/libuci.c @@ -41,6 +41,7 @@ static const char *uci_errstr[] = { #include "util.c" #include "list.c" +#include "history.c" #include "file.c" /* exported functions */ @@ -85,18 +86,6 @@ ignore: return; } -int uci_add_history_path(struct uci_context *ctx, const char *dir) -{ - struct uci_element *e; - - UCI_HANDLE_ERR(ctx); - UCI_ASSERT(ctx, dir != NULL); - e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element)); - uci_list_add(&ctx->history_path, &e->list); - - return 0; -} - int uci_set_confdir(struct uci_context *ctx, const char *dir) { char *cdir; @@ -111,20 +100,6 @@ int uci_set_confdir(struct uci_context *ctx, const char *dir) return 0; } -int uci_set_savedir(struct uci_context *ctx, const char *dir) -{ - char *sdir; - - UCI_HANDLE_ERR(ctx); - UCI_ASSERT(ctx, dir != NULL); - - sdir = uci_strdup(ctx, dir); - if (ctx->savedir != uci_savedir) - free(ctx->savedir); - ctx->savedir = sdir; - return 0; -} - int uci_cleanup(struct uci_context *ctx) { UCI_HANDLE_ERR(ctx); diff --git a/util.c b/util.c index c0f4451..e76ff6b 100644 --- a/util.c +++ b/util.c @@ -16,8 +16,16 @@ * This file contains wrappers to standard functions, which * throw exceptions upon failure. */ +#include +#include +#include #include +#include #include +#include + +#define LINEBUF 32 +#define LINEBUF_MAX 4096 static void *uci_malloc(struct uci_context *ctx, size_t size) { @@ -114,3 +122,108 @@ done: return 0; } + +static void uci_parse_error(struct uci_context *ctx, char *pos, char *reason) +{ + struct uci_parse_context *pctx = ctx->pctx; + + pctx->reason = reason; + pctx->byte = pos - pctx->buf; + UCI_THROW(ctx, UCI_ERR_PARSE); +} + + +/* + * Fetch a new line from the input stream and resize buffer if necessary + */ +static void uci_getln(struct uci_context *ctx, int offset) +{ + struct uci_parse_context *pctx = ctx->pctx; + char *p; + int ofs; + + if (pctx->buf == NULL) { + pctx->buf = uci_malloc(ctx, LINEBUF); + pctx->bufsz = LINEBUF; + } + + ofs = offset; + do { + p = &pctx->buf[ofs]; + p[ofs] = 0; + + p = fgets(p, pctx->bufsz - ofs, pctx->file); + if (!p || !*p) + return; + + ofs += strlen(p); + if (pctx->buf[ofs - 1] == '\n') { + pctx->line++; + pctx->buf[ofs - 1] = 0; + return; + } + + if (pctx->bufsz > LINEBUF_MAX/2) + uci_parse_error(ctx, p, "line too long"); + + pctx->bufsz *= 2; + pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz); + } while (1); +} + +/* + * 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, bool create) +{ + struct stat statbuf; + FILE *file = NULL; + int fd, ret; + int mode = (write ? O_RDWR : O_RDONLY); + + if (create) + mode |= O_CREAT; + + if (!write && ((stat(filename, &statbuf) < 0) || + ((statbuf.st_mode & S_IFMT) != S_IFREG))) { + UCI_THROW(ctx, UCI_ERR_NOTFOUND); + } + + fd = open(filename, mode, UCI_FILEMODE); + if (fd <= 0) + goto error; + + if (flock(fd, (write ? LOCK_EX : LOCK_SH)) < 0) + goto error; + + 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); +} + +