X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fuci.git;a=blobdiff_plain;f=file.c;h=d81bbe85995ce3cebc9c2e924698362537b009de;hp=95469c5f64e7b221621ee27b29fc2e45fb549bba;hb=818f7b8433ae369c20fc56cccbb55b22043b9024;hpb=1085fcb801f6e716b41d4c80942c7c802b5270b3 diff --git a/file.c b/file.c index 95469c5..d81bbe8 100644 --- a/file.c +++ b/file.c @@ -2,9 +2,9 @@ * 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 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 @@ -16,64 +16,15 @@ * This file contains the code for parsing uci config files */ +#define _GNU_SOURCE #include -#include +#include #include #include #include #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 */ @@ -101,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 @@ -208,6 +160,9 @@ static void parse_str(struct uci_context *ctx, char **str, char **target) case '"': parse_double_quote(ctx, str, target); break; + case '#': + **str = 0; + /* fall through */ case 0: goto done; case '\\': @@ -266,7 +221,7 @@ static void assert_eol(struct uci_context *ctx, char **str) char *tmp; tmp = next_arg(ctx, str, false, false); - if (tmp && *tmp) + if (tmp && *tmp && (ctx->flags & UCI_FLAG_STRICT)) uci_parse_error(ctx, *str, "too many arguments"); } @@ -448,7 +403,12 @@ static void uci_parse_line(struct uci_context *ctx, bool single) char *pbrk = NULL; word = strtok_r(word, " \t", &pbrk); + if (!word) + continue; + switch(word[0]) { + case '#': + return; case 'p': if ((word[1] == 0) || !strcmp(word + 1, "ackage")) uci_parse_package(ctx, &word, single); @@ -523,7 +483,7 @@ static void uci_export_package(struct uci_package *p, FILE *stream, bool header) uci_foreach_element(&p->sections, s) { struct uci_section *sec = uci_to_section(s); fprintf(stream, "\nconfig '%s'", uci_escape(ctx, sec->type)); - if (!sec->anonymous) + if (!sec->anonymous || (ctx->flags & UCI_FLAG_EXPORT_NAME)) fprintf(stream, " '%s'", uci_escape(ctx, sec->e.name)); fprintf(stream, "\n"); uci_foreach_element(&sec->options, o) { @@ -574,8 +534,10 @@ int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct u * the appropriate 'package ' string to specify the config name * NB: the config file can still override the package name */ - if (name) + if (name) { + UCI_ASSERT(ctx, uci_validate_name(name)); pctx->name = name; + } while (!feof(pctx->file)) { uci_getln(ctx, 0); @@ -607,167 +569,14 @@ 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 || !section || (!delete && !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 (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(struct uci_context *ctx, struct uci_package *p, bool flush) -{ - char *filename = NULL; - 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, flush, false); - if (p) - uci_parse_history(ctx, f, p); - UCI_TRAP_RESTORE(ctx); - -done: - 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) { char *filename; - 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); + UCI_ASSERT(ctx, uci_validate_name(name)); + filename = uci_malloc(ctx, strlen(name) + strlen(ctx->confdir) + 2); + sprintf(filename, "%s/%s", ctx->confdir, name); return filename; } @@ -779,7 +588,6 @@ int uci_load(struct uci_context *ctx, const char *name, struct uci_package **pac FILE *file = NULL; UCI_HANDLE_ERR(ctx); - UCI_ASSERT(ctx, uci_validate_name(name)); switch (name[0]) { case '.': @@ -817,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", UCI_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; @@ -904,8 +654,14 @@ int uci_commit(struct uci_context *ctx, struct uci_package **package, bool overw if (!overwrite) { name = uci_strdup(ctx, p->e.name); path = uci_strdup(ctx, p->path); + /* dump our own changes to the history file */ if (!uci_list_empty(&p->history)) UCI_INTERNAL(uci_save, ctx, p); + + /* + * other processes might have modified the config + * as well. dump and reload + */ uci_free_package(&p); uci_file_cleanup(ctx); UCI_INTERNAL(uci_import, ctx, f, name, &p, true); @@ -967,10 +723,13 @@ int uci_list_configs(struct uci_context *ctx, char ***list) glob_t globbuf; int size, i; char *buf; + char *dir; UCI_HANDLE_ERR(ctx); - if (glob(UCI_CONFDIR "/*", GLOB_MARK, NULL, &globbuf) != 0) + dir = uci_malloc(ctx, strlen(ctx->confdir) + 1 + sizeof("/*")); + sprintf(dir, "%s/*", ctx->confdir); + if (glob(dir, GLOB_MARK, NULL, &globbuf) != 0) UCI_THROW(ctx, UCI_ERR_NOTFOUND); size = sizeof(char *) * (globbuf.gl_pathc + 1); @@ -998,6 +757,7 @@ int uci_list_configs(struct uci_context *ctx, char ***list) buf += strlen(buf) + 1; } *list = configs; + free(dir); return 0; }