Avoids issues with UBIFS file system.
http://www.linux-mtd.infradead.org/faq/ubifs.html#L_atomic_change
Signed-off-by: Luka Perkov <luka@openwrt.org>
Reported-by: Tim Harvey <tharvey@gateworks.com>
#define _GNU_SOURCE
#include <sys/types.h>
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/file.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/file.h>
#include <stdbool.h>
#include <unistd.h>
} while (**str && !isspace(**str));
done:
} 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
* 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
uci_parse_error(ctx, *str, "too many arguments");
}
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
*/
* switch to a different config, either triggered by uci_load, or by a
* 'package <...>' statement in the import file
*/
* if an older config under the same name exists, unload it
* ignore errors here, e.g. if the config was not found
*/
* if an older config under the same name exists, unload it
* ignore errors here, e.g. if the config was not found
*/
static void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool overwrite)
{
struct uci_package *p = *package;
static void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool overwrite)
{
struct uci_package *p = *package;
char *name = NULL;
char *path = NULL;
char *name = NULL;
char *path = NULL;
+ char *filename = NULL;
+ struct stat statbuf;
+ bool do_rename = false;
if (!p->path) {
if (overwrite)
if (!p->path) {
if (overwrite)
UCI_THROW(ctx, UCI_ERR_INVAL);
}
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 */
/* 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);
/* flush unsaved changes and reload from delta file */
UCI_TRAP_SAVE(ctx, done);
if (!uci_list_empty(&p->delta))
UCI_INTERNAL(uci_save, ctx, p);
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_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;
p->path = path;
p->has_delta = true;
- 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:
UCI_TRAP_RESTORE(ctx);
done:
free(name);
if (path)
free(path);
free(name);
if (path)
free(path);
+ 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);
}
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
* 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
if ((asprintf(&filename2, "%s/%s", ctx->confdir, name) < 0) || !filename2) {
UCI_THROW(ctx, UCI_ERR_MEM);
} else {
if ((asprintf(&filename2, "%s/%s", ctx->confdir, name) < 0) || !filename2) {
UCI_THROW(ctx, UCI_ERR_MEM);
} else {
- if (stat(filename2,&statbuf) == 0)
+ if (stat(filename2, &statbuf) == 0)
mode = statbuf.st_mode;
free(filename2);
mode = statbuf.st_mode;
free(filename2);
}
if (!write && ((stat(filename, &statbuf) < 0) ||
}
if (!write && ((stat(filename, &statbuf) < 0) ||
- ((statbuf.st_mode & S_IFMT) != S_IFREG))) {
+ ((statbuf.st_mode & S_IFMT) != S_IFREG))) {
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
}
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
}