/* * Copyright (C) 2012, 2014 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 #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); } } closedir(dp); 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->actions) { atomic_action_free(atomic->actions); atomic->actions = NULL; } if (atomic->toplevel) { recursive_remove(atomic->toplevel); free(atomic->toplevel); atomic->toplevel = NULL; } if (atomic->error) razor_error_free(atomic->error); free(atomic->description); free(atomic); } #ifndef MSWIN_API static char *absolute_path(const char *path) { int len; char *result, *subpath, *p, *s, *t; result = realpath(path, NULL); if (!result && errno == ENOENT) { p = strdup(path); s = strrchr(p, '/'); while (s) { if (s == p) { result = strdup("/"); break; } *s = '\0'; subpath = realpath(p, NULL); if (subpath) { *s = '/'; len = strlen(subpath); result = malloc(len + strlen(s) + 1); memcpy(result, subpath, len); strcpy(result + len, s); break; } else if (errno != ENOENT) break; t = strrchr(p, '/'); *s = '/'; s = t; } if (!s) result = realpath(".", NULL); free(p); } return result; } #endif /* * 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 filesystem in * question, the best location is at the relevant mount point. * The most common case where this assumption fails is when * testing, when the current directory is a good choice. */ static int razor_atomic_set_toplevel_from_path(struct razor_atomic *atomic, const char *path) { #ifndef MSWIN_API dev_t filesystem; struct stat buf; #endif 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 { /* * Find the mount point (assuming we can write to the * whole filesystem). Otherwise stop at the first * unwritable directory and take one step back. */ char *s, *abspath, saved; int len, can_step_back = 0; abspath = absolute_path(path); if (!abspath) { atomic->error = razor_error_new_posix(path); return -1; } if (stat(abspath, &buf) < 0) { if (errno == ENOENT) filesystem = 0; else { atomic->error = razor_error_new_posix(abspath); free(abspath); return -1; } } else filesystem = buf.st_dev; len = strlen(abspath); while(len > 1 && (s = strrchr(abspath, '/'))) { if (s == abspath) { saved = s[1]; s[1] = '\0'; len = s + 1 - abspath; } else { s[0] = '\0'; len = s - abspath; } if (stat(abspath, &buf) < 0) { if (errno == ENOENT) continue; else { atomic->error = razor_error_new_posix(abspath); free(abspath); return -1; } } else if (!filesystem) filesystem = buf.st_dev; if (buf.st_dev != filesystem || access(abspath, W_OK)) { if (can_step_back) { if (s == abspath) s[1] = saved; else s[0] = '/'; } len = strlen(abspath); break; } else can_step_back = 1; } if (len == 1) len = 0; /* Avoid an unslightly double slash. */ atomic->toplevel = malloc(len + strlen("/.atomic-XXXXXX") + 1); memcpy(atomic->toplevel, abspath, len); strcpy(atomic->toplevel + len, "/.atomic-XXXXXX"); free(abspath); } #endif if (!mkdtemp(atomic->toplevel)) { int err = errno; #ifdef EACCES if (err == EACCES) { char *s = strdup("atomic-XXXXXX"); #ifndef MSWIN_API if (stat(".", &buf) < 0) { atomic->error = razor_error_new_posix("."); free(s); free(atomic->toplevel); atomic->toplevel = NULL; return -1; } if (buf.st_dev != filesystem) /* * Don't use a different filesystem. It will * only fail later on (in rename) and cause * an unhelpful error message (EXDEV). */ free(s); else #endif if (mkdtemp(s)) { free(atomic->toplevel); atomic->toplevel = s; return 0; } else free(s); } #endif atomic->error = razor_error_new_str(RAZOR_POSIX_ERROR, err, 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(RAZOR_POSIX_ERROR, ENOSYS, 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_posix(filename); 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 */