From ca0a186a4b06408b9fd8f5c8b41777d0b330377d Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 14 Mar 2022 14:38:40 +0100 Subject: [PATCH] common: Add some file utils. Supports more advanced file operations than we'd normally need. Intended to be used by magic disk cache. Signed-off-by: Hans-Kristian Arntzen --- include/private/vkd3d_file_utils.h | 42 +++++++ libs/vkd3d-common/file_utils.c | 188 +++++++++++++++++++++++++++++ libs/vkd3d-common/meson.build | 1 + 3 files changed, 231 insertions(+) create mode 100644 include/private/vkd3d_file_utils.h create mode 100644 libs/vkd3d-common/file_utils.c diff --git a/include/private/vkd3d_file_utils.h b/include/private/vkd3d_file_utils.h new file mode 100644 index 00000000..013ac1d5 --- /dev/null +++ b/include/private/vkd3d_file_utils.h @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Hans-Kristian Arntzen for Valve Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __VKD3D_FILE_UTILS_H +#define __VKD3D_FILE_UTILS_H + +#include +#include +#include + +struct vkd3d_memory_mapped_file +{ + void *mapped; + size_t mapped_size; +}; + +/* On failure, ensures the struct is cleared to zero. + * A reference to the file is kept through the memory mapping. */ +bool vkd3d_file_map_read_only(const char *path, struct vkd3d_memory_mapped_file *file); +/* Clears out file on unmap. */ +void vkd3d_file_unmap(struct vkd3d_memory_mapped_file *file); +bool vkd3d_file_rename_overwrite(const char *from_path, const char *to_path); +bool vkd3d_file_rename_no_replace(const char *from_path, const char *to_path); +bool vkd3d_file_delete(const char *path); +FILE *vkd3d_file_open_exclusive_write(const char *path); + +#endif diff --git a/libs/vkd3d-common/file_utils.c b/libs/vkd3d-common/file_utils.c new file mode 100644 index 00000000..2eb31fdc --- /dev/null +++ b/libs/vkd3d-common/file_utils.c @@ -0,0 +1,188 @@ +/* + * Copyright 2022 Hans-Kristian Arntzen for Valve Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define VKD3D_DBG_CHANNEL VKD3D_DBG_CHANNEL_API + +#include "vkd3d_file_utils.h" +#include "vkd3d_debug.h" + +/* For disk cache. */ +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include + +bool vkd3d_file_rename_overwrite(const char *from_path, const char *to_path) +{ +#ifdef _WIN32 + DWORD code = ERROR_SUCCESS; + + if (!MoveFileA(from_path, to_path)) + { + code = GetLastError(); + if (code == ERROR_ALREADY_EXISTS) + { + code = ERROR_SUCCESS; + if (!ReplaceFileA(to_path, from_path, NULL, 0, NULL, NULL)) + code = GetLastError(); + } + } + + return code == ERROR_SUCCESS; +#else + return rename(from_path, to_path) == 0; +#endif +} + +bool vkd3d_file_rename_no_replace(const char *from_path, const char *to_path) +{ +#ifdef _WIN32 + DWORD code = ERROR_SUCCESS; + if (!MoveFileA(from_path, to_path)) + code = GetLastError(); + return code == ERROR_SUCCESS; +#else + return renameat2(AT_FDCWD, from_path, AT_FDCWD, to_path, RENAME_NOREPLACE) == 0; +#endif +} + +bool vkd3d_file_delete(const char *path) +{ +#ifdef _WIN32 + DWORD code = ERROR_SUCCESS; + if (!DeleteFileA(path)) + code = GetLastError(); + return code == ERROR_SUCCESS; +#else + return unlink(path) == 0; +#endif +} + +FILE *vkd3d_file_open_exclusive_write(const char *path) +{ +#ifdef _WIN32 + /* From Fossilize. AFAIK, there is no direct way to make this work with FILE interface, so have to roundtrip + * through jank POSIX layer. + * wbx kinda works, but Wine warns about it, despite it working anyways. + * Older MSVC runtimes do not support wbx. */ + FILE *file = NULL; + int fd; + fd = _open(path, _O_BINARY | _O_WRONLY | _O_CREAT | _O_EXCL | _O_TRUNC | _O_SEQUENTIAL, + _S_IWRITE | _S_IREAD); + if (fd >= 0) + { + file = _fdopen(fd, "wb"); + /* _fdopen takes ownership. */ + if (!file) + _close(fd); + } + return file; +#else + return fopen(path, "wbx"); +#endif +} + +void vkd3d_file_unmap(struct vkd3d_memory_mapped_file *file) +{ + if (file->mapped) + { +#ifdef _WIN32 + UnmapViewOfFile(file->mapped); +#else + munmap(file->mapped, file->mapped_size); +#endif + } + memset(file, 0, sizeof(*file)); +} + +bool vkd3d_file_map_read_only(const char *path, struct vkd3d_memory_mapped_file *file) +{ +#ifdef _WIN32 + DWORD size_hi, size_lo; + HANDLE file_mapping; + HANDLE handle; +#else + struct stat stat_buf; + int fd; +#endif + + file->mapped = NULL; + file->mapped_size = 0; + +#ifdef _WIN32 + handle = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + INVALID_HANDLE_VALUE); + if (handle == INVALID_HANDLE_VALUE) + goto out; + + size_lo = GetFileSize(handle, &size_hi); + file->mapped_size = size_lo | (((uint64_t)size_hi) << 32); + + file_mapping = CreateFileMappingA(handle, NULL, PAGE_READONLY, 0, 0, NULL); + if (file_mapping == INVALID_HANDLE_VALUE) + goto out; + + file->mapped = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, file->mapped_size); + CloseHandle(file_mapping); + file_mapping = INVALID_HANDLE_VALUE; + if (!file->mapped) + { + ERR("Failed to MapViewOfFile for %s.\n", path); + goto out; + } + +out: + if (handle != INVALID_HANDLE_VALUE) + CloseHandle(handle); +#else + fd = open(path, O_RDONLY); + if (fd < 0) + goto out; + + if (fstat(fd, &stat_buf) < 0) + { + ERR("Failed to fstat pipeline cache.\n"); + goto out; + } + + /* Map private to make sure we get CoW behavior in case someone clobbers + * the cache while in flight. We need to read data directly out of the cache. */ + file->mapped = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (file->mapped != MAP_FAILED) + file->mapped_size = stat_buf.st_size; + else + goto out; + +out: + if (fd >= 0) + close(fd); +#endif + + if (!file->mapped) + file->mapped_size = 0; + return file->mapped != NULL; +} diff --git a/libs/vkd3d-common/meson.build b/libs/vkd3d-common/meson.build index 44a5db6e..17adb794 100644 --- a/libs/vkd3d-common/meson.build +++ b/libs/vkd3d-common/meson.build @@ -4,6 +4,7 @@ vkd3d_common_src = [ 'utf8.c', 'profiling.c', 'string.c', + 'file_utils.c', ] vkd3d_common_lib = static_library('vkd3d_common', vkd3d_common_src, vkd3d_header_files,