Emulate atomic transactions
authorJ. Ali Harlow <ali@juiblex.co.uk>
Thu, 9 Feb 2012 20:42:08 +0000 (20:42 +0000)
committerJ. Ali Harlow <ali@juiblex.co.uk>
Thu, 9 Feb 2012 20:42:08 +0000 (20:42 +0000)
configure.ac
librazor/Makefile.am
librazor/atomic-actions.c [new file with mode: 0644]
librazor/atomic-emulate.c [new file with mode: 0644]
librazor/atomic-ktm.c [new file with mode: 0644]
librazor/atomic-none.c [new file with mode: 0644]
librazor/atomic.c
librazor/razor-internal.h
librazor/razor.h
librazor/util.c

index ff0c83a..88911de 100644 (file)
@@ -33,6 +33,15 @@ AC_PROG_LN_S
 AC_SYS_LARGEFILE
 AM_PROG_CC_C_O
 
+AC_ARG_ENABLE([atomic],
+             [AS_HELP_STRING([--disable-atomic],
+                             [disable atomic transactions])],
+             [],
+             [enable_atomic=yes])
+if test "$enable_atomic" = "yes"; then
+    AC_DEFINE([ENABLE_ATOMIC],[1],[Define if atomic transactions are wanted.])
+fi
+
 AC_MSG_CHECKING([for Microsoft Windows native API])
 case $host_os in
     *mingw*)   AC_DEFINE([MSWIN_API], 1,
@@ -45,7 +54,7 @@ AC_MSG_RESULT([$mswin_api])
 AM_CONDITIONAL(MSWIN_API, test "$mswin_api" = "yes")
 AC_SUBST(EXTRA_LIBS)
 
-if test "$mswin_api" = "yes"; then
+if test "$enable_atomic" = "yes" -a "$mswin_api" = "yes"; then
     AC_MSG_CHECKING([for Microsoft Windows Kernel Transaction Manager])
     save_LIBS="$LIBS"
     LIBS="-lktmw32 $LIBS"
index bc5c469..7196484 100644 (file)
@@ -34,6 +34,10 @@ librazor_la_SOURCES =                                        \
        importer.c                                      \
        merger.c                                        \
        atomic.c                                        \
+       atomic-ktm.c                                    \
+       atomic-none.c                                   \
+       atomic-emulate.c                                \
+       atomic-actions.c                                \
        transaction.c
 
 if HAVE_LUA
diff --git a/librazor/atomic-actions.c b/librazor/atomic-actions.c
new file mode 100644 (file)
index 0000000..0b4662b
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2012  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#if ENABLE_ATOMIC && !HAVE_WINDOWS_KTM
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <assert.h>
+#include "razor-internal.h"
+
+char *atomic_action_attic_tmpnam(struct razor_atomic *atomic)
+{
+       char filename[17];
+       sprintf(filename,"%03X",atomic->next_file_tag++);
+       return razor_concat(atomic->toplevel, "/", filename, NULL);
+}
+
+static struct atomic_action *
+atomic_action_list_pop_head(struct atomic_action **list)
+{
+       struct atomic_action *head;
+
+       head = *list;
+       if (head) {
+               *list = head->next;
+               head->next = NULL;
+       }
+
+       return head;
+}
+
+struct atomic_action *
+atomic_action_list_prepend(struct atomic_action *list,
+                          struct atomic_action *action)
+{
+       assert(action->next == NULL);
+
+       action->next=list;
+
+       return action;
+}
+
+static struct atomic_action *
+atomic_action_list_concat(struct atomic_action *list1,
+                         struct atomic_action *list2)
+{
+       struct atomic_action *action;
+
+       if (!list1)
+               return list2;
+
+       for(action=list1;action->next;action=action->next)
+               ;
+
+       action->next=list2;
+
+       return list1;
+}
+
+void atomic_action_free(struct atomic_action *action)
+{
+       struct atomic_action *a;
+
+       while(action) {
+               a = atomic_action_list_pop_head(&action);
+
+               free(a->args.path);
+
+               switch(a->type) {
+               case ACTION_MAKE_DIRS:
+                       free(a->args.u.make_dirs.root);
+                       break;
+               case ACTION_MOVE:
+                       free(a->args.u.move.dest);
+                       break;
+#if HAVE_SYMLINK
+               case ACTION_CREATE_SYMLINK:
+                       free(a->args.u.create_symlink.target);
+                       break;
+#endif
+               case ACTION_REMOVE:
+               case ACTION_CREATE_DIR:
+                       break;
+               }
+
+               free(a);
+       }
+}
+
+struct atomic_action *atomic_action_new(enum atomic_action_type type)
+{
+       struct atomic_action *action;
+
+       action = zalloc(sizeof *action);
+       action->type = type;
+
+       return action;
+}
+
+struct atomic_action *atomic_action_list_reverse(struct atomic_action *list)
+{
+       struct atomic_action *prev = NULL, *next;
+
+       while(list) {
+               next = list->next;
+               list->next = prev;
+               prev = list;
+               list = next;
+       }
+
+       return prev;
+}
+
+/*
+ * All action_ functions take 1 action and return a list of primitive
+ * actions they took (such that the first action in the list is the
+ * last action they took). Primitive actions are always reversable.
+ *
+ * On failure, an error should be set on the atomic object (if it is
+ * not already set), the action freed and NULL returned.
+ *
+ * Whether they succeed or fail, they take ownership of the passed
+ * action and should thus either free it or return it back to their
+ * caller as appropriate.
+ *
+ * A NULL return means that nothing was done which may, or may not,
+ * indicate an error.
+ */
+
+static struct atomic_action *
+atomic_action_make_dirs(struct razor_atomic *atomic,
+                       struct atomic_action *action)
+{
+       char buffer[PATH_MAX], *p;
+       const char *slash, *next;
+       struct atomic_action *primitives = NULL;
+       struct atomic_action *prim;
+
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       strcpy(buffer, action->args.u.make_dirs.root);
+       p = buffer + strlen(buffer);
+       slash = action->args.path;
+       for (; *slash != '\0'; slash = next) {
+#ifdef MSWIN_API
+               next = strpbrk(slash + 1, "/\\");
+#else
+               next = strchr(slash + 1, '/');
+#endif
+               if (next == NULL)
+                       break;
+
+               memcpy(p, slash, next - slash);
+               p += next - slash;
+               *p = '\0';
+
+               if (razor_valid_root_name(buffer))
+                       continue;
+
+               prim = atomic_action_new(ACTION_CREATE_DIR);
+               prim->args.path = strdup(buffer);
+               prim->args.u.create_dir.mode = S_IRWXU | S_IRWXG | S_IRWXO;
+               primitives = atomic_action_list_prepend(primitives, prim);
+       }
+       primitives = atomic_action_list_reverse(primitives);
+
+       return atomic_action_do(atomic, primitives);
+}
+
+static struct atomic_action *
+atomic_action_remove(struct razor_atomic *atomic, struct atomic_action *action)
+{
+#ifdef MSWIN_API
+       wchar_t *path;
+       _WDIR *dir;
+#else
+       DIR *dir;
+#endif
+       struct atomic_action *prim;
+
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       /*
+        * Non-empty directories should NOT be removed
+        */
+#ifdef MSWIN_API
+       path = razor_utf8_to_utf16(action->args.path, -1);
+
+       dir = _wopendir(path);
+       if (dir && _wreaddir(dir)) {
+               atomic_action_free(action);
+               action = NULL;
+       }
+       _wclosedir(dir);
+#else
+       dir = opendir(action->args.path);
+       if (dir && readdir(dir)) {
+               atomic_action_free(action);
+               action = NULL;
+       }
+       closedir(dir);
+#endif
+
+       if (action) {
+               prim = atomic_action_new(ACTION_MOVE);
+               prim->args.path = strdup(action->args.path);
+               prim->args.u.move.dest = atomic_action_attic_tmpnam(atomic);
+
+               atomic_action_free(action);
+       } else
+               prim = NULL;
+
+       return prim ? atomic_action_do(atomic, prim) : NULL;
+}
+
+static struct atomic_action *
+atomic_action_create_dir(struct razor_atomic *atomic,
+                        struct atomic_action *action)
+{
+       mode_t mode;
+
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       mode = action->args.u.create_dir.mode & (S_IRWXU | S_IRWXG | S_IRWXO);
+
+       if (!mkdir(action->args.path, mode))
+               return 0;
+
+       if (errno != EEXIST || chmod(action->args.path, mode) < 0) {
+               if (!atomic->error_str)
+                       razor_atomic_set_error_str(atomic, action->args.path,
+                                                  strerror(errno));
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       return action;
+}
+
+static struct atomic_action *atomic_action_rmdir(struct razor_atomic *atomic,
+                                                struct atomic_action *action)
+{
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       if (rmdir(action->args.path) < 0) {
+               if (!atomic->error_str)
+                       razor_atomic_set_error_str(atomic, action->args.path,
+                                                  strerror(errno));
+               atomic_action_free(action);
+               return NULL;
+       } else
+               return action;
+}
+
+#if HAVE_SYMLINK
+static struct atomic_action *
+atomic_action_create_symlink(struct razor_atomic *atomic,
+                            struct atomic_action *action)
+{
+       int r;
+
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       r = symlink(action->args.u.create_symlink.target, action->args.path);
+       if (r < 0) {
+               if (!atomic->error_str)
+                       razor_atomic_set_error_str(atomic, NULL,
+                                                  strerror(errno));
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       return action;
+}
+
+static struct atomic_action *
+atomic_action_remove_symlink(struct razor_atomic *atomic,
+                            struct atomic_action *action)
+{
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       if (unlink(action->args.path) < 0) {
+               if (!atomic->error_str)
+                       razor_atomic_set_error_str(atomic, NULL,
+                                                  strerror(errno));
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       return action;
+}
+#endif
+
+static int
+move_file(struct razor_atomic *atomic, const char *path, const char *dest)
+{
+#ifdef MSWIN_API
+       wchar_t *oldbuf, *newbuf;
+       const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
+
+       newbuf = razor_utf8_to_utf16(dest, -1);
+       oldbuf = razor_utf8_to_utf16(path, -1);
+
+       /*
+        * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will
+        * cover every case we care about _except_ replacing an empty
+        * directory with a file. Calling RemoveDirectory() will deal
+        * with this case while having no effect in all other cases.
+        */
+       (void)RemoveDirectoryW(newbuf);
+
+       if (!MoveFileExW(oldbuf, newbuf, flags)) {
+               if (!atomic->error_str)
+                       razor_atomic_set_error_mswin(atomic, newbuf,
+                                                    GetLastError());
+               return -1;
+       }
+
+       free(newbuf);
+       free(oldbuf);
+#else
+       if (rename(path, dest)) {
+               if (!atomic->error_str)
+                       razor_atomic_set_error_str(atomic, dest,
+                                                  strerror(errno));
+               return -1;
+       }
+#endif
+
+       return 0;
+}
+
+static struct atomic_action *
+atomic_action_move(struct razor_atomic *atomic, struct atomic_action *action)
+{
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       if (move_file(atomic, action->args.path, action->args.u.move.dest)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       return action;
+}
+
+static struct atomic_action *
+atomic_action_unmove(struct razor_atomic *atomic, struct atomic_action *action)
+{
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       if (move_file(atomic, action->args.u.move.dest, action->args.path)) {
+               atomic_action_free(action);
+               return NULL;
+       }
+
+       return action;
+}
+
+static struct atomic_action *atomic_action_apply(struct razor_atomic *atomic,
+                                                struct atomic_action *action)
+{
+       switch(action->type) {
+       case ACTION_MAKE_DIRS:
+               action = atomic_action_make_dirs(atomic, action);
+               break;
+       case ACTION_REMOVE:
+               action = atomic_action_remove(atomic, action);
+               break;
+       case ACTION_CREATE_DIR:
+               action = atomic_action_create_dir(atomic, action);
+               break;
+       case ACTION_MOVE:
+               action = atomic_action_move(atomic, action);
+               break;
+#if HAVE_SYMLINK
+       case ACTION_CREATE_SYMLINK:
+               action = atomic_action_create_symlink(atomic, action);
+               break;
+#endif
+       }
+       return action;
+}
+
+static struct atomic_action *atomic_action_reverse(struct razor_atomic *atomic,
+                                                  struct atomic_action *action)
+{
+       switch(action->type) {
+       case ACTION_MAKE_DIRS:
+       case ACTION_REMOVE:
+               /* Complex actions: should never happen */
+               break;
+       case ACTION_CREATE_DIR:
+               action = atomic_action_rmdir(atomic, action);
+               break;
+       case ACTION_MOVE:
+               action = atomic_action_unmove(atomic, action);
+               break;
+#if HAVE_SYMLINK
+       case ACTION_CREATE_SYMLINK:
+               action = atomic_action_remove_symlink(atomic, action);
+               break;
+#endif
+       }
+       return action;
+}
+
+/*
+ * Note that undo has no error checking.
+ */
+
+void atomic_action_undo(struct razor_atomic *atomic,
+                       struct atomic_action *actions)
+{
+       struct atomic_action *a;
+
+       atomic->in_undo = 1;
+
+       while (actions) {
+               a = atomic_action_list_pop_head(&actions);
+               a = atomic_action_reverse(atomic, a);
+               atomic_action_free(a);
+       }
+
+       atomic->in_undo = 0;
+}
+
+struct atomic_action *atomic_action_do(struct razor_atomic *atomic,
+                                      struct atomic_action *actions)
+{
+       struct atomic_action *done = NULL, *a;
+
+       if (razor_atomic_in_error_state(atomic)) {
+               atomic_action_free(actions);
+               return NULL;
+       }
+
+       while (actions) {
+               a = atomic_action_list_pop_head(&actions);
+               a = atomic_action_apply(atomic, a);
+               if (a)
+                       done = atomic_action_list_concat(a, done);
+               if (razor_atomic_in_error_state(atomic)) {
+                       atomic_action_undo(atomic, done);
+                       done = NULL;
+                       atomic_action_free(actions);
+               }
+       }
+
+       return done;
+}
+
+#endif /* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */
diff --git a/librazor/atomic-emulate.c b/librazor/atomic-emulate.c
new file mode 100644 (file)
index 0000000..c8f88e0
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2012  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#if ENABLE_ATOMIC && !HAVE_WINDOWS_KTM
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include "razor-internal.h"
+
+/*
+ * Emulated atomic support
+ *
+ * This implementation is better than nothing, but is certainly not atomic.
+ * It does have a couple of advantages over atomic-none:
+ *     - If a file operation fails while a package is being installed we
+ *       have a good chance of being able to rollback the transaction to
+ *       a well-known state.
+ *     - We behave similarly to atomic-ktm in that changes are not visible
+ *       on disk to non-atomic operations (eg., scripts) until the atomic
+ *       is committed. This makes the testsuite more likely to pick up
+ *       problems that would otherwise only be found when using razor on
+ *       an MS-Windows system which supports KTM.
+ */
+
+#ifndef O_BINARY
+#define O_BINARY       0
+#endif
+
+static void recursive_remove(const char *directory)
+{
+       DIR *dp;
+       struct dirent *dirp;
+       char *buf;
+
+       dp = opendir(directory);
+       while((dirp = readdir(dp))) {
+               if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) {
+                       buf = malloc(strlen(directory) + strlen(dirp->d_name)
+                                    + 2);
+                       sprintf(buf, "%s/%s", directory, dirp->d_name);
+                       if (remove(buf) < 0)
+                               recursive_remove(buf);
+                       free(buf);
+               }
+       }
+
+       rmdir(directory);
+}
+
+RAZOR_EXPORT struct razor_atomic *razor_atomic_open(const char *description)
+{
+       struct razor_atomic *atomic;
+
+       atomic = zalloc(sizeof *atomic);
+
+       atomic->toplevel = strdup(".atomic-XXXXXX");
+       if (!mkdtemp(atomic->toplevel)) {
+               free(atomic->toplevel);
+               free(atomic);
+               return NULL;
+       }
+
+       atomic->description = strdup(description);
+
+       return atomic;
+}
+
+RAZOR_EXPORT int razor_atomic_commit(struct razor_atomic *atomic)
+{
+       struct atomic_action *actions;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       if (atomic->actions) {
+               actions = atomic_action_list_reverse(atomic->actions);
+               atomic->actions = NULL;
+               actions = atomic_action_do(atomic, actions);
+               atomic_action_free(actions);
+       }
+
+       if (atomic->toplevel) {
+               recursive_remove(atomic->toplevel);
+               free(atomic->toplevel);
+               atomic->toplevel = NULL;
+       }
+
+       return !!atomic->error_str;
+}
+
+RAZOR_EXPORT void razor_atomic_destroy(struct razor_atomic *atomic)
+{
+       if (atomic->toplevel) {
+               recursive_remove(atomic->toplevel);
+               free(atomic->toplevel);
+               atomic->toplevel = NULL;
+       }
+
+       free(atomic->error_path);
+       free(atomic->error_str);
+       free(atomic->error_msg);
+       free(atomic);
+}
+
+RAZOR_EXPORT int
+razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
+                      const char *path)
+{
+       struct atomic_action *a;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       a = atomic_action_new(ACTION_MAKE_DIRS);
+       a->args.path = strdup(path);
+       a->args.u.make_dirs.root = strdup(root);
+       atomic->actions = atomic_action_list_prepend(atomic->actions, a);
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_remove(struct razor_atomic *atomic, const char *path)
+{
+       struct atomic_action *a;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       a = atomic_action_new(ACTION_REMOVE);
+       a->args.path = strdup(path);
+       atomic->actions = atomic_action_list_prepend(atomic->actions, a);
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
+                        const char *newpath)
+{
+       struct atomic_action *a;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       a = atomic_action_new(ACTION_MOVE);
+       a->args.path = strdup(oldpath);
+       a->args.u.move.dest = strdup(newpath);
+       atomic->actions = atomic_action_list_prepend(atomic->actions, a);
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
+                       mode_t mode)
+{
+       struct atomic_action *a;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       a = atomic_action_new(ACTION_CREATE_DIR);
+       a->args.path = strdup(dirname);
+       a->args.u.create_dir.mode = mode;
+       atomic->actions = atomic_action_list_prepend(atomic->actions, a);
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
+                           const char *path)
+{
+#if HAVE_SYMLINK
+       struct atomic_action *a;
+#endif
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+#if HAVE_SYMLINK
+       a = atomic_action_new(ACTION_CREATE_SYMLINK);
+       a->args.path = strdup(path);
+       a->args.u.create_symlink.target = strdup(target);
+       atomic->actions = atomic_action_list_prepend(atomic->actions, a);
+
+       return 0;
+#else
+       razor_atomic_set_error_str(atomic, NULL, "Symbolic links not supported "
+                                                "on this platform");
+
+       return -1;
+#endif
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
+                         mode_t mode)
+{
+       int fd;
+       struct atomic_action *a;
+       char *tmpnam;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       atomic->error_path = strdup(filename);
+       tmpnam = atomic_action_attic_tmpnam(atomic);
+       fd = open(tmpnam, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                 mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+       if (fd == -1)
+               razor_atomic_set_error_str(atomic, NULL, strerror(errno));
+       else {
+               a = atomic_action_new(ACTION_MOVE);
+               a->args.path = tmpnam;
+               a->args.u.move.dest = strdup(filename);
+               atomic->actions = atomic_action_list_prepend(atomic->actions,
+                                                            a);
+       }
+
+       return fd;
+}
+
+#endif /* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */
diff --git a/librazor/atomic-ktm.c b/librazor/atomic-ktm.c
new file mode 100644 (file)
index 0000000..5b4b751
--- /dev/null
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2011-2012  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#if HAVE_WINDOWS_KTM
+
+#include <stdlib.h>
+#include <windows.h>
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+#include <wchar.h>
+#include <ktmw32.h>
+
+#include "razor.h"
+#include "razor-internal.h"
+
+#define RAZOR_ASCII_ISALPHA(c) \
+                       ((c) >= 'A' && (c) <= 'Z' || (c) >= 'a' && (c) <= 'z')
+
+static int
+razor_valid_root_name2(const wchar_t *name)
+{
+       if (razor_allow_all_root_names())
+               return !wcschr(name, '/');
+
+       return RAZOR_ASCII_ISALPHA(name[0]) && name[1] == ':' &&
+              name[2] == '\0';
+}
+
+struct razor_wstr {
+       wchar_t *str;
+       int len, allocated;
+};
+
+static struct razor_wstr *
+razor_wstr_create(const char *init, int len)
+{
+       int n;
+       struct razor_wstr *wstr;
+
+       wstr = malloc(sizeof(struct razor_wstr));
+
+       n = MultiByteToWideChar(CP_UTF8, 0, init, len, NULL, 0);
+       if (len >= 0 && init[len])
+               wstr->len = n++;
+       else
+               wstr->len = n - 1;
+
+       wstr->allocated = n * 2;
+       wstr->str = malloc(wstr->allocated * sizeof(wchar_t));
+       if (!wstr->str) {
+               free(wstr);
+               return NULL;
+       }
+
+       (void)MultiByteToWideChar(CP_UTF8, 0, init, len, wstr->str, n);
+       if (len >= 0 && init[len])
+               wstr->str[wstr->len] = 0;
+
+       return wstr;
+}
+
+static int
+razor_wstr_append(struct razor_wstr *wstr, const char *s, int len)
+{
+       int n, allocated;
+       wchar_t *str;
+
+       n = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0);
+       if (len < 0 || !s[len])
+               n--;
+
+       if (wstr->allocated <= wstr->len + n) {
+               allocated = (wstr->len + n + 1) * 2;
+               str = realloc(wstr->str, allocated * sizeof(wchar_t));
+               if (!str)
+                       return -1;
+               wstr->allocated = allocated;
+               wstr->str = str;
+       }
+
+       (void)MultiByteToWideChar(CP_UTF8, 0, s, len, wstr->str + wstr->len, n);
+       wstr->len += n;
+       wstr->str[wstr->len] = 0;
+
+       return 0;
+}
+
+static void
+razor_wstr_destroy(struct razor_wstr *wstr)
+{
+       free(wstr->str);
+       free(wstr);
+}
+
+RAZOR_EXPORT struct razor_atomic *
+razor_atomic_open(const char *description)
+{
+       wchar_t *buf;
+       struct razor_atomic *atomic;
+
+       atomic = zalloc(sizeof *atomic);
+       buf = razor_utf8_to_utf16(description, -1);
+       atomic->transaction = CreateTransaction(NULL, 0,
+                                               TRANSACTION_DO_NOT_PROMOTE,
+                                               0, 0, 0, buf);
+       free(buf);
+
+       return atomic;
+}
+
+void
+razor_atomic_set_error_str(struct razor_atomic *atomic, const wchar_t *path,
+                          const char *str)
+{
+       assert(!atomic->error_str);
+
+       free(atomic->error_path);
+
+       if (path)
+               atomic->error_path = razor_utf16_to_utf8(path, -1);
+       else
+               atomic->error_path = NULL;
+
+       atomic->error_str = strdup(str);
+}
+
+static void
+razor_atomic_set_error(struct razor_atomic *atomic, const wchar_t *path,
+                      DWORD error)
+{
+       wchar_t *buf;
+
+       assert(!atomic->error_str);
+
+       free(atomic->error_path);
+
+       if (path)
+               atomic->error_path = razor_utf16_to_utf8(path, -1);
+       else
+               atomic->error_path = NULL;
+
+       FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|
+                      FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
+                      NULL, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
+                      (LPWSTR)&buf, 0, NULL);
+       atomic->error_str = razor_utf16_to_utf8(buf, -1);
+       LocalFree(buf);
+}
+
+RAZOR_EXPORT int
+razor_atomic_commit(struct razor_atomic *atomic)
+{
+       int retval;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       retval = !CommitTransaction(atomic->transaction);
+
+       if (retval) {
+               razor_atomic_set_error(atomic, NULL, GetLastError());
+               RollbackTransaction(atomic->transaction);
+       }
+
+       CloseHandle(atomic->transaction);
+       atomic->transaction = INVALID_HANDLE_VALUE;
+
+       return retval;
+}
+
+RAZOR_EXPORT void
+razor_atomic_destroy(struct razor_atomic *atomic)
+{
+       int i;
+
+       for(i = 0; i < atomic->n_files; i++) {
+               if (atomic->files[i].h != INVALID_HANDLE_VALUE) {
+                       CloseHandle(atomic->files[i].h);
+                       free(atomic->files[i].path);
+               }
+       }
+       free(atomic->files);
+       if (atomic->transaction != INVALID_HANDLE_VALUE) {
+               RollbackTransaction(atomic->transaction);
+               CloseHandle(atomic->transaction);
+       }
+       free(atomic->error_path);
+       free(atomic->error_str);
+       free(atomic->error_msg);
+       free(atomic);
+}
+
+RAZOR_EXPORT int
+razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
+                      const char *path)
+{
+       struct razor_wstr *buffer;
+       const char *slash, *next;
+       WIN32_FILE_ATTRIBUTE_DATA fa;
+       DWORD err;
+       int r, creating = 0;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       buffer = razor_wstr_create(root, -1);
+       slash = path;
+
+       for (; *slash != '\0'; slash = next) {
+               next = strpbrk(slash + 1, "/\\");
+               if (next == NULL)
+                       break;
+
+               razor_wstr_append(buffer, slash, next - slash);
+
+               if (!creating) {
+                       if (razor_valid_root_name2(buffer->str))
+                               continue;
+
+                       r = GetFileAttributesTransactedW(buffer->str,
+                                                        GetFileExInfoStandard,
+                                                        &fa,
+                                                        atomic->transaction);
+
+                       if (!r) {
+                               err = GetLastError();
+                               if (err == ERROR_FILE_NOT_FOUND) {
+                                       creating = 1;
+                               } else {
+                                       razor_atomic_set_error(atomic,
+                                                              buffer->str,
+                                                              err);
+                                       razor_wstr_destroy(buffer);
+                                       return -1;
+                               }
+                       } else if (!(fa.dwFileAttributes&
+                                    FILE_ATTRIBUTE_DIRECTORY)) {
+                               razor_atomic_set_error_str(atomic, buffer->str,
+                                                          "Not a directory");
+                               razor_wstr_destroy(buffer);
+                               return -1;
+                       }
+               }
+               if (creating) {
+                       if (!CreateDirectoryTransactedW(NULL, buffer->str, NULL,
+                                                       atomic->transaction)) {
+                               razor_atomic_set_error(atomic, buffer->str,
+                                                      GetLastError());
+                               razor_wstr_destroy(buffer);
+                               return -1;
+                       }
+
+                       /* FIXME: What to do about permissions for dirs we
+                        * have to create but are not in the cpio archive? */
+               }
+       }
+
+       razor_wstr_destroy(buffer);
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_remove(struct razor_atomic *atomic, const char *path)
+{
+       wchar_t *buf;
+       DWORD err;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       buf = razor_utf8_to_utf16(path, -1);
+
+       if (DeleteFileTransactedW(buf, atomic->transaction)) {
+               free(buf);
+               return 0;
+       }
+
+       err = GetLastError();
+       if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
+               free(buf);
+               return 0;
+       }
+
+       if (SetFileAttributesTransactedW(buf, FILE_ATTRIBUTE_NORMAL,
+                                        atomic->transaction)) {
+               if (DeleteFileTransactedW(buf, atomic->transaction)) {
+                       free(buf);
+                       return 0;
+               }
+               err = GetLastError();
+       }
+
+       if (RemoveDirectoryTransactedW(buf, atomic->transaction) ||
+           GetLastError() == ERROR_DIR_NOT_EMPTY) {
+               free(buf);
+               return 0;
+       }
+
+       /*
+        * It would be tempting to use:
+        *      MoveFileEx(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)
+        * but unless we can guarantee that the system will be rebooted
+        * before we (or some other application) write another file with the
+        * same path, this is likely to cause more problems than it solves.
+        */
+
+       razor_atomic_set_error(atomic, buf, err);
+       free(buf);
+       return -1;
+}
+
+RAZOR_EXPORT int
+razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
+                        const char *newpath)
+{
+       wchar_t *oldbuf, *newbuf;
+       const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       newbuf = razor_utf8_to_utf16(newpath, -1);
+       oldbuf = razor_utf8_to_utf16(oldpath, -1);
+
+       /*
+        * Passing MOVEFILE_REPLACE_EXISTING to MoveFileTransaction() will
+        * cover every case we care about _except_ replacing an empty
+        * directory with a file. Calling RemoveDirectoryTransacted() will deal
+        * with this case while having no effect in all other cases.
+        */
+       (void)RemoveDirectoryTransactedW(newbuf, atomic->transaction);
+
+       if (!MoveFileTransactedW(oldbuf, newbuf, NULL, NULL, flags,
+                                atomic->transaction))
+               razor_atomic_set_error(atomic, newbuf, GetLastError());
+
+       free(newbuf);
+       free(oldbuf);
+
+       return !!atomic->error_str;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
+                       mode_t mode)
+{
+       wchar_t *buf;
+       DWORD err;
+       WIN32_FILE_ATTRIBUTE_DATA fa;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       buf = razor_utf8_to_utf16(dirname, -1);
+
+       if (!CreateDirectoryTransactedW(NULL, buf, NULL, atomic->transaction)) {
+               err = GetLastError();
+               if (err != ERROR_FILE_EXISTS && err != ERROR_ALREADY_EXISTS) {
+abort:
+                       razor_atomic_set_error(atomic, buf, err);
+                       free(buf);
+                       return -1;
+               }
+
+               if (!GetFileAttributesTransactedW(buf, GetFileExInfoStandard,
+                                                 &fa, atomic->transaction))
+                       goto abort;
+
+               if (!(fa.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
+                       if (razor_atomic_remove(atomic, dirname)) {
+                               free(buf);
+                               return -1;
+                       }
+                       if (!CreateDirectoryTransactedW(NULL, buf, NULL,
+                                                       atomic->transaction)) {
+                               err = GetLastError();
+                               goto abort;
+                       }
+               }
+       }
+
+       free(buf);
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
+                           const char *path)
+{
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       /*
+        * This isn't true, but symbolic links under Windows 7
+        * need to know whether the target is a directory or not
+        * and we don't always know that at the time when the
+        * link is created, so it's a convienent lie for now.
+        */
+       razor_atomic_set_error_str(atomic, NULL, "Symbolic links not supported "
+                                                "on this platform");
+
+       return -1;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
+                        mode_t mode)
+{
+       DWORD attribs;
+       struct razor_atomic_file *files;
+       int i = atomic->n_files;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       files = realloc(atomic->files,
+                       (atomic->n_files+1) * sizeof(struct razor_atomic_file));
+       if (!files) {
+               razor_atomic_set_error_str(atomic, NULL, "Not enough memory");
+               return -1;
+       }
+       atomic->n_files++;
+       atomic->files = files;
+
+       files[i].path = razor_utf8_to_utf16(filename, -1);
+
+       /*
+        * Passing CREATE_ALWAYS to CreateFileTransacted() will cover
+        * every case we care about _except_ replacing an empty directory
+        * with a file. Calling RemoveDirectoryTransacted() will deal
+        * with this case while having no effect in all other cases.
+        */
+       (void)RemoveDirectoryTransactedW(files[i].path, atomic->transaction);
+
+       if (mode & S_IWUSR)
+               attribs = FILE_ATTRIBUTE_NORMAL;
+       else
+               attribs = FILE_ATTRIBUTE_READONLY;
+
+       files[i].h = CreateFileTransactedW(files[i].path, GENERIC_WRITE,
+                                          0, NULL, CREATE_ALWAYS, attribs,
+                                          NULL, atomic->transaction, NULL,
+                                          NULL);
+
+       if (files[i].h == INVALID_HANDLE_VALUE) {
+               razor_atomic_set_error(atomic, files[i].path, GetLastError());
+               free(files[i].path);
+               atomic->n_files--;
+               return -1;
+       }
+
+       return i;
+}
+
+RAZOR_EXPORT int
+razor_atomic_write(struct razor_atomic *atomic, int handle, const void *data,
+                  size_t size)
+{
+       DWORD written;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       assert(handle < atomic->n_files);
+       assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
+
+       while(size) {
+               if (!WriteFile(atomic->files[handle].h, data, size, &written,
+                              NULL)) {
+                       razor_atomic_set_error(atomic,
+                                              atomic->files[handle].path,
+                                              GetLastError());
+
+                       (void)CloseHandle(atomic->files[handle].h);
+                       free(atomic->files[handle].path);
+                       atomic->files[handle].path = NULL;
+                       atomic->files[handle].h = INVALID_HANDLE_VALUE;
+
+                       return -1;
+               }
+
+               data += written;
+               size -= written;
+       }
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_sync(struct razor_atomic *atomic, int handle)
+{
+       HANDLE h;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       assert(handle < atomic->n_files);
+       assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
+
+       if (!CloseHandle(atomic->files[handle].h)) {
+               razor_atomic_set_error(atomic, atomic->files[handle].path,
+                                      GetLastError());
+               free(atomic->files[handle].path);
+               atomic->files[handle].path = NULL;
+               atomic->files[handle].h = INVALID_HANDLE_VALUE;
+               return -1;
+       }
+
+       h = CreateFileTransactedW(atomic->files[handle].path, GENERIC_WRITE, 0,
+                                 NULL, OPEN_EXISTING, 0, NULL,
+                                 atomic->transaction, NULL, NULL);
+       atomic->files[handle].h = h;
+
+       if (atomic->files[handle].h == INVALID_HANDLE_VALUE) {
+               razor_atomic_set_error(atomic, atomic->files[handle].path,
+                                      GetLastError());
+               free(atomic->files[handle].path);
+               atomic->files[handle].path = NULL;
+               return -1;
+       }
+
+       return !!atomic->error_str;
+}
+
+RAZOR_EXPORT int
+razor_atomic_close(struct razor_atomic *atomic, int handle)
+{
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       assert(handle < atomic->n_files);
+       assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
+
+       if (!CloseHandle(atomic->files[handle].h))
+               razor_atomic_set_error(atomic, atomic->files[handle].path,
+                                      GetLastError());
+
+       free(atomic->files[handle].path);
+       atomic->files[handle].path = NULL;
+       atomic->files[handle].h = INVALID_HANDLE_VALUE;
+
+       while(atomic->n_files > 0 &&
+             atomic->files[atomic->n_files-1].h == INVALID_HANDLE_VALUE)
+               atomic->n_files--;
+
+       return !!atomic->error_str;
+}
+
+#endif         /* HAVE_WINDOWS_KTM */
diff --git a/librazor/atomic-none.c b/librazor/atomic-none.c
new file mode 100644 (file)
index 0000000..75b7434
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2011-2012  J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#if !ENABLE_ATOMIC
+
+#include <stdlib.h>
+#ifdef MSWIN_API
+#include <windows.h>
+#endif
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+
+#include "razor.h"
+#include "razor-internal.h"
+
+#ifndef O_BINARY
+#define O_BINARY       0
+#endif
+
+RAZOR_EXPORT struct razor_atomic *
+razor_atomic_open(const char *description)
+{
+       struct razor_atomic *atomic;
+
+       atomic = zalloc(sizeof *atomic);
+
+       return atomic;
+}
+
+RAZOR_EXPORT int
+razor_atomic_commit(struct razor_atomic *atomic)
+{
+       return razor_atomic_in_error_state(atomic);
+}
+
+RAZOR_EXPORT void
+razor_atomic_destroy(struct razor_atomic *atomic)
+{
+       free(atomic->error_path);
+       free(atomic->error_str);
+       free(atomic->error_msg);
+       free(atomic);
+}
+
+RAZOR_EXPORT int
+razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
+                      const char *path)
+{
+       char buffer[PATH_MAX], *p;
+       const char *slash, *next;
+       struct stat buf;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       strcpy(buffer, root);
+       p = buffer + strlen(buffer);
+       slash = path;
+       for (slash = path; *slash != '\0'; slash = next) {
+#ifdef MSWIN_API
+               next = strpbrk(slash + 1, "/\\");
+#else
+               next = strchr(slash + 1, '/');
+#endif
+               if (next == NULL)
+                       break;
+
+               memcpy(p, slash, next - slash);
+               p += next - slash;
+               *p = '\0';
+
+               if (razor_valid_root_name(buffer))
+                       continue;
+
+               if (stat(buffer, &buf) == 0) {
+                       if (!S_ISDIR(buf.st_mode)) {
+                               razor_atomic_set_error_str(atomic, buffer,
+                                                          "Not a directory");
+                               return -1;
+                       }
+               } else if (mkdir(buffer, 0777) < 0) {
+                       razor_atomic_set_error_str(atomic, buffer,
+                                                  strerror(errno));
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_remove(struct razor_atomic *atomic, const char *path)
+{
+#ifdef MSWIN_API
+       wchar_t *buf;
+       DWORD err;
+#endif
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+#ifdef MSWIN_API
+       buf = razor_utf8_to_utf16(path, -1);
+
+       if (!DeleteFileW(buf)) {
+               err = GetLastError();
+               if (err != ERROR_FILE_NOT_FOUND &&
+                   err != ERROR_PATH_NOT_FOUND &&
+                   !(SetFileAttributesW(buf, FILE_ATTRIBUTE_NORMAL) &&
+                     DeleteFileW(buf)) &&
+                   !RemoveDirectoryW(buf) &&
+                   GetLastError() != ERROR_DIR_NOT_EMPTY)
+                       razor_atomic_set_error_mswin(atomic, buf, err);
+       }
+
+       free(buf);
+#else
+       if (remove(path))
+               razor_atomic_set_error_str(atomic, path, strerror(errno));
+#endif
+
+       return !!atomic->error_str;
+}
+
+RAZOR_EXPORT int
+razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
+                        const char *newpath)
+{
+#ifdef MSWIN_API
+       wchar_t *oldbuf, *newbuf;
+       const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
+#endif
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+#ifdef MSWIN_API
+       newbuf = razor_utf8_to_utf16(newpath, -1);
+       oldbuf = razor_utf8_to_utf16(oldpath, -1);
+
+       /*
+        * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will
+        * cover every case we care about _except_ replacing an empty
+        * directory with a file. Calling RemoveDirectory() will deal
+        * with this case while having no effect in all other cases.
+        */
+       (void)RemoveDirectoryW(newbuf);
+
+       if (!MoveFileExW(oldbuf, newbuf, flags))
+               razor_atomic_set_error_mswin(atomic, newbuf, GetLastError());
+
+       free(newbuf);
+       free(oldbuf);
+#else
+       if (rename(oldpath, newpath))
+               razor_atomic_set_error_str(atomic, newpath, strerror(errno));
+#endif
+
+       return !!atomic->error_str;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
+                       mode_t mode)
+{
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       if (!mkdir(dirname, mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
+               return 0;
+
+       if (errno != EEXIST) {
+               razor_atomic_set_error_str(atomic, dirname, strerror(errno));
+               return -1;
+       }
+
+       if (chmod(dirname, mode & (S_IRWXU | S_IRWXG | S_IRWXO)) < 0) {
+               razor_atomic_set_error_str(atomic, dirname, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
+                           const char *path)
+{
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+#if HAVE_SYMLINK
+       if (symlink(target, path) < 0) {
+               razor_atomic_set_error_str(atomic, NULL, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+#else
+       razor_atomic_set_error_str(atomic, NULL, "Symbolic links not supported "
+                                                "on this platform");
+
+       return -1;
+#endif
+}
+
+RAZOR_EXPORT int
+razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
+                        mode_t mode)
+{
+       int fd;
+
+       if (razor_atomic_in_error_state(atomic))
+               return -1;
+
+       atomic->error_path = strdup(filename);
+       fd = open(atomic->error_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+                 mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+       if (fd == -1)
+               razor_atomic_set_error_str(atomic, NULL, strerror(errno));
+
+       return fd;
+}
+
+#endif /* !ENABLE_ATOMIC */
index ffc5bbf..261afe7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011  J. Ali Harlow <ali@juiblex.co.uk>
+ * Copyright (C) 2011-2012  J. Ali Harlow <ali@juiblex.co.uk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include "config.h"
 
 #include <stdlib.h>
-#ifdef MSWIN_API
-#include <windows.h>
-#endif
 #include <stdio.h>
 #include <limits.h>
 #include <errno.h>
 #include <unistd.h>
-#include <fcntl.h>
+#include <sys/types.h>
 #include <sys/stat.h>
+#include <fcntl.h>
 #include <string.h>
 #include <assert.h>
-#if HAVE_WINDOWS_KTM
-#include <wchar.h>
-#include <ktmw32.h>
-#endif
 
 #include "razor.h"
 #include "razor-internal.h"
  * Atomic transactions
  */
 
-#ifndef O_BINARY
-#define O_BINARY       0
-#endif
-
-#define RAZOR_ASCII_ISALPHA(c) \
-                       ((c) >= 'A' && (c) <= 'Z' || (c) >= 'a' && (c) <= 'z')
-
 static int allow_all_root_names = 0;
 
 /*
@@ -60,596 +47,54 @@ razor_disable_root_name_checks(int disable)
        allow_all_root_names = disable;
 }
 
-#ifdef MSWIN_API
-
-static char *
-razor_utf16_to_utf8(const wchar_t *utf16, int len)
-{
-       int n;
-       char *utf8;
-
-       n = WideCharToMultiByte(CP_UTF8, 0, utf16, len, NULL, 0, NULL, NULL);
-       if (len >= 0 && utf16[len])
-               n++;
-       utf8 = malloc(n);
-       (void)WideCharToMultiByte(CP_UTF8, 0, utf16, len, utf8, n, NULL, NULL);
-       if (len >= 0 && utf16[len])
-               utf8[n - 1] = 0;
-
-       return utf8;
-}
-
-static wchar_t *
-razor_utf8_to_utf16(const char *utf8, int len)
-{
-       int n;
-       wchar_t *utf16;
-
-       n = MultiByteToWideChar(CP_UTF8, 0, utf8, len, NULL, 0);
-       if (len >= 0 && utf8[len])
-               n++;
-       utf16 = malloc(n * sizeof(wchar_t));
-       (void)MultiByteToWideChar(CP_UTF8, 0, utf8, len, utf16, n);
-       if (len >= 0 && utf8[len])
-               utf16[n - 1] = 0;
-
-       return utf16;
-}
-
-#endif /* MSWIN_API */
-
-#if HAVE_WINDOWS_KTM
-
-static int
-razor_valid_root_name(const wchar_t *name)
-{
-       if (allow_all_root_names)
-               return !wcschr(name, '/');
-
-       return RAZOR_ASCII_ISALPHA(name[0]) && name[1] == ':' &&
-              name[2] == '\0';
-}
-
-struct razor_atomic {
-       HANDLE transaction;
-       int n_files;
-       struct razor_atomic_file {
-               wchar_t *path;
-               HANDLE h;
-       } *files;
-       char *error_path;
-       char *error_str;
-       char *error_msg;
-};
-
-struct razor_wstr {
-       wchar_t *str;
-       int len, allocated;
-};
-
-static struct razor_wstr *
-razor_wstr_create(const char *init, int len)
-{
-       int n;
-       struct razor_wstr *wstr;
-
-       wstr = malloc(sizeof(struct razor_wstr));
-
-       n = MultiByteToWideChar(CP_UTF8, 0, init, len, NULL, 0);
-       if (len >= 0 && init[len])
-               wstr->len = n++;
-       else
-               wstr->len = n - 1;
-
-       wstr->allocated = n * 2;
-       wstr->str = malloc(wstr->allocated * sizeof(wchar_t));
-       if (!wstr->str) {
-               free(wstr);
-               return NULL;
-       }
-
-       (void)MultiByteToWideChar(CP_UTF8, 0, init, len, wstr->str, n);
-       if (len >= 0 && init[len])
-               wstr->str[wstr->len] = 0;
-
-       return wstr;
-}
-
-static int
-razor_wstr_append(struct razor_wstr *wstr, const char *s, int len)
-{
-       int n, allocated;
-       wchar_t *str;
-
-       n = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0);
-       if (len < 0 || !s[len])
-               n--;
-
-       if (wstr->allocated <= wstr->len + n) {
-               allocated = (wstr->len + n + 1) * 2;
-               str = realloc(wstr->str, allocated * sizeof(wchar_t));
-               if (!str)
-                       return -1;
-               wstr->allocated = allocated;
-               wstr->str = str;
-       }
-
-       (void)MultiByteToWideChar(CP_UTF8, 0, s, len, wstr->str + wstr->len, n);
-       wstr->len += n;
-       wstr->str[wstr->len] = 0;
-
-       return 0;
-}
-
-static void
-razor_wstr_destroy(struct razor_wstr *wstr)
-{
-       free(wstr->str);
-       free(wstr);
-}
-
-RAZOR_EXPORT struct razor_atomic *
-razor_atomic_open(const char *description)
-{
-       wchar_t *buf;
-       struct razor_atomic *atomic;
-
-       atomic = zalloc(sizeof *atomic);
-       buf = razor_utf8_to_utf16(description, -1);
-       atomic->transaction = CreateTransaction(NULL, 0,
-                                               TRANSACTION_DO_NOT_PROMOTE,
-                                               0, 0, 0, buf);
-       free(buf);
-
-       return atomic;
-}
-
-static void
-razor_atomic_set_error_str(struct razor_atomic *atomic, const wchar_t *path,
-                          const char *str)
+int
+razor_allow_all_root_names(void)
 {
-       assert(!atomic->error_str);
-
-       free(atomic->error_path);
-
-       if (path)
-               atomic->error_path = razor_utf16_to_utf8(path, -1);
-       else
-               atomic->error_path = NULL;
-
-       atomic->error_str = strdup(str);
+       return allow_all_root_names;
 }
 
-static void
-razor_atomic_set_error(struct razor_atomic *atomic, const wchar_t *path,
-                      DWORD error)
-{
-       wchar_t *buf;
-
-       assert(!atomic->error_str);
-
-       free(atomic->error_path);
-
-       if (path)
-               atomic->error_path = razor_utf16_to_utf8(path, -1);
-       else
-               atomic->error_path = NULL;
-
-       FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|
-                      FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
-                      NULL, error, MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
-                      (LPWSTR)&buf, 0, NULL);
-       atomic->error_str = razor_utf16_to_utf8(buf, -1);
-       LocalFree(buf);
-}
-
-RAZOR_EXPORT int
-razor_atomic_commit(struct razor_atomic *atomic)
+RAZOR_EXPORT const char *
+razor_atomic_get_error_msg(struct razor_atomic *atomic)
 {
-       int retval;
-
-       if (atomic->error_str)
-               return -1;
-
-       retval = !CommitTransaction(atomic->transaction);
-
-       if (retval) {
-               razor_atomic_set_error(atomic, NULL, GetLastError());
-               RollbackTransaction(atomic->transaction);
+       if (!atomic->error_msg) {
+               if (atomic->error_path)
+                       atomic->error_msg = razor_concat(atomic->error_path,
+                                                        ": ",
+                                                        atomic->error_str,
+                                                        NULL);
+               else
+                       atomic->error_msg = strdup(atomic->error_str);
        }
 
-       CloseHandle(atomic->transaction);
-       atomic->transaction = INVALID_HANDLE_VALUE;
-
-       return retval;
+       return atomic->error_msg;
 }
 
 RAZOR_EXPORT void
-razor_atomic_destroy(struct razor_atomic *atomic)
-{
-       int i;
-
-       for(i = 0; i < atomic->n_files; i++) {
-               if (atomic->files[i].h != INVALID_HANDLE_VALUE) {
-                       CloseHandle(atomic->files[i].h);
-                       free(atomic->files[i].path);
-               }
-       }
-       free(atomic->files);
-       if (atomic->transaction != INVALID_HANDLE_VALUE) {
-               RollbackTransaction(atomic->transaction);
-               CloseHandle(atomic->transaction);
-       }
-       free(atomic->error_path);
-       free(atomic->error_str);
-       free(atomic->error_msg);
-       free(atomic);
-}
-
-RAZOR_EXPORT int
-razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
-                      const char *path)
-{
-       struct razor_wstr *buffer;
-       const char *slash, *s, *next;
-       WIN32_FILE_ATTRIBUTE_DATA fa;
-       DWORD err;
-       int r, creating = 0;
-
-       if (atomic->error_str)
-               return -1;
-
-       buffer = razor_wstr_create(root, -1);
-       slash = path;
-
-       for (; *slash != '\0'; slash = next) {
-               next = strpbrk(slash + 1, "/\\");
-               if (next == NULL)
-                       break;
-
-               razor_wstr_append(buffer, slash, next - slash);
-
-               if (!creating) {
-                       if (razor_valid_root_name(buffer->str))
-                               continue;
-
-                       r = GetFileAttributesTransactedW(buffer->str,
-                                                        GetFileExInfoStandard,
-                                                        &fa,
-                                                        atomic->transaction);
-
-                       if (!r) {
-                               err = GetLastError();
-                               if (err == ERROR_FILE_NOT_FOUND) {
-                                       creating = 1;
-                               } else {
-                                       razor_atomic_set_error(atomic,
-                                                              buffer->str,
-                                                              err);
-                                       razor_wstr_destroy(buffer);
-                                       return -1;
-                               }
-                       } else if (!(fa.dwFileAttributes&
-                                    FILE_ATTRIBUTE_DIRECTORY)) {
-                               razor_atomic_set_error_str(atomic, buffer->str,
-                                                          "Not a directory");
-                               razor_wstr_destroy(buffer);
-                               return -1;
-                       }
-               }
-               if (creating) {
-                       if (!CreateDirectoryTransactedW(NULL, buffer->str, NULL,
-                                                       atomic->transaction)) {
-                               razor_atomic_set_error(atomic, buffer->str,
-                                                      GetLastError());
-                               razor_wstr_destroy(buffer);
-                               return -1;
-                       }
-
-                       /* FIXME: What to do about permissions for dirs we
-                        * have to create but are not in the cpio archive? */
-               }
-       }
-
-       razor_wstr_destroy(buffer);
-
-       return 0;
-}
-
-RAZOR_EXPORT int
-razor_atomic_remove(struct razor_atomic *atomic, const char *path)
-{
-       wchar_t *buf;
-       DWORD err;
-
-       if (atomic->error_str)
-               return -1;
-
-       buf = razor_utf8_to_utf16(path, -1);
-
-       if (DeleteFileTransactedW(buf, atomic->transaction)) {
-               free(buf);
-               return 0;
-       }
-
-       err = GetLastError();
-       if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
-               free(buf);
-               return 0;
-       }
-
-       if (SetFileAttributesTransactedW(buf, FILE_ATTRIBUTE_NORMAL,
-                                        atomic->transaction)) {
-               if (DeleteFileTransactedW(buf, atomic->transaction)) {
-                       free(buf);
-                       return 0;
-               }
-               err = GetLastError();
-       }
-
-       if (RemoveDirectoryTransactedW(buf, atomic->transaction) ||
-           GetLastError() == ERROR_DIR_NOT_EMPTY) {
-               free(buf);
-               return 0;
-       }
-
-       /*
-        * It would be tempting to use:
-        *      MoveFileEx(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)
-        * but unless we can guarantee that the system will be rebooted
-        * before we (or some other application) write another file with the
-        * same path, this is likely to cause more problems than it solves.
-        */
-
-       razor_atomic_set_error(atomic, buf, err);
-       free(buf);
-       return -1;
-}
-
-RAZOR_EXPORT int
-razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
-                        const char *newpath)
-{
-       wchar_t *oldbuf, *newbuf;
-       const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
-
-       if (atomic->error_str)
-               return -1;
-
-       newbuf = razor_utf8_to_utf16(newpath, -1);
-       oldbuf = razor_utf8_to_utf16(oldpath, -1);
-
-       /*
-        * Passing MOVEFILE_REPLACE_EXISTING to MoveFileTransaction() will
-        * cover every case we care about _except_ replacing an empty
-        * directory with a file. Calling RemoveDirectoryTransacted() will deal
-        * with this case while having no effect in all other cases.
-        */
-       (void)RemoveDirectoryTransactedW(newbuf, atomic->transaction);
-
-       if (!MoveFileTransactedW(oldbuf, newbuf, NULL, NULL, flags,
-                                atomic->transaction))
-               razor_atomic_set_error(atomic, newbuf, GetLastError());
-
-       free(newbuf);
-       free(oldbuf);
-
-       return !!atomic->error_str;
-}
-
-RAZOR_EXPORT int
-razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
-                       mode_t mode)
-{
-       wchar_t *buf;
-       DWORD err;
-       WIN32_FILE_ATTRIBUTE_DATA fa;
-
-       if (atomic->error_str)
-               return -1;
-
-       buf = razor_utf8_to_utf16(dirname, -1);
-
-       if (!CreateDirectoryTransactedW(NULL, buf, NULL, atomic->transaction)) {
-               err = GetLastError();
-               if (err != ERROR_FILE_EXISTS && err != ERROR_ALREADY_EXISTS) {
-abort:
-                       razor_atomic_set_error(atomic, buf, err);
-                       free(buf);
-                       return -1;
-               }
-
-               if (!GetFileAttributesTransactedW(buf, GetFileExInfoStandard,
-                                                 &fa, atomic->transaction))
-                       goto abort;
-
-               if (!(fa.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
-                       if (razor_atomic_remove(atomic, dirname)) {
-                               free(buf);
-                               return -1;
-                       }
-                       if (!CreateDirectoryTransactedW(NULL, buf, NULL,
-                                                       atomic->transaction)) {
-                               err = GetLastError();
-                               goto abort;
-                       }
-               }
-       }
-
-       free(buf);
-
-       return 0;
-}
-
-RAZOR_EXPORT int
-razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
-                           const char *path)
-{
-       if (atomic->error_str)
-               return -1;
-
-       /*
-        * This isn't true, but symbolic links under Windows 7
-        * need to know whether the target is a directory or not
-        * and we don't always know that at the time when the
-        * link is created, so it's a convienent lie for now.
-        */
-       razor_atomic_set_error_str(atomic, NULL, "Symbolic links not supported "
-                                                "on this platform");
-
-       return -1;
-}
-
-RAZOR_EXPORT int
-razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
-                        mode_t mode)
-{
-       DWORD attribs;
-       struct razor_atomic_file *files;
-       int i = atomic->n_files;
-
-       if (atomic->error_str)
-               return -1;
-
-       files = realloc(atomic->files,
-                       (atomic->n_files+1) * sizeof(struct razor_atomic_file));
-       if (!files) {
-               razor_atomic_set_error_str(atomic, NULL, "Not enough memory");
-               return -1;
-       }
-       atomic->n_files++;
-       atomic->files = files;
-
-       files[i].path = razor_utf8_to_utf16(filename, -1);
-
-       /*
-        * Passing CREATE_ALWAYS to CreateFileTransacted() will cover
-        * every case we care about _except_ replacing an empty directory
-        * with a file. Calling RemoveDirectoryTransacted() will deal
-        * with this case while having no effect in all other cases.
-        */
-       (void)RemoveDirectoryTransactedW(files[i].path, atomic->transaction);
-
-       if (mode & S_IWUSR)
-               attribs = FILE_ATTRIBUTE_NORMAL;
-       else
-               attribs = FILE_ATTRIBUTE_READONLY;
-
-       files[i].h = CreateFileTransactedW(files[i].path, GENERIC_WRITE,
-                                          0, NULL, CREATE_ALWAYS, attribs,
-                                          NULL, atomic->transaction, NULL,
-                                          NULL);
-
-       if (files[i].h == INVALID_HANDLE_VALUE) {
-               razor_atomic_set_error(atomic, files[i].path, GetLastError());
-               free(files[i].path);
-               atomic->n_files--;
-               return -1;
-       }
-
-       return i;
-}
-
-RAZOR_EXPORT int
-razor_atomic_write(struct razor_atomic *atomic, int handle, const void *data,
-                  size_t size)
+razor_atomic_abort(struct razor_atomic *atomic, const char *error_msg)
 {
-       DWORD written;
-
-       if (atomic->error_str)
-               return -1;
-
-       assert(handle < atomic->n_files);
-       assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
-
-       while(size) {
-               if (!WriteFile(atomic->files[handle].h, data, size, &written,
-                              NULL)) {
-                       razor_atomic_set_error(atomic,
-                                              atomic->files[handle].path,
-                                              GetLastError());
-
-                       (void)CloseHandle(atomic->files[handle].h);
-                       free(atomic->files[handle].path);
-                       atomic->files[handle].path = NULL;
-                       atomic->files[handle].h = INVALID_HANDLE_VALUE;
-
-                       return -1;
-               }
-
-               data += written;
-               size -= written;
-       }
-
-       return 0;
+       if (!atomic->error_str)
+               razor_atomic_set_error_str(atomic, NULL, error_msg);
 }
 
 RAZOR_EXPORT int
-razor_atomic_sync(struct razor_atomic *atomic, int handle)
+razor_atomic_in_error_state(struct razor_atomic *atomic)
 {
-       HANDLE h;
-
-       if (atomic->error_str)
-               return -1;
-
-       assert(handle < atomic->n_files);
-       assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
-
-       if (!CloseHandle(atomic->files[handle].h)) {
-               razor_atomic_set_error(atomic, atomic->files[handle].path,
-                                      GetLastError());
-               free(atomic->files[handle].path);
-               atomic->files[handle].path = NULL;
-               atomic->files[handle].h = INVALID_HANDLE_VALUE;
-               return -1;
-       }
-
-       h = CreateFileTransactedW(atomic->files[handle].path, GENERIC_WRITE, 0,
-                                 NULL, OPEN_EXISTING, 0, NULL,
-                                 atomic->transaction, NULL, NULL);
-       atomic->files[handle].h = h;
-
-       if (atomic->files[handle].h == INVALID_HANDLE_VALUE) {
-               razor_atomic_set_error(atomic, atomic->files[handle].path,
-                                      GetLastError());
-               free(atomic->files[handle].path);
-               atomic->files[handle].path = NULL;
-               return -1;
-       }
-
-       return !!atomic->error_str;
+       return atomic->error_str && !atomic->in_undo;
 }
 
-RAZOR_EXPORT int
-razor_atomic_close(struct razor_atomic *atomic, int handle)
-{
-       if (atomic->error_str)
-               return -1;
-
-       assert(handle < atomic->n_files);
-       assert(atomic->files[handle].h != INVALID_HANDLE_VALUE);
-
-       if (!CloseHandle(atomic->files[handle].h))
-               razor_atomic_set_error(atomic, atomic->files[handle].path,
-                                      GetLastError());
-
-       free(atomic->files[handle].path);
-       atomic->files[handle].path = NULL;
-       atomic->files[handle].h = INVALID_HANDLE_VALUE;
-
-       while(atomic->n_files > 0 &&
-             atomic->files[atomic->n_files-1].h == INVALID_HANDLE_VALUE)
-               atomic->n_files--;
+#if !HAVE_WINDOWS_KTM
 
-       return !!atomic->error_str;
-}
+/*
+ * Common code with atomic-none and atomic-emulate
+ */
 
-#else          /* HAVE_WINDOWS_KVM */
+#define RAZOR_ASCII_ISALPHA(c) \
+                       ((c) >= 'A' && (c) <= 'Z' || (c) >= 'a' && (c) <= 'z')
 
-static int
+int
 razor_valid_root_name(const char *name)
 {
-       if (allow_all_root_names) {
+       if (razor_allow_all_root_names()) {
 #ifdef MSWIN_API
                return !strpbrk(name, "/\\");
 #else
@@ -665,36 +110,10 @@ razor_valid_root_name(const char *name)
 #endif
 }
 
-struct razor_atomic {
-       char *error_path;
-       char *error_str;
-       char *error_msg;
-};
-
-RAZOR_EXPORT struct razor_atomic *
-razor_atomic_open(const char *description)
-{
-       struct razor_atomic *atomic;
-
-       atomic = zalloc(sizeof *atomic);
-
-       return atomic;
-}
-
-static void
-razor_atomic_set_error_str(struct razor_atomic *atomic, const char *path,
-                          const char *str)
-{
-       assert(!atomic->error_str);
-
-       atomic->error_path = path ? strdup(path) : NULL;
-       atomic->error_str = strdup(str);
-}
-
 #ifdef MSWIN_API
-static void
+void
 razor_atomic_set_error_mswin(struct razor_atomic *atomic, const wchar_t *path,
-                      DWORD error)
+                            DWORD error)
 {
        wchar_t *buf;
 
@@ -716,198 +135,14 @@ razor_atomic_set_error_mswin(struct razor_atomic *atomic, const wchar_t *path,
 }
 #endif
 
-RAZOR_EXPORT int
-razor_atomic_commit(struct razor_atomic *atomic)
-{
-       return !!atomic->error_str;
-}
-
-RAZOR_EXPORT void
-razor_atomic_destroy(struct razor_atomic *atomic)
-{
-       free(atomic->error_path);
-       free(atomic->error_str);
-       free(atomic->error_msg);
-       free(atomic);
-}
-
-RAZOR_EXPORT int
-razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root,
-                      const char *path)
-{
-       char buffer[PATH_MAX], *p;
-       const char *slash, *next;
-       struct stat buf;
-
-       if (atomic->error_str)
-               return -1;
-
-       strcpy(buffer, root);
-       p = buffer + strlen(buffer);
-       slash = path;
-       for (slash = path; *slash != '\0'; slash = next) {
-#ifdef MSWIN_API
-               next = strpbrk(slash + 1, "/\\");
-#else
-               next = strchr(slash + 1, '/');
-#endif
-               if (next == NULL)
-                       break;
-
-               memcpy(p, slash, next - slash);
-               p += next - slash;
-               *p = '\0';
-
-               if (razor_valid_root_name(buffer))
-                       continue;
-
-               if (stat(buffer, &buf) == 0) {
-                       if (!S_ISDIR(buf.st_mode)) {
-                               razor_atomic_set_error_str(atomic, buffer,
-                                                          "Not a directory");
-                               return -1;
-                       }
-               } else if (mkdir(buffer, 0777) < 0) {
-                       razor_atomic_set_error_str(atomic, buffer,
-                                                  strerror(errno));
-                       return -1;
-               }
-       }
-
-       return 0;
-}
-
-RAZOR_EXPORT int
-razor_atomic_remove(struct razor_atomic *atomic, const char *path)
-{
-#ifdef MSWIN_API
-       wchar_t *buf;
-       DWORD err;
-#endif
-
-       if (atomic->error_str)
-               return -1;
-
-#ifdef MSWIN_API
-       buf = razor_utf8_to_utf16(path, -1);
-
-       if (!DeleteFileW(buf)) {
-               err = GetLastError();
-               if (err != ERROR_FILE_NOT_FOUND &&
-                   err != ERROR_PATH_NOT_FOUND &&
-                   !(SetFileAttributesW(buf, FILE_ATTRIBUTE_NORMAL) &&
-                     DeleteFileW(buf)) &&
-                   !RemoveDirectoryW(buf) &&
-                   GetLastError() != ERROR_DIR_NOT_EMPTY)
-                       razor_atomic_set_error_mswin(atomic, buf, err);
-       }
-
-       free(buf);
-#else
-       if (remove(path))
-               razor_atomic_set_error_str(atomic, path, strerror(errno));
-#endif
-
-       return !!atomic->error_str;
-}
-
-RAZOR_EXPORT int
-razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath,
-                        const char *newpath)
-{
-#ifdef MSWIN_API
-       wchar_t *oldbuf, *newbuf;
-       const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;
-#endif
-
-       if (atomic->error_str)
-               return -1;
-
-#ifdef MSWIN_API
-       newbuf = razor_utf8_to_utf16(newpath, -1);
-       oldbuf = razor_utf8_to_utf16(oldpath, -1);
-
-       /*
-        * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will
-        * cover every case we care about _except_ replacing an empty
-        * directory with a file. Calling RemoveDirectory() will deal
-        * with this case while having no effect in all other cases.
-        */
-       (void)RemoveDirectoryW(newbuf);
-
-       if (!MoveFileExW(oldbuf, newbuf, flags))
-               razor_atomic_set_error_mswin(atomic, newbuf, GetLastError());
-
-       free(newbuf);
-       free(oldbuf);
-#else
-       if (rename(oldpath, newpath))
-               razor_atomic_set_error_str(atomic, newpath, strerror(errno));
-#endif
-
-       return !!atomic->error_str;
-}
-
-RAZOR_EXPORT int
-razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname,
-                       mode_t mode)
-{
-       if (atomic->error_str)
-               return -1;
-
-       if (!mkdir(dirname, mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
-               return 0;
-
-       if (errno != EEXIST) {
-               razor_atomic_set_error_str(atomic, dirname, strerror(errno));
-               return -1;
-       }
-
-       if (chmod(dirname, mode & (S_IRWXU | S_IRWXG | S_IRWXO)) < 0) {
-               razor_atomic_set_error_str(atomic, dirname, strerror(errno));
-               return -1;
-       }
-
-       return 0;
-}
-
-RAZOR_EXPORT int
-razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target,
-                           const char *path)
-{
-       if (atomic->error_str)
-               return -1;
-
-#if HAVE_SYMLINK
-       if (symlink(target, path) < 0) {
-               razor_atomic_set_error_str(atomic, NULL, strerror(errno));
-               return -1;
-       }
-#else
-       razor_atomic_set_error_str(atomic, NULL, "Symbolic links not supported "
-                                                "on this platform");
-#endif
-
-       return 0;
-}
-
-RAZOR_EXPORT int
-razor_atomic_create_file(struct razor_atomic *atomic, const char *filename,
-                        mode_t mode)
+void
+razor_atomic_set_error_str(struct razor_atomic *atomic, const char *path,
+                          const char *str)
 {
-       int fd;
-
-       if (atomic->error_str)
-               return -1;
-
-       atomic->error_path = strdup(filename);
-       fd = open(atomic->error_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
-                 mode & (S_IRWXU | S_IRWXG | S_IRWXO));
-
-       if (fd == -1)
-               razor_atomic_set_error_str(atomic, NULL, strerror(errno));
+       assert(!atomic->error_str);
 
-       return fd;
+       atomic->error_path = path ? strdup(path) : NULL;
+       atomic->error_str = strdup(str);
 }
 
 RAZOR_EXPORT int
@@ -916,13 +151,14 @@ razor_atomic_write(struct razor_atomic *atomic, int fd, const void *data,
 {
        int written;
 
-       if (atomic->error_str)
+       if (razor_atomic_in_error_state(atomic))
                return -1;
 
        while(size) {
                written = write(fd, data, size);
                if (written < 0) {
-                       razor_atomic_set_error_str(atomic, NULL, strerror(errno));
+                       razor_atomic_set_error_str(atomic, NULL,
+                                                  strerror(errno));
 
                        (void)close(fd);
 
@@ -939,7 +175,7 @@ razor_atomic_write(struct razor_atomic *atomic, int fd, const void *data,
 RAZOR_EXPORT int
 razor_atomic_sync(struct razor_atomic *atomic, int handle)
 {
-       if (atomic->error_str)
+       if (razor_atomic_in_error_state(atomic))
                return -1;
 
        if (fsync(handle) < 0) {
@@ -956,7 +192,7 @@ razor_atomic_sync(struct razor_atomic *atomic, int handle)
 RAZOR_EXPORT int
 razor_atomic_close(struct razor_atomic *atomic, int fd)
 {
-       if (atomic->error_str)
+       if (razor_atomic_in_error_state(atomic))
                return -1;
 
        if (close(fd) < 0) {
@@ -970,33 +206,4 @@ razor_atomic_close(struct razor_atomic *atomic, int fd)
        return 0;
 }
 
-#endif         /* HAVE_WINDOWS_KVM */
-
-RAZOR_EXPORT const char *
-razor_atomic_get_error_msg(struct razor_atomic *atomic)
-{
-       if (!atomic->error_msg) {
-               if (atomic->error_path)
-                       atomic->error_msg = razor_concat(atomic->error_path,
-                                                        ": ",
-                                                        atomic->error_str,
-                                                        NULL);
-               else
-                       atomic->error_msg = strdup(atomic->error_str);
-       }
-
-       return atomic->error_msg;
-}
-
-RAZOR_EXPORT void
-razor_atomic_abort(struct razor_atomic *atomic, const char *error_msg)
-{
-       if (!atomic->error_str)
-               razor_atomic_set_error_str(atomic, NULL, error_msg);
-}
-
-RAZOR_EXPORT int
-razor_atomic_in_error_state(struct razor_atomic *atomic)
-{
-       return !!atomic->error_str;
-}
+#endif /* !HAVE_WINDOWS_KTM */
index 22f193b..790429d 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2008  Kristian Høgsberg <krh@redhat.com>
  * Copyright (C) 2008  Red Hat, Inc
- * Copyright (C) 2009, 2011  J. Ali Harlow <ali@juiblex.co.uk>
+ * Copyright (C) 2009, 2011-2012  J. Ali Harlow <ali@juiblex.co.uk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,9 @@
 #ifndef _RAZOR_INTERNAL_H_
 #define _RAZOR_INTERNAL_H_
 
+#ifdef MSWIN_API
+#include <windows.h>
+#endif
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdarg.h>
@@ -234,4 +237,109 @@ void environment_set(struct environment *env);
 void environment_unset(struct environment *env);
 void environment_release(struct environment *env);
 
+#ifdef MSWIN_API
+char *razor_utf16_to_utf8(const wchar_t *utf16, int len);
+wchar_t *razor_utf8_to_utf16(const char *utf8, int len);
+#endif
+
+/* Atomic functions */
+
+#if HAVE_WINDOWS_KTM
+struct razor_atomic {
+       HANDLE transaction;
+       int n_files;
+       struct razor_atomic_file {
+               wchar_t *path;
+               HANDLE h;
+       } *files;
+       int in_undo;
+       char *error_path;
+       char *error_str;
+       char *error_msg;
+};
+#elif ENABLE_ATOMIC
+struct atomic_action {
+       struct atomic_action *next;
+       enum atomic_action_type {
+               /* Complex actions */
+               ACTION_MAKE_DIRS,
+               ACTION_REMOVE,
+               /* Primitive actions */
+               ACTION_CREATE_DIR,
+#if HAVE_SYMLINK
+               ACTION_CREATE_SYMLINK,
+#endif
+               ACTION_MOVE,
+       } type;
+       struct {
+               char *path;
+               union atomic_action_args {
+                       struct {
+                               char *root;
+                       } make_dirs;
+                       struct {
+                               mode_t mode;
+                       } create_dir;
+#if HAVE_SYMLINK
+                       struct {
+                               char *target;
+                       } create_symlink;
+#endif
+                       struct {
+                               char *dest;
+                       } move;
+               } u;
+       } args;
+};
+
+struct razor_atomic {
+       struct atomic_action *actions;
+       char *description;
+       char *toplevel;
+       unsigned next_file_tag;
+       int in_undo;
+       char *error_path;
+       char *error_str;
+       char *error_msg;
+};
+
+char *atomic_action_attic_tmpnam(struct razor_atomic *atomic);
+struct atomic_action *
+atomic_action_list_prepend(struct atomic_action *list,
+                          struct atomic_action *action);
+struct atomic_action *atomic_action_new(enum atomic_action_type type);
+void atomic_action_free(struct atomic_action *action);
+struct atomic_action *atomic_action_list_reverse(struct atomic_action *list);
+struct atomic_action *
+atomic_action_do(struct razor_atomic *atomic, struct atomic_action *action);
+void
+atomic_action_undo(struct razor_atomic *atomic, struct atomic_action *action);
+#else  /* !HAVE_WINDOWS_KTM && !ENABLE_ATOMIC */
+struct razor_atomic {
+       int in_undo;
+       char *error_path;
+       char *error_str;
+       char *error_msg;
+};
+#endif
+
+int razor_allow_all_root_names(void);
+int razor_valid_root_name(const char *name);
+
+#ifdef MSWIN_API
+void
+razor_atomic_set_error_mswin(struct razor_atomic *atomic, const wchar_t *path,
+                            DWORD error);
+#endif
+
+#if HAVE_WINDOWS_KTM
+void
+razor_atomic_set_error_str(struct razor_atomic *atomic, const wchar_t *path,
+                          const char *str);
+#else
+void
+razor_atomic_set_error_str(struct razor_atomic *atomic, const char *path,
+                          const char *str);
+#endif
+
 #endif /* _RAZOR_INTERNAL_H_ */
index 0806091..52cac4a 100644 (file)
@@ -104,8 +104,13 @@ enum razor_property_flags {
  * that should either all succeed or all fail.
  *
  * Note that currently only Windows 7 has a native implementation and that
- * the fallback implementation will not rollback even if an error does occur.
- * This could (and should) be improved.
+ * while the emulated fallback implementation attempts to rollback the
+ * transaction if it fails, this is not guaranteed to succeed and that
+ * the transaction is held in core (and thus vulnernable to program crashes,
+ * power loss, etc.). This could (and should) be improved.
+ *
+ * Atomic transactions can be disabled via configure, in which case all
+ * bets are off.
  **/
 struct razor_atomic;
 
@@ -331,7 +336,8 @@ struct razor_install_iterator;
 
 enum razor_install_action {
        RAZOR_INSTALL_ACTION_ADD,
-       RAZOR_INSTALL_ACTION_REMOVE
+       RAZOR_INSTALL_ACTION_REMOVE,
+       RAZOR_INSTALL_ACTION_COMMIT
 };
 
 struct razor_install_iterator *
index dba6b7a..047a5d6 100644 (file)
@@ -349,3 +349,39 @@ RAZOR_EXPORT const char *razor_system_arch(void)
                return un.machine;
 #endif
 }
+
+#ifdef MSWIN_API
+
+char *razor_utf16_to_utf8(const wchar_t *utf16, int len)
+{
+       int n;
+       char *utf8;
+
+       n = WideCharToMultiByte(CP_UTF8, 0, utf16, len, NULL, 0, NULL, NULL);
+       if (len >= 0 && utf16[len])
+               n++;
+       utf8 = malloc(n);
+       (void)WideCharToMultiByte(CP_UTF8, 0, utf16, len, utf8, n, NULL, NULL);
+       if (len >= 0 && utf16[len])
+               utf8[n - 1] = 0;
+
+       return utf8;
+}
+
+wchar_t *razor_utf8_to_utf16(const char *utf8, int len)
+{
+       int n;
+       wchar_t *utf16;
+
+       n = MultiByteToWideChar(CP_UTF8, 0, utf8, len, NULL, 0);
+       if (len >= 0 && utf8[len])
+               n++;
+       utf16 = malloc(n * sizeof(wchar_t));
+       (void)MultiByteToWideChar(CP_UTF8, 0, utf8, len, utf16, n);
+       if (len >= 0 && utf8[len])
+               utf16[n - 1] = 0;
+
+       return utf16;
+}
+
+#endif /* MSWIN_API */