/* * Copyright (C) 2011, 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 HAVE_WINDOWS_KTM #include #include #include #include #include #include #include #include #include #include #include "razor.h" #include "razor-internal.h" static int razor_valid_root_name2(const wchar_t *name) { if (razor_allow_all_root_names()) return !wcschr(name, '/'); return RAZOR_ASCII_ISALPHA(name[0]) && name[1] == ':' && name[2] == '\0'; } struct razor_wstr { wchar_t *str; int len, allocated; }; static struct razor_wstr * razor_wstr_create(const char *init, int len) { int n; struct razor_wstr *wstr; wstr = malloc(sizeof(struct razor_wstr)); n = MultiByteToWideChar(CP_UTF8, 0, init, len, NULL, 0); if (len >= 0 && init[len]) wstr->len = n++; else wstr->len = n - 1; wstr->allocated = n * 2; wstr->str = malloc(wstr->allocated * sizeof(wchar_t)); if (!wstr->str) { free(wstr); return NULL; } (void)MultiByteToWideChar(CP_UTF8, 0, init, len, wstr->str, n); if (len >= 0 && init[len]) wstr->str[wstr->len] = 0; return wstr; } static int razor_wstr_append(struct razor_wstr *wstr, const char *s, int len) { int n, allocated; wchar_t *str; n = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0); if (len < 0 || !s[len]) n--; if (wstr->allocated <= wstr->len + n) { allocated = (wstr->len + n + 1) * 2; str = realloc(wstr->str, allocated * sizeof(wchar_t)); if (!str) return -1; wstr->allocated = allocated; wstr->str = str; } (void)MultiByteToWideChar(CP_UTF8, 0, s, len, wstr->str + wstr->len, n); wstr->len += n; wstr->str[wstr->len] = 0; return 0; } static void razor_wstr_destroy(struct razor_wstr *wstr) { free(wstr->str); free(wstr); } RAZOR_EXPORT struct razor_atomic * razor_atomic_open(const char *description) { wchar_t *buf; struct razor_atomic *atomic; atomic = zalloc(sizeof *atomic); buf = razor_utf8_to_utf16(description, -1); atomic->transaction = CreateTransaction(NULL, 0, TRANSACTION_DO_NOT_PROMOTE, 0, 0, 0, buf); free(buf); return atomic; } RAZOR_EXPORT int razor_atomic_commit(struct razor_atomic *atomic) { int retval; if (razor_atomic_in_error_state(atomic)) return -1; retval = !CommitTransaction(atomic->transaction); if (retval) { razor_set_error_mswin(&atomic->error, NULL, GetLastError()); RollbackTransaction(atomic->transaction); } CloseHandle(atomic->transaction); atomic->transaction = INVALID_HANDLE_VALUE; return retval; } RAZOR_EXPORT void razor_atomic_destroy(struct razor_atomic *atomic) { int i; for(i = 0; i < atomic->n_files; i++) { if (atomic->files[i].h != INVALID_HANDLE_VALUE) { CloseHandle(atomic->files[i].h); free(atomic->files[i].path); } } free(atomic->files); if (atomic->transaction != INVALID_HANDLE_VALUE) { RollbackTransaction(atomic->transaction); CloseHandle(atomic->transaction); } if (atomic->error) razor_error_free(atomic->error); free(atomic); } /* * If root_uri is empty, then uri can be a URI (but not a relative-ref; ie., * a scheme must be specified). If root_uri is non-empty, then it must be a * URI (but not a relative-ref) and uri must be a non-empty path. */ RAZOR_EXPORT int razor_atomic_make_dirs(struct razor_atomic *atomic, const char *root_uri, const char *uri) { struct razor_wstr *buffer; const char *slash, *next; WIN32_FILE_ATTRIBUTE_DATA fa; DWORD err; int r, creating = 0; struct razor_uri ru; struct razor_error *tmp_error = NULL; char *s, *root, *path; if (razor_atomic_in_error_state(atomic)) return -1; if (!*root_uri) { if (razor_uri_parse(&ru, uri, &tmp_error)) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } s = SKIP_DRIVE_LETTER(ru.path); if (*s == '/') s++; *s = '\0'; s = razor_uri_recompose(&ru); uri += strlen(s); free(s); root = razor_path_from_parsed_uri(&ru, &tmp_error); razor_uri_destroy(&ru); if (!root) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } path = razor_path_from_uri(uri, &tmp_error); if (!path) { razor_atomic_propagate_error(atomic, tmp_error, NULL); free(root); return -1; } } else { root = razor_path_from_uri(root_uri, &tmp_error); if (!root) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } s = razor_concat("file:", uri, NULL); path = razor_path_from_uri(s, &tmp_error); free(s); if (!path) { razor_atomic_propagate_error(atomic, tmp_error, NULL); free(root); return -1; } /* * Arguably, razor_atomic_make_dirs(a, "file:///", "c:/xxx") * is wrong, and we should be called as: * razor_atomic_make_dirs(a, "file:///c:/", "xxx") * but cope with it anyway. */ s = SKIP_DRIVE_LETTER(path); if (!strcmp(root, "/") && *s == '/') { free(root); root = strdup(""); } } buffer = razor_wstr_create(root, -1); free(root); slash = buffer->len ? SKIP_DRIVE_LETTER(path) : path; for (; *slash != '\0'; slash = next) { next = strchr(slash + 1, '/'); if (next == NULL) break; razor_wstr_append(buffer, slash, next - slash); if (!creating) { if (razor_valid_root_name2(buffer->str)) continue; r = GetFileAttributesTransactedW(buffer->str, GetFileExInfoStandard, &fa, atomic->transaction); if (!r) { err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND) { creating = 1; } else { razor_set_error_mswin(&atomic->error, buffer->str, err); razor_wstr_destroy(buffer); free(path); return -1; } } else if (!(fa.dwFileAttributes& FILE_ATTRIBUTE_DIRECTORY)) { razor_set_error2(&atomic->error, RAZOR_MSWIN_ERROR, ERROR_DIRECTORY, buffer->str, "Not a directory"); razor_wstr_destroy(buffer); free(path); return -1; } } if (creating) { if (!CreateDirectoryTransactedW(NULL, buffer->str, NULL, atomic->transaction)) { razor_set_error_mswin(&atomic->error, buffer->str, GetLastError()); razor_wstr_destroy(buffer); free(path); return -1; } /* FIXME: What to do about permissions for dirs we * have to create but are not in the cpio archive? */ } } razor_wstr_destroy(buffer); free(path); return 0; } RAZOR_EXPORT int razor_atomic_remove(struct razor_atomic *atomic, const char *uri) { struct razor_error *tmp_error = NULL; char *path; wchar_t *buf; DWORD err; if (razor_atomic_in_error_state(atomic)) return -1; path = razor_path_from_uri(uri, &tmp_error); if (!path) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } buf = razor_utf8_to_utf16(path, -1); free(path); if (DeleteFileTransactedW(buf, atomic->transaction)) { free(buf); return 0; } err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { free(buf); return 0; } if (SetFileAttributesTransactedW(buf, FILE_ATTRIBUTE_NORMAL, atomic->transaction)) { if (DeleteFileTransactedW(buf, atomic->transaction)) { free(buf); return 0; } err = GetLastError(); } if (RemoveDirectoryTransactedW(buf, atomic->transaction) || GetLastError() == ERROR_DIR_NOT_EMPTY) { free(buf); return 0; } /* * It would be tempting to use: * MoveFileEx(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) * but unless we can guarantee that the system will be rebooted * before we (or some other application) write another file with the * same path, this is likely to cause more problems than it solves. */ razor_set_error_mswin(&atomic->error, buf, err); free(buf); return -1; } RAZOR_EXPORT int razor_atomic_rename_file(struct razor_atomic *atomic, const char *old_uri, const char *new_uri) { struct razor_error *tmp_error = NULL; char *oldpath, *newpath; wchar_t *oldbuf, *newbuf; const DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING; if (razor_atomic_in_error_state(atomic)) return -1; newpath = razor_path_from_uri(new_uri, &tmp_error); if (!newpath) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } oldpath = razor_path_from_uri(old_uri, &tmp_error); if (!oldpath) { razor_atomic_propagate_error(atomic, tmp_error, NULL); free(newpath); return -1; } newbuf = razor_utf8_to_utf16(newpath, -1); oldbuf = razor_utf8_to_utf16(oldpath, -1); free(newpath); free(oldpath); /* * Passing MOVEFILE_REPLACE_EXISTING to MoveFileTransaction() will * cover every case we care about _except_ replacing an empty * directory with a file. Calling RemoveDirectoryTransacted() will deal * with this case while having no effect in all other cases. */ (void)RemoveDirectoryTransactedW(newbuf, atomic->transaction); if (!MoveFileTransactedW(oldbuf, newbuf, NULL, NULL, flags, atomic->transaction)) razor_set_error_mswin(&atomic->error, newbuf, GetLastError()); free(newbuf); free(oldbuf); return razor_atomic_in_error_state(atomic); } RAZOR_EXPORT int razor_atomic_create_dir(struct razor_atomic *atomic, const char *uri, mode_t mode) { struct razor_error *tmp_error = NULL; char *dirname; wchar_t *buf; DWORD err; WIN32_FILE_ATTRIBUTE_DATA fa; if (razor_atomic_in_error_state(atomic)) return -1; dirname = razor_path_from_uri(uri, &tmp_error); if (!dirname) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } buf = razor_utf8_to_utf16(dirname, -1); free(dirname); if (!CreateDirectoryTransactedW(NULL, buf, NULL, atomic->transaction)) { err = GetLastError(); if (err != ERROR_FILE_EXISTS && err != ERROR_ALREADY_EXISTS) { abort: razor_set_error_mswin(&atomic->error, buf, err); free(buf); return -1; } if (!GetFileAttributesTransactedW(buf, GetFileExInfoStandard, &fa, atomic->transaction)) goto abort; if (!(fa.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) { if (razor_atomic_remove(atomic, uri)) { free(buf); return -1; } if (!CreateDirectoryTransactedW(NULL, buf, NULL, atomic->transaction)) { err = GetLastError(); goto abort; } } } free(buf); return 0; } RAZOR_EXPORT int razor_atomic_create_symlink(struct razor_atomic *atomic, const char *target, const char *uri) { if (razor_atomic_in_error_state(atomic)) return -1; /* * This isn't true, but symbolic links under Windows 7 * need to know whether the target is a directory or not * and we don't always know that at the time when the * link is created, so it's a convienent lie for now. */ razor_set_error(&atomic->error, RAZOR_MSWIN_ERROR, ERROR_NOT_SUPPORTED, NULL, "Symbolic links not supported on this platform"); return -1; } RAZOR_EXPORT int razor_atomic_create_file(struct razor_atomic *atomic, const char *uri, mode_t mode) { struct razor_error *tmp_error = NULL; char *filename; DWORD attribs; struct razor_atomic_file *files; int i = atomic->n_files; if (razor_atomic_in_error_state(atomic)) return -1; filename = razor_path_from_uri(uri, &tmp_error); if (!filename) { razor_atomic_propagate_error(atomic, tmp_error, NULL); return -1; } files = realloc(atomic->files, (atomic->n_files+1) * sizeof(struct razor_atomic_file)); if (!files) { razor_set_error(&atomic->error, RAZOR_POSIX_ERROR, ENOMEM, NULL, "Not enough memory"); free(filename); return -1; } atomic->n_files++; atomic->files = files; files[i].path = razor_utf8_to_utf16(filename, -1); free(filename); /* * Passing CREATE_ALWAYS to CreateFileTransacted() will cover * every case we care about _except_ replacing an empty directory * with a file. Calling RemoveDirectoryTransacted() will deal * with this case while having no effect in all other cases. */ (void)RemoveDirectoryTransactedW(files[i].path, atomic->transaction); if (mode & S_IWUSR) attribs = FILE_ATTRIBUTE_NORMAL; else attribs = FILE_ATTRIBUTE_READONLY; files[i].h = CreateFileTransactedW(files[i].path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attribs, NULL, atomic->transaction, NULL, NULL); if (files[i].h == INVALID_HANDLE_VALUE) { razor_set_error_mswin(&atomic->error, files[i].path, GetLastError()); free(files[i].path); atomic->n_files--; return -1; } return i; } RAZOR_EXPORT int razor_atomic_write(struct razor_atomic *atomic, int handle, const void *data, size_t size) { DWORD written; if (razor_atomic_in_error_state(atomic)) return -1; assert(handle < atomic->n_files); assert(atomic->files[handle].h != INVALID_HANDLE_VALUE); while(size) { if (!WriteFile(atomic->files[handle].h, data, size, &written, NULL)) { razor_set_error_mswin(&atomic->error, atomic->files[handle].path, GetLastError()); (void)CloseHandle(atomic->files[handle].h); free(atomic->files[handle].path); atomic->files[handle].path = NULL; atomic->files[handle].h = INVALID_HANDLE_VALUE; return -1; } data += written; size -= written; } return 0; } RAZOR_EXPORT int razor_atomic_sync(struct razor_atomic *atomic, int handle) { HANDLE h; if (razor_atomic_in_error_state(atomic)) return -1; assert(handle < atomic->n_files); assert(atomic->files[handle].h != INVALID_HANDLE_VALUE); if (!CloseHandle(atomic->files[handle].h)) { razor_set_error_mswin(&atomic->error, atomic->files[handle].path, GetLastError()); free(atomic->files[handle].path); atomic->files[handle].path = NULL; atomic->files[handle].h = INVALID_HANDLE_VALUE; return -1; } h = CreateFileTransactedW(atomic->files[handle].path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL, atomic->transaction, NULL, NULL); atomic->files[handle].h = h; if (atomic->files[handle].h == INVALID_HANDLE_VALUE) { razor_set_error_mswin(&atomic->error, atomic->files[handle].path, GetLastError()); free(atomic->files[handle].path); atomic->files[handle].path = NULL; return -1; } return razor_atomic_in_error_state(atomic); } RAZOR_EXPORT int razor_atomic_close(struct razor_atomic *atomic, int handle) { if (razor_atomic_in_error_state(atomic)) return -1; assert(handle < atomic->n_files); assert(atomic->files[handle].h != INVALID_HANDLE_VALUE); if (!CloseHandle(atomic->files[handle].h)) razor_set_error_mswin(&atomic->error, atomic->files[handle].path, GetLastError()); free(atomic->files[handle].path); atomic->files[handle].path = NULL; atomic->files[handle].h = INVALID_HANDLE_VALUE; while(atomic->n_files > 0 && atomic->files[atomic->n_files-1].h == INVALID_HANDLE_VALUE) atomic->n_files--; return razor_atomic_in_error_state(atomic); } #endif /* HAVE_WINDOWS_KTM */