diff --git a/FEXCore/Source/Utils/FileUtils.cpp b/FEXCore/Source/Utils/FileUtils.cpp index 5d5c644e4..7f69ddada 100644 --- a/FEXCore/Source/Utils/FileUtils.cpp +++ b/FEXCore/Source/Utils/FileUtils.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT #include +#include #include #include #include @@ -115,10 +116,87 @@ end: FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) { return RecursiveRemoveDirectory(AT_FDCWD, Directory.c_str()) && unlinkat(AT_FDCWD, Directory.c_str(), true); } + +FEX_DEFAULT_VISIBILITY void WalkDirectory(std::string_view Directory, fextl::move_only_function Callback) { + constexpr int DIR_FLAGS = O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC; + int dir_fd = ::openat(AT_FDCWD, fextl::string(Directory).c_str(), DIR_FLAGS); + if (dir_fd == -1) { + if (errno == ENOENT) { + // Probably raced something. Non-error. + return; + } + } + // 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? + 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? + goto end; + } + + continue; + } + + // Anything else just exit. + 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; + } + + Callback(path_name_view, path_dirent->d_type == DT_DIR); + + // 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); +} + #else -FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(std::string_view Directory) { +FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) { std::error_code ec; return std::filesystem::remove_all(Directory, ec) != ~0ULL; } + +FEX_DEFAULT_VISIBILITY void WalkDirectory(std::string_view Directory, fextl::move_only_function Callback) { + for (const auto& iter : std::filesystem::directory_iterator(Directory)) { + Callback(iter.path().string(), iter.is_directory()); + } +} #endif } // namespace FEXCore::FileUtils diff --git a/FEXCore/include/FEXCore/Utils/FileUtils.h b/FEXCore/include/FEXCore/Utils/FileUtils.h index d64241dae..eb6aa5077 100644 --- a/FEXCore/include/FEXCore/Utils/FileUtils.h +++ b/FEXCore/include/FEXCore/Utils/FileUtils.h @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT #pragma once +#include #include + #include namespace FEXCore::FileUtils { @@ -10,4 +12,6 @@ namespace FEXCore::FileUtils { * @return True if the directory didn't exist or was deleted. */ FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory); + +FEX_DEFAULT_VISIBILITY void WalkDirectory(std::string_view Directory, fextl::move_only_function Callback); } // namespace FEXCore::FileUtils