ali@416: /* ali@444: * Copyright (C) 2012, 2014 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@423: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@416: #include ali@444: #include ali@416: #include "razor-internal.h" ali@416: ali@416: /* ali@416: * Emulated atomic support ali@416: * ali@416: * This implementation is better than nothing, but is certainly not atomic. ali@416: * It does have a couple of advantages over atomic-none: ali@416: * - If a file operation fails while a package is being installed we ali@416: * have a good chance of being able to rollback the transaction to ali@416: * a well-known state. ali@416: * - We behave similarly to atomic-ktm in that changes are not visible ali@416: * on disk to non-atomic operations (eg., scripts) until the atomic ali@416: * is committed. This makes the testsuite more likely to pick up ali@416: * problems that would otherwise only be found when using razor on ali@416: * an MS-Windows system which supports KTM. ali@416: */ ali@416: ali@416: #ifndef O_BINARY ali@416: #define O_BINARY 0 ali@416: #endif ali@416: ali@416: static void recursive_remove(const char *directory) ali@416: { ali@416: DIR *dp; ali@416: struct dirent *dirp; ali@416: char *buf; ali@416: ali@416: dp = opendir(directory); ali@416: while((dirp = readdir(dp))) { ali@416: if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) { ali@416: buf = malloc(strlen(directory) + strlen(dirp->d_name) ali@416: + 2); ali@416: sprintf(buf, "%s/%s", directory, dirp->d_name); ali@416: if (remove(buf) < 0) ali@416: recursive_remove(buf); ali@416: free(buf); ali@416: } ali@416: } ali@416: ali@416: rmdir(directory); ali@416: } ali@416: ali@416: RAZOR_EXPORT struct razor_atomic *razor_atomic_open(const char *description) ali@416: { ali@416: struct razor_atomic *atomic; ali@416: ali@416: atomic = zalloc(sizeof *atomic); ali@416: ali@416: atomic->description = strdup(description); ali@416: ali@416: return atomic; ali@416: } ali@416: ali@416: RAZOR_EXPORT int razor_atomic_commit(struct razor_atomic *atomic) ali@416: { ali@416: struct atomic_action *actions; ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: if (atomic->actions) { ali@416: actions = atomic_action_list_reverse(atomic->actions); ali@416: atomic->actions = NULL; ali@416: actions = atomic_action_do(atomic, actions); ali@416: atomic_action_free(actions); ali@416: } ali@416: ali@416: if (atomic->toplevel) { ali@416: recursive_remove(atomic->toplevel); ali@416: free(atomic->toplevel); ali@416: atomic->toplevel = NULL; ali@416: } ali@416: ali@423: return razor_atomic_in_error_state(atomic); ali@416: } ali@416: ali@416: RAZOR_EXPORT void razor_atomic_destroy(struct razor_atomic *atomic) ali@416: { ali@416: if (atomic->toplevel) { ali@416: recursive_remove(atomic->toplevel); ali@416: free(atomic->toplevel); ali@416: atomic->toplevel = NULL; ali@416: } ali@416: ali@423: if (atomic->error) ali@423: razor_error_free(atomic->error); ali@423: ali@416: free(atomic); ali@416: } ali@416: ali@444: #ifndef MSWIN_API ali@444: static char *absolute_path(const char *path) ali@444: { ali@444: int len; ali@444: char *result, *subpath, *p, *s, *t; ali@444: ali@444: result = realpath(path, NULL); ali@444: ali@444: if (!result && errno == ENOENT) { ali@444: p = strdup(path); ali@444: s = strrchr(p, '/'); ali@444: ali@444: while (s) { ali@444: if (s == p) { ali@444: result = strdup("/"); ali@444: break; ali@444: } ali@444: ali@444: *s = '\0'; ali@444: subpath = realpath(p, NULL); ali@444: ali@444: if (subpath) { ali@444: *s = '/'; ali@444: len = strlen(subpath); ali@444: result = malloc(len + strlen(s) + 1); ali@444: memcpy(result, subpath, len); ali@444: strcpy(result + len, s); ali@444: break; ali@444: } else if (errno != ENOENT) ali@444: break; ali@444: ali@444: t = strrchr(p, '/'); ali@444: *s = '/'; ali@444: s = t; ali@444: } ali@444: ali@444: if (!s) ali@444: result = realpath(".", NULL); ali@444: ali@444: free(p); ali@444: } ali@444: ali@444: return result; ali@444: } ali@444: #endif ali@444: ali@435: /* ali@435: * We need a toplevel directory in which to hold temporary files ali@435: * before they are committed. Since we can generally assume that ali@444: * we have write permissions anywhere on the filesystem in ali@444: * question, the best location is at the relevant mount point. ali@444: * The most common case where this assumption fails is when ali@444: * testing, when the current directory is a good choice. ali@435: */ ali@435: ali@435: static int ali@435: razor_atomic_set_toplevel_from_path(struct razor_atomic *atomic, ali@435: const char *path) ali@435: { ali@444: #ifndef MSWIN_API ali@444: dev_t filesystem; ali@444: struct stat buf; ali@444: #endif ali@444: ali@435: if (razor_atomic_in_error_state(atomic)) ali@435: return -1; ali@435: ali@435: if (atomic->toplevel) ali@435: return 0; ali@435: ali@435: #ifdef MSWIN_API ali@435: if (path[0]=='\\' && path[1]=='\\' && path[2] && path[2]!='\\' ali@435: && strchr(path+3,'\\')) { ali@435: /* We have a UNC path: \\servername\sharename... */ ali@435: const char *sharename, *root; ali@435: int disklen; ali@435: ali@435: sharename = strchr(path+3,'\\')+1; ali@435: root = strchr(sharename,'\\'); ali@435: if (root) ali@435: disklen = root - path; ali@435: else ali@435: disklen = strlen(path); ali@435: ali@435: atomic->toplevel = ali@435: malloc(disklen + strlen("\\atomic-XXXXXX") + 1); ali@435: memcpy(atomic->toplevel, path, disklen); ali@435: strcpy(atomic->toplevel + disklen, "\\atomic-XXXXXX"); ali@435: } else if ((*path>='A' && *path<='Z' || *path>='a' && *path<='z') && ali@435: path[1]==':') { ali@435: atomic->toplevel = strdup("X:\\atomic-XXXXXX"); ali@435: *atomic->toplevel = *path; ali@435: } else { ali@435: DWORD n; ali@435: wchar_t *buf; ali@435: char *dir; ali@435: ali@435: n = GetCurrentDirectoryW(0, NULL); ali@435: buf = malloc(n * sizeof(wchar_t)); ali@435: ali@435: if (GetCurrentDirectoryW(n, buf)) { ali@435: dir = razor_utf16_to_utf8(buf, n - 1); ali@435: razor_atomic_set_toplevel_from_path(atomic, dir); ali@435: ali@435: free(dir); ali@435: free(buf); ali@435: return; ali@435: } else ali@435: atomic->toplevel = strdup("C:\\atomic-XXXXXX"); ali@435: ali@435: free(buf); ali@435: } ali@435: #else ali@444: { ali@444: /* ali@444: * Find the mount point (assuming we can write to the ali@444: * whole filesystem). Otherwise stop at the first ali@444: * unwritable directory and take one step back. ali@444: */ ali@444: char *s, *abspath, saved; ali@444: int len; ali@444: ali@444: abspath = absolute_path(path); ali@444: if (!abspath) { ali@444: atomic->error = razor_error_new_str(path, ali@444: strerror(errno)); ali@444: return -1; ali@444: } ali@444: ali@444: if (stat(abspath, &buf) < 0) { ali@444: atomic->error = razor_error_new_str(abspath, ali@444: strerror(errno)); ali@444: free(abspath); ali@444: return -1; ali@444: } ali@444: filesystem = buf.st_dev; ali@444: ali@444: len = strlen(abspath); ali@444: while(len > 1 && (s = strrchr(abspath, '/'))) { ali@444: if (s == abspath) { ali@444: saved = s[1]; ali@444: s[1] = '\0'; ali@444: len = s + 1 - abspath; ali@444: } else { ali@444: s[0] = '\0'; ali@444: len = s - abspath; ali@444: } ali@444: ali@444: if (stat(abspath, &buf) < 0) { ali@444: atomic->error = ali@444: razor_error_new_str(abspath, strerror(errno)); ali@444: free(abspath); ali@444: return -1; ali@444: } ali@444: ali@444: if (buf.st_dev != filesystem || access(abspath, W_OK)) { ali@444: if (s == abspath) ali@444: s[1] = saved; ali@444: else ali@444: s[0] = '/'; ali@444: len = strlen(abspath); ali@444: break; ali@444: } ali@444: } ali@444: ali@444: if (len == 1) ali@444: len = 0; /* Avoid an unslightly double slash. */ ali@444: atomic->toplevel = malloc(len + strlen("/.atomic-XXXXXX") + 1); ali@444: memcpy(atomic->toplevel, abspath, len); ali@444: strcpy(atomic->toplevel + len, "/.atomic-XXXXXX"); ali@444: ali@444: free(abspath); ali@444: } ali@435: #endif ali@435: ali@435: if (!mkdtemp(atomic->toplevel)) { ali@435: int err = errno; ali@435: ali@435: #ifdef EACCES ali@435: if (err == EACCES) { ali@435: char *s = strdup("atomic-XXXXXX"); ali@435: ali@444: #ifndef MSWIN_API ali@444: if (stat(".", &buf) < 0) { ali@444: atomic->error = ali@444: razor_error_new_str(".", strerror(errno)); ali@444: free(s); ali@444: free(atomic->toplevel); ali@444: atomic->toplevel = NULL; ali@444: return -1; ali@444: } ali@444: if (buf.st_dev != filesystem) ali@444: /* ali@444: * Don't use a different filesystem. It will ali@444: * only fail later on (in rename) and cause ali@444: * an unhelpful error message (EXDEV). ali@444: */ ali@444: free(s); ali@444: else ali@444: #endif ali@435: if (mkdtemp(s)) { ali@435: free(atomic->toplevel); ali@435: atomic->toplevel = s; ali@435: return 0; ali@435: } else ali@435: free(s); ali@435: } ali@435: #endif ali@435: ali@435: atomic->error = razor_error_new_str(atomic->toplevel, ali@435: strerror(err)); ali@435: ali@435: free(atomic->toplevel); ali@435: atomic->toplevel = NULL; ali@435: } ali@435: ali@435: return !atomic->toplevel; ali@435: } ali@435: ali@416: RAZOR_EXPORT int ali@416: razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root, ali@416: const char *path) ali@416: { ali@416: struct atomic_action *a; ali@416: ali@435: razor_atomic_set_toplevel_from_path(atomic, *root ? root : path); ali@435: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: a = atomic_action_new(ACTION_MAKE_DIRS); ali@416: a->args.path = strdup(path); ali@416: a->args.u.make_dirs.root = strdup(root); ali@416: atomic->actions = atomic_action_list_prepend(atomic->actions, a); ali@416: ali@416: return 0; ali@416: } ali@416: ali@416: RAZOR_EXPORT int ali@416: razor_atomic_remove(struct razor_atomic *atomic, const char *path) ali@416: { ali@416: struct atomic_action *a; ali@416: ali@435: razor_atomic_set_toplevel_from_path(atomic, path); ali@435: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: a = atomic_action_new(ACTION_REMOVE); ali@416: a->args.path = strdup(path); ali@416: atomic->actions = atomic_action_list_prepend(atomic->actions, a); ali@416: ali@416: return 0; ali@416: } ali@416: ali@416: RAZOR_EXPORT int ali@416: razor_atomic_rename_file(struct razor_atomic *atomic, const char *oldpath, ali@416: const char *newpath) ali@416: { ali@416: struct atomic_action *a; ali@416: ali@435: razor_atomic_set_toplevel_from_path(atomic, newpath); ali@435: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: a = atomic_action_new(ACTION_MOVE); ali@416: a->args.path = strdup(oldpath); ali@416: a->args.u.move.dest = strdup(newpath); ali@416: atomic->actions = atomic_action_list_prepend(atomic->actions, a); ali@416: ali@416: return 0; ali@416: } ali@416: ali@416: RAZOR_EXPORT int ali@416: razor_atomic_create_dir(struct razor_atomic *atomic, const char *dirname, ali@416: mode_t mode) ali@416: { ali@416: struct atomic_action *a; ali@416: ali@435: razor_atomic_set_toplevel_from_path(atomic, dirname); ali@435: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: a = atomic_action_new(ACTION_CREATE_DIR); ali@416: a->args.path = strdup(dirname); ali@416: a->args.u.create_dir.mode = mode; ali@416: atomic->actions = atomic_action_list_prepend(atomic->actions, a); ali@416: ali@416: return 0; ali@416: } ali@416: ali@416: RAZOR_EXPORT int ali@416: razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target, ali@416: const char *path) ali@416: { ali@416: #if HAVE_SYMLINK ali@416: struct atomic_action *a; ali@435: ali@435: razor_atomic_set_toplevel_from_path(atomic, path); ali@416: #endif ali@416: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: #if HAVE_SYMLINK ali@416: a = atomic_action_new(ACTION_CREATE_SYMLINK); ali@416: a->args.path = strdup(path); ali@416: a->args.u.create_symlink.target = strdup(target); ali@416: atomic->actions = atomic_action_list_prepend(atomic->actions, a); ali@416: ali@416: return 0; ali@416: #else ali@423: atomic->error = razor_error_new_str(NULL, ali@423: "Symbolic links not supported " ali@423: "on this platform"); ali@416: ali@416: return -1; ali@416: #endif ali@416: } ali@416: ali@416: RAZOR_EXPORT int ali@416: razor_atomic_create_file(struct razor_atomic *atomic, const char *filename, ali@416: mode_t mode) ali@416: { ali@416: int fd; ali@416: struct atomic_action *a; ali@416: char *tmpnam; ali@416: ali@435: razor_atomic_set_toplevel_from_path(atomic, filename); ali@435: ali@416: if (razor_atomic_in_error_state(atomic)) ali@416: return -1; ali@416: ali@416: tmpnam = atomic_action_attic_tmpnam(atomic); ali@416: fd = open(tmpnam, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, ali@416: mode & (S_IRWXU | S_IRWXG | S_IRWXO)); ali@416: ali@416: if (fd == -1) ali@423: atomic->error = razor_error_new_str(filename, strerror(errno)); ali@416: else { ali@416: a = atomic_action_new(ACTION_MOVE); ali@416: a->args.path = tmpnam; ali@416: a->args.u.move.dest = strdup(filename); ali@416: atomic->actions = atomic_action_list_prepend(atomic->actions, ali@416: a); ali@416: } ali@416: ali@416: return fd; ali@416: } ali@416: ali@416: #endif /* ENABLE_ATOMIC && !HAVE_WINDOWS_KTM */