From 065867e2c24e9856c360fc2d6b9a86c92aedc43e Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Tue, 25 May 2021 19:32:56 -0400 Subject: common: fs: Rework the Common Filesystem interface to make use of std::filesystem (#6270) * common: fs: fs_types: Create filesystem types Contains various filesystem types used by the Common::FS library * common: fs: fs_util: Add std::string to std::u8string conversion utility * common: fs: path_util: Add utlity functions for paths Contains various utility functions for getting or manipulating filesystem paths used by the Common::FS library * common: fs: file: Rewrite the IOFile implementation * common: fs: Reimplement Common::FS library using std::filesystem * common: fs: fs_paths: Add fs_paths to replace common_paths * common: fs: path_util: Add the rest of the path functions * common: Remove the previous Common::FS implementation * general: Remove unused fs includes * string_util: Remove unused function and include * nvidia_flags: Migrate to the new Common::FS library * settings: Migrate to the new Common::FS library * logging: backend: Migrate to the new Common::FS library * core: Migrate to the new Common::FS library * perf_stats: Migrate to the new Common::FS library * reporter: Migrate to the new Common::FS library * telemetry_session: Migrate to the new Common::FS library * key_manager: Migrate to the new Common::FS library * bis_factory: Migrate to the new Common::FS library * registered_cache: Migrate to the new Common::FS library * xts_archive: Migrate to the new Common::FS library * service: acc: Migrate to the new Common::FS library * applets/profile: Migrate to the new Common::FS library * applets/web: Migrate to the new Common::FS library * service: filesystem: Migrate to the new Common::FS library * loader: Migrate to the new Common::FS library * gl_shader_disk_cache: Migrate to the new Common::FS library * nsight_aftermath_tracker: Migrate to the new Common::FS library * vulkan_library: Migrate to the new Common::FS library * configure_debug: Migrate to the new Common::FS library * game_list_worker: Migrate to the new Common::FS library * config: Migrate to the new Common::FS library * configure_filesystem: Migrate to the new Common::FS library * configure_per_game_addons: Migrate to the new Common::FS library * configure_profile_manager: Migrate to the new Common::FS library * configure_ui: Migrate to the new Common::FS library * input_profiles: Migrate to the new Common::FS library * yuzu_cmd: config: Migrate to the new Common::FS library * yuzu_cmd: Migrate to the new Common::FS library * vfs_real: Migrate to the new Common::FS library * vfs: Migrate to the new Common::FS library * vfs_libzip: Migrate to the new Common::FS library * service: bcat: Migrate to the new Common::FS library * yuzu: main: Migrate to the new Common::FS library * vfs_real: Delete the contents of an existing file in CreateFile Current usages of CreateFile expect to delete the contents of an existing file, retain this behavior for now. * input_profiles: Don't iterate the input profile dir if it does not exist Silences an error produced in the log if the directory does not exist. * game_list_worker: Skip parsing file if the returned VfsFile is nullptr Prevents crashes in GetLoader when the virtual file is nullptr * common: fs: Validate paths for path length * service: filesystem: Open the mod load directory as read only --- src/common/fs/fs.cpp | 610 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 src/common/fs/fs.cpp (limited to 'src/common/fs/fs.cpp') diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp new file mode 100644 index 000000000..d492480d9 --- /dev/null +++ b/src/common/fs/fs.cpp @@ -0,0 +1,610 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" + +namespace Common::FS { + +namespace fs = std::filesystem; + +// File Operations + +bool NewFile(const fs::path& path, u64 size) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path.parent_path())) { + LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", + PathToUTF8String(path)); + return false; + } + + if (Exists(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", PathToUTF8String(path)); + return false; + } + + IOFile io_file{path, FileAccessMode::Write}; + + if (!io_file.IsOpen()) { + LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", PathToUTF8String(path)); + return false; + } + + if (!io_file.SetSize(size)) { + LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}", + PathToUTF8String(path), size); + return false; + } + + io_file.Close(); + + LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}", + PathToUTF8String(path), size); + + return true; +} + +bool RemoveFile(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsFile(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RenameFile(const fs::path& old_path, const fs::path& new_path) { + if (!ValidatePath(old_path) || !ValidatePath(new_path)) { + LOG_ERROR(Common_Filesystem, + "One or both input path(s) is not valid, old_path={}, new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + return false; + } + + if (!Exists(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", + PathToUTF8String(old_path)); + return false; + } + + if (!IsFile(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file", + PathToUTF8String(old_path)); + return false; + } + + if (Exists(new_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", + PathToUTF8String(new_path)); + return false; + } + + std::error_code ec; + + fs::rename(old_path, new_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to rename the file from old_path={} to new_path={}, ec_message={}", + PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + + return true; +} + +std::shared_ptr FileOpen(const fs::path& path, FileAccessMode mode, FileType type, + FileShareFlag flag) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return nullptr; + } + + if (!IsFile(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", + PathToUTF8String(path)); + return nullptr; + } + + auto io_file = std::make_shared(path, mode, type, flag); + + if (!io_file->IsOpen()) { + io_file.reset(); + + LOG_ERROR(Common_Filesystem, + "Failed to open the file at path={} with mode={}, type={}, flag={}", + PathToUTF8String(path), mode, type, flag); + + return nullptr; + } + + LOG_DEBUG(Common_Filesystem, + "Successfully opened the file at path={} with mode={}, type={}, flag={}", + PathToUTF8String(path), mode, type, flag); + + return io_file; +} + +// Directory Operations + +bool CreateDir(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path.parent_path())) { + LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", + PathToUTF8String(path)); + return false; + } + + if (IsDir(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", + PathToUTF8String(path)); + return true; + } + + std::error_code ec; + + fs::create_directory(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}", + PathToUTF8String(path)); + + return true; +} + +bool CreateDirs(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (IsDir(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", + PathToUTF8String(path)); + return true; + } + + std::error_code ec; + + fs::create_directories(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}", + PathToUTF8String(path)); + + return true; +} + +bool CreateParentDir(const fs::path& path) { + return CreateDir(path.parent_path()); +} + +bool CreateParentDirs(const fs::path& path) { + return CreateDirs(path.parent_path()); +} + +bool RemoveDir(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RemoveDirRecursively(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove_all(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to remove the directory and its contents at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RemoveDirContentsRecursively(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to completely enumerate the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + break; + } + + fs::remove(entry.path(), ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to remove the filesystem object at path={}, ec_message={}", + PathToUTF8String(entry.path()), ec.message()); + break; + } + } + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to remove all the contents of the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, + "Successfully removed all the contents of the directory at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RenameDir(const fs::path& old_path, const fs::path& new_path) { + if (!ValidatePath(old_path) || !ValidatePath(new_path)) { + LOG_ERROR(Common_Filesystem, + "One or both input path(s) is not valid, old_path={}, new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + return false; + } + + if (!Exists(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", + PathToUTF8String(old_path)); + return false; + } + + if (!IsDir(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory", + PathToUTF8String(old_path)); + return false; + } + + if (Exists(new_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", + PathToUTF8String(new_path)); + return false; + } + + std::error_code ec; + + fs::rename(old_path, new_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to rename the file from old_path={} to new_path={}, ec_message={}", + PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + + return true; +} + +void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, + DirEntryFilter filter) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return; + } + + if (!Exists(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return; + } + + bool callback_error = false; + + std::error_code ec; + + for (const auto& entry : fs::directory_iterator(path, ec)) { + if (ec) { + break; + } + + if (True(filter & DirEntryFilter::File) && + entry.status().type() == fs::file_type::regular) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + + if (True(filter & DirEntryFilter::Directory) && + entry.status().type() == fs::file_type::directory) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + } + + if (callback_error || ec) { + LOG_ERROR(Common_Filesystem, + "Failed to visit all the directory entries of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return; + } + + LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", + PathToUTF8String(path)); +} + +void IterateDirEntriesRecursively(const std::filesystem::path& path, + const DirEntryCallable& callback, DirEntryFilter filter) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return; + } + + if (!Exists(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return; + } + + bool callback_error = false; + + std::error_code ec; + + for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + if (ec) { + break; + } + + if (True(filter & DirEntryFilter::File) && + entry.status().type() == fs::file_type::regular) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + + if (True(filter & DirEntryFilter::Directory) && + entry.status().type() == fs::file_type::directory) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + } + + if (callback_error || ec) { + LOG_ERROR(Common_Filesystem, + "Failed to visit all the directory entries of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return; + } + + LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", + PathToUTF8String(path)); +} + +// Generic Filesystem Operations + +bool Exists(const fs::path& path) { + return fs::exists(path); +} + +bool IsFile(const fs::path& path) { + return fs::is_regular_file(path); +} + +bool IsDir(const fs::path& path) { + return fs::is_directory(path); +} + +fs::path GetCurrentDir() { + std::error_code ec; + + const auto current_path = fs::current_path(ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message()); + return {}; + } + + return current_path; +} + +bool SetCurrentDir(const fs::path& path) { + std::error_code ec; + + fs::current_path(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + return true; +} + +fs::file_type GetEntryType(const fs::path& path) { + std::error_code ec; + + const auto file_status = fs::status(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return fs::file_type::not_found; + } + + return file_status.type(); +} + +u64 GetSize(const fs::path& path) { + std::error_code ec; + + const auto file_size = fs::file_size(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return file_size; +} + +u64 GetFreeSpaceSize(const fs::path& path) { + std::error_code ec; + + const auto space_info = fs::space(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the available free space of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return space_info.free; +} + +u64 GetTotalSpaceSize(const fs::path& path) { + std::error_code ec; + + const auto space_info = fs::space(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the total capacity of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return space_info.capacity; +} + +} // namespace Common::FS -- cgit v1.2.3