diff --git a/FEXCore/Source/CMakeLists.txt b/FEXCore/Source/CMakeLists.txt index d292e5ca7..bfed4ad59 100644 --- a/FEXCore/Source/CMakeLists.txt +++ b/FEXCore/Source/CMakeLists.txt @@ -4,6 +4,7 @@ set(FEXCORE_BASE_SRCS Interface/Config/Config.cpp Utils/Allocator.cpp Utils/FileLoading.cpp + Utils/FileUtils.cpp Utils/ForcedAssert.cpp Utils/LogManager.cpp Utils/SpinWaitLock.cpp diff --git a/FEXCore/Source/Utils/FileUtils.cpp b/FEXCore/Source/Utils/FileUtils.cpp new file mode 100644 index 000000000..5d5c644e4 --- /dev/null +++ b/FEXCore/Source/Utils/FileUtils.cpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +namespace FEXCore::FileUtils { +#ifndef _WIN32 +static inline bool unlinkat(int fd, const char* path, bool dir) { + if (::unlinkat(fd, path, dir ? AT_REMOVEDIR : 0) == -1) { + return errno == ENOENT; + } + return true; +} + +static bool RecursiveRemoveDirectory(int parent_fd, const char* Directory) { + // Don't follow symlinks and ensure it closes on exec. + constexpr int DIR_FLAGS = O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC; + int dir_fd = ::openat(parent_fd, Directory, DIR_FLAGS); + + if (dir_fd == -1) { + if (errno == ENOENT) { + // Probably raced something. Non-error. + return true; + } + } + + // Walk the directory listing. + bool Result = true; + // Four pages arbitrary chosen to be a balance between NFS wanting to return data in page-size granules and + // local filesystems returning arbitrary sizes. + size_t dirent_size = 4096 * 4; + uint8_t* dirent_buffer = reinterpret_cast(FEXCore::Allocator::malloc(dirent_size)); + + if (!dirent_buffer) { + // Ran out of memory? + Result = false; + goto end; + } + + while (true) { + ssize_t read = getdents64(dir_fd, dirent_buffer, dirent_size); + + if (read == -1) { + if (errno == EINVAL) { + // Buffer too small? Scale and try again. + dirent_size *= 2; + FEXCore::Allocator::free(dirent_buffer); + dirent_buffer = reinterpret_cast(FEXCore::Allocator::malloc(dirent_size)); + if (!dirent_buffer) { + // Ran out of memory? + Result = false; + goto end; + } + + continue; + } + + // Anything else just exit. + Result = false; + goto end; + } + + if (read == 0) { + // Done. + break; + } + + for (size_t dirent_offset = 0; dirent_offset < read;) { + auto path_dirent = reinterpret_cast(dirent_buffer + dirent_offset); + std::string_view path_name_view = path_dirent->d_name; + + if (path_name_view == "." || path_name_view == "..") { + // Skip these two special files. + dirent_offset += path_dirent->d_reclen; + continue; + } + + if (path_dirent->d_type == DT_DIR) { + // Recurse directories as we find them and remove them. + if (!RecursiveRemoveDirectory(dir_fd, path_dirent->d_name)) { + Result = false; + goto end; + } + } + + // Remove anything possible. + if (!unlinkat(dir_fd, path_dirent->d_name, path_dirent->d_type == DT_DIR)) { + // Couldn't unlink the file for some reason. + LogMan::Msg::IFmt("Failed to remove file: {}", path_name_view); + Result = false; + goto end; + } + + // dirent is a VLA so we need to increment by reported size. + dirent_offset += path_dirent->d_reclen; + } + } + +end: + if (dirent_buffer) { + FEXCore::Allocator::free(dirent_buffer); + } + + close(dir_fd); + return Result; +} + +FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) { + return RecursiveRemoveDirectory(AT_FDCWD, Directory.c_str()) && unlinkat(AT_FDCWD, Directory.c_str(), true); +} +#else +FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(std::string_view Directory) { + std::error_code ec; + return std::filesystem::remove_all(Directory, ec) != ~0ULL; +} +#endif +} // namespace FEXCore::FileUtils diff --git a/FEXCore/include/FEXCore/Utils/FileUtils.h b/FEXCore/include/FEXCore/Utils/FileUtils.h new file mode 100644 index 000000000..d64241dae --- /dev/null +++ b/FEXCore/include/FEXCore/Utils/FileUtils.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include +#include + +namespace FEXCore::FileUtils { +/** + * @brief Removes an entirely directory passed in. Including the path itself. + * + * @return True if the directory didn't exist or was deleted. + */ +FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory); +} // namespace FEXCore::FileUtils