From 616d57e7fc9fd294f29f9d76874bd2afe745728e Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 22 Feb 2023 02:17:11 +0100 Subject: [PATCH 1/7] Common/FileUtil: Add Copy() function as a wrapper around std::filesystem::copy(). --- Source/Core/Common/FileUtil.cpp | 25 +++++++++++++++++++++++++ Source/Core/Common/FileUtil.h | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 0d4288c609..e7e3e033aa 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -516,6 +516,31 @@ bool DeleteDirRecursively(const std::string& directory) return success; } +bool Copy(std::string_view source_path, std::string_view dest_path, bool overwrite_existing) +{ + DEBUG_LOG_FMT(COMMON, "{}: {} --> {} ({})", __func__, source_path, dest_path, + overwrite_existing ? "overwrite" : "preserve"); + + auto src_path = StringToPath(source_path); + auto dst_path = StringToPath(dest_path); + std::error_code error; + auto options = fs::copy_options::recursive; + if (overwrite_existing) + options |= fs::copy_options::overwrite_existing; + fs::copy(src_path, dst_path, options, error); + if (error) + { + std::error_code error_ignored; + if (fs::equivalent(src_path, dst_path, error_ignored)) + return true; + + ERROR_LOG_FMT(COMMON, "{}: failed {} --> {} ({}): {}", __func__, source_path, dest_path, + overwrite_existing ? "overwrite" : "preserve", error.message()); + return false; + } + return true; +} + // Create directory and copy contents (optionally overwrites existing files) bool CopyDir(const std::string& source_path, const std::string& dest_path, const bool destructive) { diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index ff71d394bc..e6523498c0 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -185,6 +185,11 @@ bool DeleteDirRecursively(const std::string& directory); // Returns the current directory std::string GetCurrentDir(); +// Copies source_path to dest_path, as if by std::filesystem::copy(). Returns true on success or if +// the source and destination are already the same (as determined by std::filesystem::equivalent()). +bool Copy(std::string_view source_path, std::string_view dest_path, + bool overwrite_existing = false); + // Create directory and copy contents (optionally overwrites existing files) bool CopyDir(const std::string& source_path, const std::string& dest_path, bool destructive = false); From e479f9241829130d9a23b12eea2b1142b6e2f282 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 22 Feb 2023 02:31:06 +0100 Subject: [PATCH 2/7] Common/FileUtil: Add CreateDirs() function as a wrapper around std::filesystem::create_directories(). --- Source/Core/Common/FileUtil.cpp | 17 ++++++++++++++++- Source/Core/Common/FileUtil.h | 5 ++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index e7e3e033aa..454277f55a 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -193,7 +193,6 @@ bool Delete(const std::string& filename, IfAbsentBehavior behavior) return true; } -// Returns true if successful, or path already exists. bool CreateDir(const std::string& path) { DEBUG_LOG_FMT(COMMON, "{}: directory {}", __func__, path); @@ -209,6 +208,22 @@ bool CreateDir(const std::string& path) return success; } +bool CreateDirs(std::string_view path) +{ + DEBUG_LOG_FMT(COMMON, "{}: directory {}", __func__, path); + + std::error_code error; + auto native_path = StringToPath(path); + bool success = fs::create_directories(native_path, error); + // If the path was not created, check if it was a pre-existing directory + std::error_code error_ignored; + if (!success && fs::is_directory(native_path, error_ignored)) + success = true; + if (!success) + ERROR_LOG_FMT(COMMON, "{}: failed on {}: {}", __func__, path, error.message()); + return success; +} + bool CreateFullPath(std::string_view fullPath) { DEBUG_LOG_FMT(COMMON, "{}: path {}", __func__, fullPath); diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index e6523498c0..f8cd92a880 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -140,9 +140,12 @@ u64 GetSize(const std::string& path); // Overloaded GetSize, accepts FILE* u64 GetSize(FILE* f); -// Returns true if successful, or path already exists. +// Creates a single directory. Returns true if successful or if the path already exists. bool CreateDir(const std::string& filename); +// Creates directories recursively. Returns true if successful or if the path already exists. +bool CreateDirs(std::string_view filename); + // Creates the full path to the file given in fullPath. // That is, for path '/a/b/c.bin', creates folders '/a' and '/a/b'. // Returns true if creation is successful or if the path already exists. From 884917a6d534cdce4fe7b2f39e1188e591d64b0f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 22 Feb 2023 21:03:23 +0100 Subject: [PATCH 3/7] Common/FileUtil: Use non-throwing overload of is_directory() in CreateDir() and CreateFullPath(). --- Source/Core/Common/FileUtil.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 454277f55a..656e74134b 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -201,7 +201,8 @@ bool CreateDir(const std::string& path) auto native_path = StringToPath(path); bool success = fs::create_directory(native_path, error); // If the path was not created, check if it was a pre-existing directory - if (!success && fs::is_directory(native_path)) + std::error_code error_ignored; + if (!success && fs::is_directory(native_path, error_ignored)) success = true; if (!success) ERROR_LOG_FMT(COMMON, "{}: failed on {}: {}", __func__, path, error.message()); @@ -232,7 +233,8 @@ bool CreateFullPath(std::string_view fullPath) auto native_path = StringToPath(fullPath).parent_path(); bool success = fs::create_directories(native_path, error); // If the path was not created, check if it was a pre-existing directory - if (!success && fs::is_directory(native_path)) + std::error_code error_ignored; + if (!success && fs::is_directory(native_path, error_ignored)) success = true; if (!success) ERROR_LOG_FMT(COMMON, "{}: failed on {}: {}", __func__, fullPath, error.message()); From 1ed0e014cd1c9087830fcee94e26f7dc3a3d45a8 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 22 Feb 2023 02:23:21 +0100 Subject: [PATCH 4/7] Migrate non-destructive calls of File::CopyDir() to File::Copy(). --- Source/Core/Core/WiiRoot.cpp | 11 +++++++---- Source/Core/UICommon/AutoUpdate.cpp | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/WiiRoot.cpp b/Source/Core/Core/WiiRoot.cpp index b6e38be832..4c39619e43 100644 --- a/Source/Core/Core/WiiRoot.cpp +++ b/Source/Core/Core/WiiRoot.cpp @@ -186,7 +186,10 @@ static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs, const auto& netplay_redirect_folder = boot_session_data.GetWiiSyncRedirectFolder(); if (!netplay_redirect_folder.empty()) - File::CopyDir(netplay_redirect_folder, s_temp_redirect_root + "/"); + { + File::CreateDirs(s_temp_redirect_root); + File::Copy(netplay_redirect_folder, s_temp_redirect_root); + } } } @@ -359,11 +362,11 @@ void InitializeWiiFileSystemContents( if (!File::IsDirectory(save_redirect->m_target_path)) { - File::CreateFullPath(save_redirect->m_target_path + "/"); + File::CreateDirs(save_redirect->m_target_path); if (save_redirect->m_clone) { - File::CopyDir(Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT), - save_redirect->m_target_path); + File::Copy(Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT), + save_redirect->m_target_path); } } s_nand_redirects.emplace_back(IOS::HLE::FS::NandRedirect{ diff --git a/Source/Core/UICommon/AutoUpdate.cpp b/Source/Core/UICommon/AutoUpdate.cpp index 35b17ed19d..b67c499c14 100644 --- a/Source/Core/UICommon/AutoUpdate.cpp +++ b/Source/Core/UICommon/AutoUpdate.cpp @@ -248,7 +248,7 @@ void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInforma #ifdef __APPLE__ // Copy the updater so it can update itself if needed. const std::string reloc_updater_path = UpdaterPath(true); - if (!File::CopyDir(UpdaterPath(), reloc_updater_path)) + if (!File::Copy(UpdaterPath(), reloc_updater_path)) { CriticalAlertFmtT("Unable to create updater copy."); return; From 5367bf394c6092bed589bc4cdb06a2f0ab2c0f75 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 24 Feb 2023 21:39:02 +0100 Subject: [PATCH 5/7] Common/FileUtil: Add Move() function. --- Source/Core/Common/FileUtil.cpp | 52 +++++++++++++++++++++++++++++++++ Source/Core/Common/FileUtil.h | 7 +++++ 2 files changed, 59 insertions(+) diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 656e74134b..333831d39f 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -558,6 +558,58 @@ bool Copy(std::string_view source_path, std::string_view dest_path, bool overwri return true; } +static bool MoveWithOverwrite(const std::filesystem::path& src, const std::filesystem::path& dst, + std::error_code& error) +{ + fs::rename(src, dst, error); + if (!error) + return true; + + // rename failed, try fallbacks + + if (!fs::is_directory(src)) + { + // src is not a directory (ie, probably a file), try to copy file + delete + if (!fs::copy_file(src, dst, fs::copy_options::overwrite_existing, error)) + return false; + if (!fs::remove(src, error)) + return false; + return true; + } + + // src is a directory, recurse into it and try to move all sub-elements one by one + // this usually happens because the target is a non-empty directory + for (fs::directory_iterator it(src, error); it != fs::directory_iterator(); it.increment(error)) + { + if (error) + return false; + if (!MoveWithOverwrite(it->path(), dst / it->path().filename(), error)) + return false; + } + if (error) + return false; + + // all sub-elements moved, remove top directory + if (!fs::remove(src, error)) + return false; + + return true; +} + +bool MoveWithOverwrite(std::string_view source_path, std::string_view dest_path) +{ + DEBUG_LOG_FMT(COMMON, "{}: {} --> {}", __func__, source_path, dest_path); + auto src_path = StringToPath(source_path); + auto dst_path = StringToPath(dest_path); + std::error_code error; + if (!MoveWithOverwrite(src_path, dst_path, error)) + { + ERROR_LOG_FMT(COMMON, "{}: failed {} --> {}: {}", __func__, source_path, dest_path, + error.message()); + } + return true; +} + // Create directory and copy contents (optionally overwrites existing files) bool CopyDir(const std::string& source_path, const std::string& dest_path, const bool destructive) { diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index f8cd92a880..53d5d46d3f 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -193,6 +193,13 @@ std::string GetCurrentDir(); bool Copy(std::string_view source_path, std::string_view dest_path, bool overwrite_existing = false); +// Moves source_path to dest_path. On success, the source_path will no longer exist, and the +// dest_path will contain the data previously in source_path. Files in dest_path will be overwritten +// if they match files in source_path, but files that only exist in dest_path will be kept. No +// guarantee on the state is given on failure; the move may have completely failed or partially +// completed. +bool MoveWithOverwrite(std::string_view source_path, std::string_view dest_path); + // Create directory and copy contents (optionally overwrites existing files) bool CopyDir(const std::string& source_path, const std::string& dest_path, bool destructive = false); From 4f462b4ef6b274468b0e8d38e0c712b286cf69ae Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 24 Feb 2023 21:48:01 +0100 Subject: [PATCH 6/7] Migrate destructive calls of File::CopyDir() to File::Move(). --- Source/Core/Core/IOS/WFS/WFSI.cpp | 2 +- Source/Core/Core/WiiRoot.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/IOS/WFS/WFSI.cpp b/Source/Core/Core/IOS/WFS/WFSI.cpp index f1f66e1554..833052ce55 100644 --- a/Source/Core/Core/IOS/WFS/WFSI.cpp +++ b/Source/Core/Core/IOS/WFS/WFSI.cpp @@ -121,7 +121,7 @@ void WFSIDevice::FinalizePatchInstall() const std::string current_title_dir = fmt::format("/vol/{}/title/{}/{}", m_device_name, m_current_group_id_str, m_current_title_id_str); const std::string patch_dir = current_title_dir + "/_patch"; - File::CopyDir(WFS::NativePath(patch_dir), WFS::NativePath(current_title_dir), true); + File::MoveWithOverwrite(WFS::NativePath(patch_dir), WFS::NativePath(current_title_dir)); } std::optional WFSIDevice::IOCtl(const IOCtlRequest& request) diff --git a/Source/Core/Core/WiiRoot.cpp b/Source/Core/Core/WiiRoot.cpp index 4c39619e43..7311a6b725 100644 --- a/Source/Core/Core/WiiRoot.cpp +++ b/Source/Core/Core/WiiRoot.cpp @@ -197,7 +197,7 @@ static void MoveToBackupIfExists(const std::string& path) { if (File::Exists(path)) { - const std::string backup_path = path.substr(0, path.size() - 1) + ".backup" DIR_SEP; + const std::string backup_path = path.substr(0, path.size() - 1) + ".backup"; WARN_LOG_FMT(IOS_FS, "Temporary directory at {} exists, moving to backup...", path); // If backup exists, delete it as we don't want a mess @@ -207,7 +207,7 @@ static void MoveToBackupIfExists(const std::string& path) File::DeleteDirRecursively(backup_path); } - File::CopyDir(path, backup_path, true); + File::MoveWithOverwrite(path, backup_path); } } @@ -399,7 +399,10 @@ void CleanUpWiiFileSystemContents(const BootSessionData& boot_session_data) // copy back the temp nand redirected files to where they should normally be redirected to for (const auto& redirect : s_temp_nand_redirects) - File::CopyDir(redirect.temp_path, redirect.real_path + "/", true); + { + File::CreateFullPath(redirect.real_path); + File::MoveWithOverwrite(redirect.temp_path, redirect.real_path); + } IOS::HLE::EmulationKernel* ios = IOS::HLE::GetIOS(); From a11b9d585f0d4668a8d4f698e0d03d1c79013eac Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 24 Feb 2023 21:48:48 +0100 Subject: [PATCH 7/7] Common/FileUtil: Remove obsolete CopyDir() function. --- Source/Core/Common/FileUtil.cpp | 23 ----------------------- Source/Core/Common/FileUtil.h | 4 ---- 2 files changed, 27 deletions(-) diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 333831d39f..d456952980 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -610,29 +610,6 @@ bool MoveWithOverwrite(std::string_view source_path, std::string_view dest_path) return true; } -// Create directory and copy contents (optionally overwrites existing files) -bool CopyDir(const std::string& source_path, const std::string& dest_path, const bool destructive) -{ - auto src_path = StringToPath(source_path); - auto dst_path = StringToPath(dest_path); - if (fs::equivalent(src_path, dst_path)) - return true; - - DEBUG_LOG_FMT(COMMON, "{}: {} --> {}", __func__, source_path, dest_path); - - auto options = fs::copy_options::recursive; - if (destructive) - options |= fs::copy_options::overwrite_existing; - std::error_code error; - bool copied = fs::copy_file(src_path, dst_path, options, error); - if (!copied) - { - ERROR_LOG_FMT(COMMON, "{}: failed {} --> {}: {}", __func__, source_path, dest_path, - error.message()); - } - return copied; -} - // Returns the current directory std::string GetCurrentDir() { diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 53d5d46d3f..cadabbd541 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -200,10 +200,6 @@ bool Copy(std::string_view source_path, std::string_view dest_path, // completed. bool MoveWithOverwrite(std::string_view source_path, std::string_view dest_path); -// Create directory and copy contents (optionally overwrites existing files) -bool CopyDir(const std::string& source_path, const std::string& dest_path, - bool destructive = false); - // Set the current directory to given directory bool SetCurrentDir(const std::string& directory);