ali@416: /* ali@416: * Copyright (C) 2012 J. Ali Harlow ali@416: * ali@416: * This program is free software; you can redistribute it and/or modify ali@416: * it under the terms of the GNU General Public License as published by ali@416: * the Free Software Foundation; either version 2 of the License, or ali@416: * (at your option) any later version. ali@416: * ali@416: * This program is distributed in the hope that it will be useful, ali@416: * but WITHOUT ANY WARRANTY; without even the implied warranty of ali@416: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ali@416: * GNU General Public License for more details. ali@416: * ali@416: * You should have received a copy of the GNU General Public License along ali@416: * with this program; if not, write to the Free Software Foundation, Inc., ali@416: * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ali@416: */ ali@416: ali@416: #include "config.h" ali@416: ali@416: #if ENABLE_ATOMIC && !HAVE_WINDOWS_KTM ali@416: ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include "razor-internal.h" ali@416: ali@416: char *atomic_action_attic_tmpnam(struct razor_atomic *atomic) ali@416: { ali@416: char filename[17]; ali@416: sprintf(filename,"%03X",atomic->next_file_tag++); ali@416: return razor_concat(atomic->toplevel, "/", filename, NULL); ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_list_pop_head(struct atomic_action **list) ali@416: { ali@416: struct atomic_action *head; ali@416: ali@416: head = *list; ali@416: if (head) { ali@416: *list = head->next; ali@416: head->next = NULL; ali@416: } ali@416: ali@416: return head; ali@416: } ali@416: ali@416: struct atomic_action * ali@416: atomic_action_list_prepend(struct atomic_action *list, ali@416: struct atomic_action *action) ali@416: { ali@416: assert(action->next == NULL); ali@416: ali@416: action->next=list; ali@416: ali@416: return action; ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_list_concat(struct atomic_action *list1, ali@416: struct atomic_action *list2) ali@416: { ali@416: struct atomic_action *action; ali@416: ali@416: if (!list1) ali@416: return list2; ali@416: ali@416: for(action=list1;action->next;action=action->next) ali@416: ; ali@416: ali@416: action->next=list2; ali@416: ali@416: return list1; ali@416: } ali@416: ali@416: void atomic_action_free(struct atomic_action *action) ali@416: { ali@416: struct atomic_action *a; ali@416: ali@416: while(action) { ali@416: a = atomic_action_list_pop_head(&action); ali@416: ali@416: free(a->args.path); ali@416: ali@416: switch(a->type) { ali@416: case ACTION_MAKE_DIRS: ali@416: free(a->args.u.make_dirs.root); ali@416: break; ali@416: case ACTION_MOVE: ali@416: free(a->args.u.move.dest); ali@416: break; ali@416: #if HAVE_SYMLINK ali@416: case ACTION_CREATE_SYMLINK: ali@416: free(a->args.u.create_symlink.target); ali@416: break; ali@416: #endif ali@416: case ACTION_REMOVE: ali@416: case ACTION_CREATE_DIR: ali@416: break; ali@416: } ali@416: ali@416: free(a); ali@416: } ali@416: } ali@416: ali@416: struct atomic_action *atomic_action_new(enum atomic_action_type type) ali@416: { ali@416: struct atomic_action *action; ali@416: ali@416: action = zalloc(sizeof *action); ali@416: action->type = type; ali@416: ali@416: return action; ali@416: } ali@416: ali@416: struct atomic_action *atomic_action_list_reverse(struct atomic_action *list) ali@416: { ali@416: struct atomic_action *prev = NULL, *next; ali@416: ali@416: while(list) { ali@416: next = list->next; ali@416: list->next = prev; ali@416: prev = list; ali@416: list = next; ali@416: } ali@416: ali@416: return prev; ali@416: } ali@416: ali@416: /* ali@416: * All action_ functions take 1 action and return a list of primitive ali@416: * actions they took (such that the first action in the list is the ali@416: * last action they took). Primitive actions are always reversable. ali@416: * ali@416: * On failure, an error should be set on the atomic object (if it is ali@416: * not already set), the action freed and NULL returned. ali@416: * ali@416: * Whether they succeed or fail, they take ownership of the passed ali@416: * action and should thus either free it or return it back to their ali@416: * caller as appropriate. ali@416: * ali@416: * A NULL return means that nothing was done which may, or may not, ali@416: * indicate an error. ali@416: */ ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_make_dirs(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: char buffer[PATH_MAX], *p; ali@416: const char *slash, *next; ali@416: struct atomic_action *primitives = NULL; ali@416: struct atomic_action *prim; ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: strcpy(buffer, action->args.u.make_dirs.root); ali@416: p = buffer + strlen(buffer); ali@416: slash = action->args.path; ali@416: for (; *slash != '\0'; slash = next) { ali@416: #ifdef MSWIN_API ali@416: next = strpbrk(slash + 1, "/\\"); ali@416: #else ali@416: next = strchr(slash + 1, '/'); ali@416: #endif ali@416: if (next == NULL) ali@416: break; ali@416: ali@416: memcpy(p, slash, next - slash); ali@416: p += next - slash; ali@416: *p = '\0'; ali@416: ali@416: if (razor_valid_root_name(buffer)) ali@416: continue; ali@416: ali@416: prim = atomic_action_new(ACTION_CREATE_DIR); ali@416: prim->args.path = strdup(buffer); ali@416: prim->args.u.create_dir.mode = S_IRWXU | S_IRWXG | S_IRWXO; ali@416: primitives = atomic_action_list_prepend(primitives, prim); ali@416: } ali@416: primitives = atomic_action_list_reverse(primitives); ali@416: ali@416: return atomic_action_do(atomic, primitives); ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_remove(struct razor_atomic *atomic, struct atomic_action *action) ali@416: { ali@416: #ifdef MSWIN_API ali@416: wchar_t *path; ali@416: _WDIR *dir; ali@416: #else ali@416: DIR *dir; ali@416: #endif ali@416: struct atomic_action *prim; ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: /* ali@416: * Non-empty directories should NOT be removed ali@416: */ ali@416: #ifdef MSWIN_API ali@416: path = razor_utf8_to_utf16(action->args.path, -1); ali@416: ali@416: dir = _wopendir(path); ali@416: if (dir && _wreaddir(dir)) { ali@416: atomic_action_free(action); ali@416: action = NULL; ali@416: } ali@416: _wclosedir(dir); ali@416: #else ali@416: dir = opendir(action->args.path); ali@416: if (dir && readdir(dir)) { ali@416: atomic_action_free(action); ali@416: action = NULL; ali@416: } ali@416: closedir(dir); ali@416: #endif ali@416: ali@416: if (action) { ali@416: prim = atomic_action_new(ACTION_MOVE); ali@416: prim->args.path = strdup(action->args.path); ali@416: prim->args.u.move.dest = atomic_action_attic_tmpnam(atomic); ali@416: ali@416: atomic_action_free(action); ali@416: } else ali@416: prim = NULL; ali@416: ali@416: return prim ? atomic_action_do(atomic, prim) : NULL; ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_create_dir(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: mode_t mode; ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: mode = action->args.u.create_dir.mode & (S_IRWXU | S_IRWXG | S_IRWXO); ali@416: ali@416: if (!mkdir(action->args.path, mode)) ali@416: return 0; ali@416: ali@416: if (errno != EEXIST || chmod(action->args.path, mode) < 0) { ali@423: if (!atomic->error) ali@423: atomic->error = razor_error_new_str(action->args.path, ali@423: strerror(errno)); ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: return action; ali@416: } ali@416: ali@416: static struct atomic_action *atomic_action_rmdir(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: if (rmdir(action->args.path) < 0) { ali@423: if (!atomic->error) ali@423: atomic->error = razor_error_new_str(action->args.path, ali@423: strerror(errno)); ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } else ali@416: return action; ali@416: } ali@416: ali@416: #if HAVE_SYMLINK ali@416: static struct atomic_action * ali@416: atomic_action_create_symlink(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: int r; ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: r = symlink(action->args.u.create_symlink.target, action->args.path); ali@416: if (r < 0) { ali@423: if (!atomic->error) ali@423: atomic->error = razor_error_new_str(action->args.path, ali@423: strerror(errno)); ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: return action; ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_remove_symlink(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: if (unlink(action->args.path) < 0) { ali@423: if (!atomic->error) ali@423: atomic->error = razor_error_new_str(action->args.path, ali@423: strerror(errno)); ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: return action; ali@416: } ali@416: #endif ali@416: ali@416: static int ali@416: move_file(struct razor_atomic *atomic, const char *path, const char *dest) ali@416: { ali@416: #ifdef MSWIN_API ali@416: wchar_t *oldbuf, *newbuf; ali@416: const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING; ali@416: ali@416: newbuf = razor_utf8_to_utf16(dest, -1); ali@416: oldbuf = razor_utf8_to_utf16(path, -1); ali@416: ali@416: /* ali@416: * Passing MOVEFILE_REPLACE_EXISTING to MoveFileEx() will ali@416: * cover every case we care about _except_ replacing an empty ali@416: * directory with a file. Calling RemoveDirectory() will deal ali@416: * with this case while having no effect in all other cases. ali@416: */ ali@416: (void)RemoveDirectoryW(newbuf); ali@416: ali@416: if (!MoveFileExW(oldbuf, newbuf, flags)) { ali@423: if (!atomic->error) ali@423: atomic->error = razor_error_new_mswin(newbuf, ali@423: GetLastError()); ali@416: return -1; ali@416: } ali@416: ali@416: free(newbuf); ali@416: free(oldbuf); ali@416: #else ali@416: if (rename(path, dest)) { ali@423: if (!atomic->error) ali@423: atomic->error = razor_error_new_str(dest, ali@423: strerror(errno)); ali@416: return -1; ali@416: } ali@416: #endif ali@416: ali@416: return 0; ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_move(struct razor_atomic *atomic, struct atomic_action *action) ali@416: { ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: if (move_file(atomic, action->args.path, action->args.u.move.dest)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: return action; ali@416: } ali@416: ali@416: static struct atomic_action * ali@416: atomic_action_unmove(struct razor_atomic *atomic, struct atomic_action *action) ali@416: { ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: if (move_file(atomic, action->args.u.move.dest, action->args.path)) { ali@416: atomic_action_free(action); ali@416: return NULL; ali@416: } ali@416: ali@416: return action; ali@416: } ali@416: ali@416: static struct atomic_action *atomic_action_apply(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: switch(action->type) { ali@416: case ACTION_MAKE_DIRS: ali@416: action = atomic_action_make_dirs(atomic, action); ali@416: break; ali@416: case ACTION_REMOVE: ali@416: action = atomic_action_remove(atomic, action); ali@416: break; ali@416: case ACTION_CREATE_DIR: ali@416: action = atomic_action_create_dir(atomic, action); ali@416: break; ali@416: case ACTION_MOVE: ali@416: action = atomic_action_move(atomic, action); ali@416: break; ali@416: #if HAVE_SYMLINK ali@416: case ACTION_CREATE_SYMLINK: ali@416: action = atomic_action_create_symlink(atomic, action); ali@416: break; ali@416: #endif ali@416: } ali@416: return action; ali@416: } ali@416: ali@416: static struct atomic_action *atomic_action_reverse(struct razor_atomic *atomic, ali@416: struct atomic_action *action) ali@416: { ali@416: switch(action->type) { ali@416: case ACTION_MAKE_DIRS: ali@416: case ACTION_REMOVE: ali@416: /* Complex actions: should never happen */ ali@416: break; ali@416: case ACTION_CREATE_DIR: ali@416: action = atomic_action_rmdir(atomic, action); ali@416: break; ali@416: case ACTION_MOVE: ali@416: action = atomic_action_unmove(atomic, action); ali@416: break; ali@416: #if HAVE_SYMLINK ali@416: case ACTION_CREATE_SYMLINK: ali@416: action = atomic_action_remove_symlink(atomic, action); ali@416: break; ali@416: #endif ali@416: } ali@416: return action; ali@416: } ali@416: ali@416: /* ali@416: * Note that undo has no error checking. ali@416: */ ali@416: ali@416: void atomic_action_undo(struct razor_atomic *atomic, ali@416: struct atomic_action *actions) ali@416: { ali@416: struct atomic_action *a; ali@416: ali@416: atomic->in_undo = 1; ali@416: ali@416: while (actions) { ali@416: a = atomic_action_list_pop_head(&actions); ali@416: a = atomic_action_reverse(atomic, a); ali@416: atomic_action_free(a); ali@416: } ali@416: ali@416: atomic->in_undo = 0; ali@416: } ali@416: ali@416: struct atomic_action *atomic_action_do(struct razor_atomic *atomic, ali@416: struct atomic_action *actions) ali@416: { ali@416: struct atomic_action *done = NULL, *a; ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_free(actions); ali@416: return NULL; ali@416: } ali@416: ali@416: while (actions) { ali@416: a = atomic_action_list_pop_head(&actions); ali@416: a = atomic_action_apply(atomic, a); ali@416: if (a) ali@416: done = atomic_action_list_concat(a, done); ali@416: if (razor_atomic_in_error_state(atomic)) { ali@416: atomic_action_undo(atomic, done); ali@416: done = NULL; ali@416: atomic_action_free(actions); ali@416: } ali@416: } ali@416: ali@416: return done; ali@416: } ali@416: ali@416: #endif /* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */