/* * 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" /* * 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->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 razor_atomic_in_error_state(atomic); } RAZOR_EXPORT void razor_atomic_destroy(struct razor_atomic *atomic) { if (atomic->toplevel) { recursive_remove(atomic->toplevel); free(atomic->toplevel); atomic->toplevel = NULL; } if (atomic->error) razor_error_free(atomic->error); free(atomic); } /* * We need a toplevel directory in which to hold temporary files * before they are committed. Since we can generally assume that * we have write permissions anywhere on the disk in question, * the best location is in the relevant root directory. The most * common case where this assumption fails is when testing, when * the current directory is a good choice. * It might be even better to find a mount point above path instead * but this is hard to do, and probably not worth the effort. */ static int razor_atomic_set_toplevel_from_path(struct razor_atomic *atomic, const char *path) { if (razor_atomic_in_error_state(atomic)) return -1; if (atomic->toplevel) return 0; #ifdef MSWIN_API if (path[0]=='\\' && path[1]=='\\' && path[2] && path[2]!='\\' && strchr(path+3,'\\')) { /* We have a UNC path: \\servername\sharename... */ const char *sharename, *root; int disklen; sharename = strchr(path+3,'\\')+1; root = strchr(sharename,'\\'); if (root) disklen = root - path; else disklen = strlen(path); atomic->toplevel = malloc(disklen + strlen("\\atomic-XXXXXX") + 1); memcpy(atomic->toplevel, path, disklen); strcpy(atomic->toplevel + disklen, "\\atomic-XXXXXX"); } else if ((*path>='A' && *path<='Z' || *path>='a' && *path<='z') && path[1]==':') { atomic->toplevel = strdup("X:\\atomic-XXXXXX"); *atomic->toplevel = *path; } else { DWORD n; wchar_t *buf; char *dir; n = GetCurrentDirectoryW(0, NULL); buf = malloc(n * sizeof(wchar_t)); if (GetCurrentDirectoryW(n, buf)) { dir = razor_utf16_to_utf8(buf, n - 1); razor_atomic_set_toplevel_from_path(atomic, dir); free(dir); free(buf); return; } else atomic->toplevel = strdup("C:\\atomic-XXXXXX"); free(buf); } #else atomic->toplevel = strdup("/.atomic-XXXXXX"); #endif if (!mkdtemp(atomic->toplevel)) { int err = errno; #ifdef EACCES if (err == EACCES) { char *s = strdup("atomic-XXXXXX"); if (mkdtemp(s)) { free(atomic->toplevel); atomic->toplevel = s; return 0; } else free(s); } #endif atomic->error = razor_error_new_str(atomic->toplevel, strerror(err)); free(atomic->toplevel); atomic->toplevel = NULL; } return !atomic->toplevel; } RAZOR_EXPORT int razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root, const char *path) { struct atomic_action *a; razor_atomic_set_toplevel_from_path(atomic, *root ? root : path); 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; razor_atomic_set_toplevel_from_path(atomic, path); 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; razor_atomic_set_toplevel_from_path(atomic, newpath); 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; razor_atomic_set_toplevel_from_path(atomic, dirname); 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; razor_atomic_set_toplevel_from_path(atomic, path); #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 atomic->error = razor_error_new_str(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; razor_atomic_set_toplevel_from_path(atomic, filename); if (razor_atomic_in_error_state(atomic)) return -1; 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) atomic->error = razor_error_new_str(filename, 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 */