/* * 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) atomic->error = razor_error_new_str(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) atomic->error = razor_error_new_str(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) atomic->error = razor_error_new_str(action->args.path, 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) atomic->error = razor_error_new_str(action->args.path, 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) atomic->error = razor_error_new_mswin(newbuf, GetLastError()); return -1; } free(newbuf); free(oldbuf); #else if (rename(path, dest)) { if (!atomic->error) atomic->error = razor_error_new_str(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(a); } } return done; } #endif /* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */