diff -r 000000000000 -r d0aa9e0a6d04 librazor/atomic-actions.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librazor/atomic-actions.c Thu Feb 09 20:42:08 2012 +0000 @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2012 J. Ali Harlow + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 */