file: make uci commits atomic
[project/uci.git] / file.c
diff --git a/file.c b/file.c
index 3cd0133..f7061df 100644 (file)
--- a/file.c
+++ b/file.c
@@ -18,6 +18,7 @@
 
 #define _GNU_SOURCE
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <sys/file.h>
 #include <stdbool.h>
 #include <unistd.h>
@@ -60,7 +61,10 @@ __private void uci_getln(struct uci_context *ctx, int offset)
                ofs += strlen(p);
                if (pctx->buf[ofs - 1] == '\n') {
                        pctx->line++;
-                       pctx->buf[ofs - 1] = 0;
+                       if (ofs >= 2 && pctx->buf[ofs - 2] == '\r')
+                               pctx->buf[ofs - 2] = 0;
+                       else
+                               pctx->buf[ofs - 1] = 0;
                        return;
                }
 
@@ -200,7 +204,7 @@ static void parse_str(struct uci_context *ctx, char **str, char **target)
        } while (**str && !isspace(**str));
 done:
 
-       /* 
+       /*
         * if the string was unquoted and we've stopped at a whitespace
         * character, skip to the next one, because the whitespace will
         * be overwritten by a null byte here
@@ -316,7 +320,7 @@ static void assert_eol(struct uci_context *ctx, char **str)
                uci_parse_error(ctx, *str, "too many arguments");
 }
 
-/* 
+/*
  * switch to a different config, either triggered by uci_load, or by a
  * 'package <...>' statement in the import file
  */
@@ -341,7 +345,7 @@ static void uci_switch_config(struct uci_context *ctx)
        if (!name)
                return;
 
-       /* 
+       /*
         * if an older config under the same name exists, unload it
         * ignore errors here, e.g. if the config was not found
         */
@@ -561,10 +565,10 @@ static void uci_export_package(struct uci_package *p, FILE *stream, bool header)
        struct uci_element *s, *o, *i;
 
        if (header)
-               fprintf(stream, "package '%s'\n", uci_escape(ctx, p->e.name));
+               fprintf(stream, "package %s\n", uci_escape(ctx, p->e.name));
        uci_foreach_element(&p->sections, s) {
                struct uci_section *sec = uci_to_section(s);
-               fprintf(stream, "\nconfig '%s'", uci_escape(ctx, sec->type));
+               fprintf(stream, "\nconfig %s", uci_escape(ctx, sec->type));
                if (!sec->anonymous || (ctx->flags & UCI_FLAG_EXPORT_NAME))
                        fprintf(stream, " '%s'", uci_escape(ctx, sec->e.name));
                fprintf(stream, "\n");
@@ -572,12 +576,12 @@ static void uci_export_package(struct uci_package *p, FILE *stream, bool header)
                        struct uci_option *opt = uci_to_option(o);
                        switch(opt->type) {
                        case UCI_TYPE_STRING:
-                               fprintf(stream, "\toption '%s'", uci_escape(ctx, opt->e.name));
+                               fprintf(stream, "\toption %s", uci_escape(ctx, opt->e.name));
                                fprintf(stream, " '%s'\n", uci_escape(ctx, opt->v.string));
                                break;
                        case UCI_TYPE_LIST:
                                uci_foreach_element(&opt->v.list, i) {
-                                       fprintf(stream, "\tlist '%s'", uci_escape(ctx, opt->e.name));
+                                       fprintf(stream, "\tlist %s", uci_escape(ctx, opt->e.name));
                                        fprintf(stream, " '%s'\n", uci_escape(ctx, i->name));
                                }
                                break;
@@ -681,9 +685,12 @@ static char *uci_config_path(struct uci_context *ctx, const char *name)
 static void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool overwrite)
 {
        struct uci_package *p = *package;
-       FILE *f = NULL;
+       FILE *f1, *f2 = NULL;
        char *name = NULL;
        char *path = NULL;
+       char *filename = NULL;
+       struct stat statbuf;
+       bool do_rename = false;
 
        if (!p->path) {
                if (overwrite)
@@ -692,8 +699,18 @@ static void uci_file_commit(struct uci_context *ctx, struct uci_package **packag
                        UCI_THROW(ctx, UCI_ERR_INVAL);
        }
 
+       if ((asprintf(&filename, "%s/.%s.uci-XXXXXX", ctx->confdir, p->e.name) < 0) || !filename)
+               UCI_THROW(ctx, UCI_ERR_MEM);
+
+       mktemp(filename);
+       if (!*filename)
+               UCI_THROW(ctx, UCI_ERR_IO);
+
+       if ((stat(filename, &statbuf) == 0) && ((statbuf.st_mode & S_IFMT) != S_IFREG))
+               UCI_THROW(ctx, UCI_ERR_IO);
+
        /* open the config file for writing now, so that it is locked */
-       f = uci_open_stream(ctx, p->path, SEEK_SET, true, true);
+       f1 = uci_open_stream(ctx, p->path, SEEK_SET, false, false);
 
        /* flush unsaved changes and reload from delta file */
        UCI_TRAP_SAVE(ctx, done);
@@ -705,13 +722,13 @@ static void uci_file_commit(struct uci_context *ctx, struct uci_package **packag
                        if (!uci_list_empty(&p->delta))
                                UCI_INTERNAL(uci_save, ctx, p);
 
-                       /* 
-                        * other processes might have modified the config 
-                        * as well. dump and reload 
+                       /*
+                        * other processes might have modified the config
+                        * as well. dump and reload
                         */
                        uci_free_package(&p);
                        uci_cleanup(ctx);
-                       UCI_INTERNAL(uci_import, ctx, f, name, &p, true);
+                       UCI_INTERNAL(uci_import, ctx, f1, name, &p, true);
 
                        p->path = path;
                        p->has_delta = true;
@@ -726,11 +743,15 @@ static void uci_file_commit(struct uci_context *ctx, struct uci_package **packag
                        goto done;
        }
 
-       rewind(f);
-       if (ftruncate(fileno(f), 0) < 0)
-               UCI_THROW(ctx, UCI_ERR_IO);
+       f2 = uci_open_stream(ctx, filename, SEEK_SET, true, true);
+       uci_export(ctx, f2, p, false);
+
+       fflush(f2);
+       fsync(fileno(f2));
+       uci_close_stream(f2);
+
+       do_rename = true;
 
-       uci_export(ctx, f, p, false);
        UCI_TRAP_RESTORE(ctx);
 
 done:
@@ -738,13 +759,19 @@ done:
                free(name);
        if (path)
                free(path);
-       uci_close_stream(f);
+       uci_close_stream(f1);
+       if (do_rename && rename(filename, p->path)) {
+               unlink(filename);
+               UCI_THROW(ctx, UCI_ERR_IO);
+       }
+       free(filename);
+       sync();
        if (ctx->err)
                UCI_THROW(ctx, ctx->err);
 }
 
 
-/* 
+/*
  * This function returns the filename by returning the string
  * after the last '/' character. By checking for a non-'\0'
  * character afterwards, directories are ignored (glob marks
@@ -834,9 +861,9 @@ static struct uci_package *uci_file_load(struct uci_context *ctx, const char *na
                break;
        }
 
+       UCI_TRAP_SAVE(ctx, done);
        file = uci_open_stream(ctx, filename, SEEK_SET, false, false);
        ctx->err = 0;
-       UCI_TRAP_SAVE(ctx, done);
        UCI_INTERNAL(uci_import, ctx, file, name, &package, true);
        UCI_TRAP_RESTORE(ctx);
 
@@ -848,8 +875,10 @@ static struct uci_package *uci_file_load(struct uci_context *ctx, const char *na
 
 done:
        uci_close_stream(file);
-       if (ctx->err)
+       if (ctx->err) {
+               free(filename);
                UCI_THROW(ctx, ctx->err);
+       }
        return package;
 }