From 7835486a4d055cee005af1c2d90f5caf4074b530 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 6 Dec 2019 23:19:48 -0800 Subject: [PATCH] ams_mitm: Implement savedata redirection --- .../source/fs_mitm/fs_mitm_service.cpp | 65 +++++ .../source/fs_mitm/fs_mitm_service.hpp | 4 +- .../ams_mitm/source/fs_mitm/fs_shim.c | 13 + .../ams_mitm/source/fs_mitm/fs_shim.h | 2 + .../source/fs_mitm/fsmitm_save_utils.cpp | 114 ++++++++ .../source/fs_mitm/fsmitm_save_utils.hpp | 26 ++ .../include/stratosphere/cfg/cfg_api.hpp | 3 +- .../include/stratosphere/fs/fs_path_tool.hpp | 2 +- .../stratosphere/fs/fs_remote_filesystem.hpp | 6 +- .../include/stratosphere/fs/fsa/fs_ifile.hpp | 14 +- .../include/stratosphere/fssystem.hpp | 1 + ...ystem_directory_redirection_filesystem.hpp | 18 +- ...fssystem_directory_savedata_filesystem.hpp | 64 +++++ .../fssystem_subdirectory_filesystem.hpp | 18 +- .../fssystem/fssystem_utility.hpp | 135 +++++++++ .../fssystem_path_resolution_filesystem.hpp | 43 ++- .../sf/cmif/sf_cmif_domain_service_object.hpp | 4 + .../libstratosphere/source/cfg/cfg_flags.cpp | 4 +- ...ystem_directory_redirection_filesystem.cpp | 8 +- ...fssystem_directory_savedata_filesystem.cpp | 272 ++++++++++++++++++ .../fssystem_subdirectory_filesystem.cpp | 8 +- .../source/fssystem/fssystem_utility.cpp | 118 ++++++++ .../sf/cmif/sf_cmif_domain_service_object.cpp | 4 +- 23 files changed, 899 insertions(+), 47 deletions(-) create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp create mode 100644 stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp rename stratosphere/libstratosphere/include/stratosphere/fssystem/{ => impl}/fssystem_path_resolution_filesystem.hpp (75%) create mode 100644 stratosphere/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp create mode 100644 stratosphere/libstratosphere/source/fssystem/fssystem_utility.cpp diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp index 409548f84..4ead9d21b 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp @@ -18,6 +18,7 @@ #include "fs_mitm_service.hpp" #include "fsmitm_boot0storage.hpp" #include "fsmitm_layered_romfs_storage.hpp" +#include "fsmitm_save_utils.hpp" namespace ams::mitm::fs { @@ -78,6 +79,70 @@ namespace ams::mitm::fs { return ResultSuccess(); } + Result FsMitmService::OpenSaveDataFileSystem(sf::Out> out, u8 _space_id, const FsSaveDataAttribute &attribute) { + /* We only want to intercept saves for games, right now. */ + const bool is_game_or_hbl = this->client_info.override_status.IsHbl() || ncm::IsApplicationProgramId(this->client_info.program_id); + R_UNLESS(is_game_or_hbl, sm::mitm::ResultShouldForwardToSession()); + + /* Only redirect if the appropriate system setting is set. */ + R_UNLESS(GetSettingsItemBooleanValue("atmosphere", "fsmitm_redirect_saves_to_sd"), sm::mitm::ResultShouldForwardToSession()); + + /* Only redirect if the specific title being accessed has a redirect save flag. */ + R_UNLESS(cfg::HasContentSpecificFlag(this->client_info.program_id, "redirect_save"), sm::mitm::ResultShouldForwardToSession()); + + /* Only redirect account savedata. */ + R_UNLESS(attribute.save_data_type == FsSaveDataType_Account, sm::mitm::ResultShouldForwardToSession()); + + /* Get enum type for space id. */ + auto space_id = static_cast(_space_id); + + /* Verify we can open the save. */ + FsFileSystem save_fs; + R_UNLESS(R_SUCCEEDED(fsOpenSaveDataFileSystemFwd(this->forward_service.get(), &save_fs, space_id, &attribute)), sm::mitm::ResultShouldForwardToSession()); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&save_fs.s)}; + std::unique_ptr save_ifs = std::make_unique(save_fs); + + /* Mount the SD card using fs.mitm's session. */ + FsFileSystem sd_fs; + R_TRY(fsOpenSdCardFileSystem(&sd_fs)); + std::shared_ptr sd_ifs = std::make_shared(sd_fs); + + /* Verify that we can open the save directory, and that it exists. */ + const ncm::ProgramId application_id = attribute.application_id == 0 ? this->client_info.program_id : ncm::ProgramId{attribute.application_id}; + char save_dir_path[fs::EntryNameLengthMax + 1]; + R_TRY(mitm::fs::SaveUtil::GetDirectorySaveDataPath(save_dir_path, sizeof(save_dir_path), application_id, space_id, attribute)); + + /* Check if this is the first time we're making the save. */ + bool is_new_save = false; + { + fs::DirectoryEntryType ent; + R_TRY_CATCH(sd_ifs->GetEntryType(&ent, save_dir_path)) { + R_CATCH(fs::ResultPathNotFound) { is_new_save = true; } + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + } + + /* Ensure the directory exists. */ + R_TRY(fssystem::EnsureDirectoryExistsRecursively(sd_ifs.get(), save_dir_path)); + + /* Create directory savedata filesystem. */ + std::unique_ptr subdir_fs = std::make_unique(sd_ifs, save_dir_path); + std::shared_ptr dirsave_ifs = std::make_shared(std::move(subdir_fs)); + + /* Ensure correct directory savedata filesystem state. */ + R_TRY(dirsave_ifs->Initialize()); + + /* If it's the first time we're making the save, copy existing savedata over. */ + if (is_new_save) { + /* TODO: Check error? */ + dirsave_ifs->CopySaveFromFileSystem(save_ifs.get()); + } + + /* Set output. */ + out.SetValue(std::make_shared(std::move(dirsave_ifs), false), target_object_id); + return ResultSuccess(); + } + Result FsMitmService::OpenBisStorage(sf::Out> out, u32 _bis_partition_id) { const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id); diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp index cd68beabd..4ff9a2670 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp @@ -63,7 +63,7 @@ namespace ams::mitm::fs { /* Result OpenFileSystemWithPatch(Out> out, u64 program_id, u32 filesystem_type); */ /* Result OpenFileSystemWithId(Out> out, InPointer path, u64 program_id, u32 filesystem_type); */ Result OpenSdCardFileSystem(sf::Out> out); - /* Result OpenSaveDataFileSystem(Out> out, u8 space_id, FsSave save_struct); */ + Result OpenSaveDataFileSystem(sf::Out> out, u8 space_id, const FsSaveDataAttribute &attribute); Result OpenBisStorage(sf::Out> out, u32 bis_partition_id); Result OpenDataStorageByCurrentProcess(sf::Out> out); Result OpenDataStorageByDataId(sf::Out> out, ncm::ProgramId /* TODO: ncm::DataId */ data_id, u8 storage_id); @@ -72,7 +72,7 @@ namespace ams::mitm::fs { /* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithPatch, hos::Version_200), */ /* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithId, hos::Version_200), */ MAKE_SERVICE_COMMAND_META(OpenSdCardFileSystem), - /* MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), */ + MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), MAKE_SERVICE_COMMAND_META(OpenBisStorage), MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess), MAKE_SERVICE_COMMAND_META(OpenDataStorageByDataId), diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c index e836b31ae..3065dce19 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c @@ -50,3 +50,16 @@ Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, Ncm .out_objects = &out->s, ); } + +Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr) { + const struct { + u8 save_data_space_id; + u8 pad[7]; + FsSaveDataAttribute attr; + } in = { (u8)save_data_space_id, {0}, *attr }; + + return serviceDispatchIn(s, 51, in, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h index 910ee0075..a39278a28 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h @@ -17,6 +17,8 @@ Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partitio Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out); Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id); +Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr); + #ifdef __cplusplus } diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp new file mode 100644 index 000000000..7599abe88 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "fsmitm_save_utils.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + Result GetSaveDataSpaceIdString(const char **out_str, u8 space_id) { + switch (space_id) { + case FsSaveDataSpaceId_System: + case FsSaveDataSpaceId_ProperSystem: + *out_str = "sys"; + break; + case FsSaveDataSpaceId_User: + *out_str = "user"; + break; + case FsSaveDataSpaceId_SdSystem: + *out_str = "sd_sys"; + break; + case FsSaveDataSpaceId_Temporary: + *out_str = "temp"; + break; + case FsSaveDataSpaceId_SdUser: + *out_str = "sd_user"; + break; + case FsSaveDataSpaceId_SafeMode: + *out_str = "safe"; + break; + default: + return fs::ResultInvalidSaveDataSpaceId(); + } + + return ResultSuccess(); + } + + Result GetSaveDataTypeString(const char **out_str, u8 save_data_type) { + switch (save_data_type) { + case FsSaveDataType_System: + *out_str = "system"; + break; + case FsSaveDataType_Account: + *out_str = "account"; + break; + case FsSaveDataType_Bcat: + *out_str = "bcat"; + break; + case FsSaveDataType_Device: + *out_str = "device"; + break; + case FsSaveDataType_Temporary: + *out_str = "temp"; + break; + case FsSaveDataType_Cache: + *out_str = "cache"; + break; + case FsSaveDataType_SystemBcat: + *out_str = "system_bcat"; + break; + default: + /* TODO: Better result? */ + return fs::ResultInvalidArgument(); + } + + return ResultSuccess(); + } + + constexpr inline bool IsEmptyAccountId(const AccountUid &uid) { + constexpr AccountUid empty_uid = {}; + return std::memcmp(&uid, &empty_uid, sizeof(uid)) == 0; + } + + + } + + Result SaveUtil::GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const FsSaveDataAttribute &attribute) { + /* Saves should be separate for emunand vs sysnand. */ + const char *emummc_str = emummc::IsActive() ? "emummc" : "sysmmc"; + + /* Get space_id, save_data_type strings. */ + const char *space_id_str, *save_type_str; + R_TRY(GetSaveDataSpaceIdString(&space_id_str, space_id)); + R_TRY(GetSaveDataTypeString(&save_type_str, attribute.save_data_type)); + + /* Initialize the path. */ + const bool is_system = attribute.system_save_data_id != 0 && IsEmptyAccountId(attribute.uid); + size_t out_path_len; + if (is_system) { + out_path_len = static_cast(std::snprintf(dst, dst_size, "/atmosphere/saves/%s/%s/%s/%016lx", emummc_str, space_id_str, save_type_str, attribute.system_save_data_id)); + } else { + out_path_len = static_cast(std::snprintf(dst, dst_size, "/atmosphere/saves/%s/%s/%s/%016lx/%016lx%016lx", emummc_str, space_id_str, save_type_str, static_cast(program_id), attribute.uid.uid[1], attribute.uid.uid[0])); + } + + R_UNLESS(out_path_len < dst_size, fs::ResultTooLongPath()); + + return ResultSuccess(); + } + +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp new file mode 100644 index 000000000..85c9379bb --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::fs { + + class SaveUtil { + public: + static Result GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const FsSaveDataAttribute &attribute); + }; + +} diff --git a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp index 747f7e355..e111164a8 100644 --- a/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/cfg/cfg_api.hpp @@ -16,6 +16,7 @@ #pragma once #include "cfg_types.hpp" #include "cfg_locale_types.hpp" +#include "../sm/sm_types.hpp" namespace ams::cfg { @@ -36,7 +37,7 @@ namespace ams::cfg { OverrideLocale GetOverrideLocale(ncm::ProgramId program_id); /* Flag utilities. */ - bool HasFlag(ncm::ProgramId program_id, const char *flag); + bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag); bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag); bool HasGlobalFlag(const char *flag); diff --git a/stratosphere/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp b/stratosphere/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp index c74c68316..3143ca65d 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp @@ -30,7 +30,7 @@ namespace ams::fs { class PathTool { public: - static constexpr fssrv::sf::Path RootPath = fssrv::sf::FspPath::Encode("/"); + static constexpr const char RootPath[] = "/"; public: static constexpr inline bool IsSeparator(char c) { return c == StringTraits::DirectorySeparator; diff --git a/stratosphere/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp b/stratosphere/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp index 8c39b4618..c5b2942f9 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp @@ -33,7 +33,7 @@ namespace ams::fs { virtual ~RemoteFile() { fsFileClose(this->base_file.get()); } public: - virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const ReadOption &option) override final { + virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final { return fsFileRead(this->base_file.get(), offset, buffer, size, option.value, out); } @@ -45,7 +45,7 @@ namespace ams::fs { return fsFileFlush(this->base_file.get()); } - virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const WriteOption &option) override final { + virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { return fsFileWrite(this->base_file.get(), offset, buffer, size, option.value); } @@ -53,7 +53,7 @@ namespace ams::fs { return fsFileSetSize(this->base_file.get(), size); } - virtual Result OperateRangeImpl(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { /* TODO: How should this be handled? */ return fs::ResultNotImplemented(); } diff --git a/stratosphere/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp b/stratosphere/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp index 9ecc920aa..3cafb2769 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp @@ -24,7 +24,7 @@ namespace ams::fs::fsa { public: virtual ~IFile() { /* ... */ } - Result Read(size_t *out, s64 offset, void *buffer, size_t size, const ReadOption &option) { + Result Read(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); if (size == 0) { *out = 0; @@ -51,7 +51,7 @@ namespace ams::fs::fsa { return this->FlushImpl(); } - Result Write(s64 offset, const void *buffer, size_t size, const WriteOption &option) { + Result Write(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) { if (size == 0) { if (option.HasFlushFlag()) { R_TRY(this->Flush()); @@ -71,11 +71,11 @@ namespace ams::fs::fsa { return this->SetSizeImpl(size); } - Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { return this->OperateRangeImpl(dst, dst_size, op_id, offset, size, src, src_size); } - Result OperateRange(OperationId op_id, s64 offset, s64 size) { + Result OperateRange(fs::OperationId op_id, s64 offset, s64 size) { return this->OperateRangeImpl(nullptr, 0, op_id, offset, size, nullptr, 0); } public: @@ -84,12 +84,12 @@ namespace ams::fs::fsa { protected: /* ...? */ private: - virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const ReadOption &option) = 0; + virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) = 0; virtual Result GetSizeImpl(s64 *out) = 0; virtual Result FlushImpl() = 0; - virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const WriteOption &option) = 0; + virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) = 0; virtual Result SetSizeImpl(s64 size) = 0; - virtual Result OperateRangeImpl(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) = 0; + virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) = 0; }; } diff --git a/stratosphere/libstratosphere/include/stratosphere/fssystem.hpp b/stratosphere/libstratosphere/include/stratosphere/fssystem.hpp index 90d84444a..d786b2e96 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fssystem.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fssystem.hpp @@ -19,3 +19,4 @@ #include "fssystem/fssystem_path_tool.hpp" #include "fssystem/fssystem_subdirectory_filesystem.hpp" #include "fssystem/fssystem_directory_redirection_filesystem.hpp" +#include "fssystem/fssystem_directory_savedata_filesystem.hpp" diff --git a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp index 3d56f3e6c..daa7babb6 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp @@ -14,29 +14,33 @@ * along with this program. If not, see . */ #pragma once -#include "fssystem_path_resolution_filesystem.hpp" +#include "impl/fssystem_path_resolution_filesystem.hpp" namespace ams::fssystem { - class DirectoryRedirectionFileSystem : public IPathResolutionFileSystem { + class DirectoryRedirectionFileSystem : public impl::IPathResolutionFileSystem { NON_COPYABLE(DirectoryRedirectionFileSystem); private: - using PathResolutionFileSystem = IPathResolutionFileSystem; + using PathResolutionFileSystem = impl::IPathResolutionFileSystem; + friend class impl::IPathResolutionFileSystem; private: char *before_dir; size_t before_dir_len; char *after_dir; size_t after_dir_len; - bool unc_preserved; public: - DirectoryRedirectionFileSystem(std::shared_ptr fs, const char *before, const char *after); - DirectoryRedirectionFileSystem(std::shared_ptr fs, const char *before, const char *after, bool unc); + DirectoryRedirectionFileSystem(std::shared_ptr fs, const char *before, const char *after, bool unc = false); + DirectoryRedirectionFileSystem(std::unique_ptr &&fs, const char *before, const char *after, bool unc = false); virtual ~DirectoryRedirectionFileSystem(); + protected: + inline std::optional> GetAccessorLock() const { + /* No accessor lock is needed. */ + return std::nullopt; + } private: Result GetNormalizedDirectoryPath(char **out, size_t *out_size, const char *dir); Result Initialize(const char *before, const char *after); - public: Result ResolveFullPath(char *out, size_t out_size, const char *relative_path); }; diff --git a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp new file mode 100644 index 000000000..2014ea43d --- /dev/null +++ b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include "impl/fssystem_path_resolution_filesystem.hpp" + +namespace ams::fssystem { + + class DirectorySaveDataFileSystem : public impl::IPathResolutionFileSystem { + NON_COPYABLE(DirectorySaveDataFileSystem); + private: + using PathResolutionFileSystem = impl::IPathResolutionFileSystem; + friend class impl::IPathResolutionFileSystem; + private: + os::Mutex accessor_mutex; + s32 open_writable_files; + public: + DirectorySaveDataFileSystem(std::shared_ptr fs); + DirectorySaveDataFileSystem(std::unique_ptr fs); + Result Initialize(); + + virtual ~DirectorySaveDataFileSystem(); + protected: + inline std::optional> GetAccessorLock() { + /* We have a real accessor lock that we want to use. */ + return std::make_optional>(this->accessor_mutex); + } + private: + Result AllocateWorkBuffer(std::unique_ptr *out, size_t *out_size, size_t ideal_size); + Result SynchronizeDirectory(const char *dst, const char *src); + Result ResolveFullPath(char *out, size_t out_size, const char *relative_path); + public: + void OnWritableFileClose(); + Result CopySaveFromFileSystem(fs::fsa::IFileSystem *save_fs); + public: + /* Overridden from IPathResolutionFileSystem */ + virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, fs::OpenMode mode) override; + virtual Result CommitImpl() override; + + /* Overridden from IPathResolutionFileSystem but not commands. */ + virtual Result CommitProvisionallyImpl(s64 counter) override; + virtual Result RollbackImpl() override; + + /* Explicitly overridden to be not implemented. */ + virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) override; + virtual Result GetTotalSpaceSizeImpl(s64 *out, const char *path) override; + virtual Result GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) override; + virtual Result QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) override; + virtual Result FlushImpl() override; + }; + +} diff --git a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp index 0f827e060..99a8213cb 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp @@ -14,26 +14,30 @@ * along with this program. If not, see . */ #pragma once -#include "fssystem_path_resolution_filesystem.hpp" +#include "impl/fssystem_path_resolution_filesystem.hpp" namespace ams::fssystem { - class SubDirectoryFileSystem : public IPathResolutionFileSystem { + class SubDirectoryFileSystem : public impl::IPathResolutionFileSystem { NON_COPYABLE(SubDirectoryFileSystem); private: - using PathResolutionFileSystem = IPathResolutionFileSystem; + using PathResolutionFileSystem = impl::IPathResolutionFileSystem; + friend class impl::IPathResolutionFileSystem; private: char *base_path; size_t base_path_len; - bool unc_preserved; public: - SubDirectoryFileSystem(std::shared_ptr fs, const char *bp); - SubDirectoryFileSystem(std::shared_ptr fs, const char *bp, bool unc); + SubDirectoryFileSystem(std::shared_ptr fs, const char *bp, bool unc = false); + SubDirectoryFileSystem(std::unique_ptr &&fs, const char *bp, bool unc = false); virtual ~SubDirectoryFileSystem(); + protected: + inline std::optional> GetAccessorLock() const { + /* No accessor lock is needed. */ + return std::nullopt; + } private: Result Initialize(const char *bp); - public: Result ResolveFullPath(char *out, size_t out_size, const char *relative_path); }; diff --git a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp index 4376c6011..62aa3bf5d 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp @@ -15,9 +15,119 @@ */ #pragma once #include "../fs/fs_common.hpp" +#include "../fs/fs_file.hpp" +#include "../fs/fs_directory.hpp" +#include "../fs/fs_filesystem.hpp" +#include "fssystem_path_tool.hpp" namespace ams::fssystem { + namespace impl { + + /* Iteration. */ + template + Result IterateDirectoryRecursivelyImpl(fs::fsa::IFileSystem *fs, char *work_path, size_t work_path_size, fs::DirectoryEntry *dir_ent, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + /* Open the directory. */ + std::unique_ptr dir; + R_TRY(fs->OpenDirectory(&dir, work_path, fs::OpenDirectoryMode_All)); + + const size_t parent_len = strnlen(work_path, work_path_size - 1); + + /* Read and handle entries. */ + while (true) { + /* Read a single entry. */ + s64 read_count = 0; + R_TRY(dir->Read(&read_count, dir_ent, 1)); + + /* If we're out of entries, we're done. */ + if (read_count == 0) { + break; + } + + /* Validate child path size. */ + const size_t child_name_len = strnlen(dir_ent->name, sizeof(dir_ent->name) - 1); + const bool is_dir = dir_ent->type == fs::DirectoryEntryType_Directory; + const size_t separator_size = is_dir ? 1 : 0; + R_UNLESS(parent_len + child_name_len + separator_size < work_path_size, fs::ResultTooLongPath()); + + /* Set child path. */ + std::strncat(work_path, dir_ent->name, work_path_size - parent_len - 1); + { + if (is_dir) { + /* Enter directory. */ + R_TRY(on_enter_dir(work_path, *dir_ent)); + + /* Append separator, recurse. */ + std::strncat(work_path, "/", work_path_size - (parent_len + child_name_len) - 1); + R_TRY(IterateDirectoryRecursivelyImpl(fs, work_path, work_path_size, dir_ent, on_enter_dir, on_exit_dir, on_file)); + + /* Exit directory. */ + R_TRY(on_exit_dir(work_path, *dir_ent)); + } else { + /* Call file handler. */ + R_TRY(on_file(work_path, *dir_ent)); + } + } + + /* Restore parent path. */ + work_path[parent_len] = StringTraits::NullTerminator; + } + + return ResultSuccess(); + } + + /* TODO: Cleanup. */ + + } + + /* Iteration API */ + template + Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *root_path, char *work_path, size_t work_path_size, fs::DirectoryEntry *dir_ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + AMS_ASSERT(work_path_size >= fs::EntryNameLengthMax + 1); + + /* Get size of the root path. */ + size_t root_path_len = strnlen(root_path, fs::EntryNameLengthMax + 1); + R_UNLESS(root_path_len <= fs::EntryNameLengthMax, fs::ResultTooLongPath()); + + /* Copy root path in, add a / if necessary. */ + std::memcpy(work_path, root_path, root_path_len); + if (!PathTool::IsSeparator(work_path[root_path_len - 1])) { + work_path[root_path_len++] = StringTraits::DirectorySeparator; + } + + /* Make sure the result path is still valid. */ + R_UNLESS(root_path_len <= fs::EntryNameLengthMax, fs::ResultTooLongPath()); + work_path[root_path_len] = StringTraits::NullTerminator; + + return impl::IterateDirectoryRecursivelyImpl(fs, work_path, work_path_size, dir_ent_buf, on_enter_dir, on_exit_dir, on_file); + } + + template + Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *root_path, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + fs::DirectoryEntry dir_entry = {}; + char work_path[fs::EntryNameLengthMax + 1] = {}; + return IterateDirectoryRecursively(fs, root_path, work_path, sizeof(work_path), &dir_entry, on_enter_dir, on_exit_dir, on_file); + } + + template + Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + return IterateDirectoryRecursively(fs, PathTool::RootPath, on_enter_dir, on_exit_dir, on_file); + } + + /* TODO: Cleanup API */ + + /* Copy API. */ + Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size); + NX_INLINE Result CopyFile(fs::fsa::IFileSystem *fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) { + return CopyFile(fs, fs, dst_parent_path, src_path, dir_ent, work_buf, work_buf_size); + } + + Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size); + NX_INLINE Result CopyDirectoryRecursively(fs::fsa::IFileSystem *fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) { + return CopyDirectoryRecursively(fs, fs, dst_path, src_path, work_buf, work_buf_size); + } + + /* Semaphore adapter class. */ class SemaphoreAdapter : public os::Semaphore { public: SemaphoreAdapter(int c, int mc) : os::Semaphore(c, mc) { /* ... */ } @@ -31,4 +141,29 @@ namespace ams::fssystem { } }; + /* Other utility. */ + Result EnsureDirectoryExistsRecursively(fs::fsa::IFileSystem *fs, const char *path); + + template + NX_INLINE Result RetryFinitelyForTargetLocked(F f) { + /* Retry up to 10 times, 100ms between retries. */ + constexpr s32 MaxRetryCount = 10; + constexpr u64 RetryWaitTime = 100'000'000ul; + + s32 remaining_retries = MaxRetryCount; + while (true) { + R_TRY_CATCH(f()) { + R_CATCH(fs::ResultTargetLocked) { + R_UNLESS(remaining_retries > 0, fs::ResultTargetLocked()); + + remaining_retries--; + svcSleepThread(RetryWaitTime); + continue; + } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + } + } diff --git a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_path_resolution_filesystem.hpp b/stratosphere/libstratosphere/include/stratosphere/fssystem/impl/fssystem_path_resolution_filesystem.hpp similarity index 75% rename from stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_path_resolution_filesystem.hpp rename to stratosphere/libstratosphere/include/stratosphere/fssystem/impl/fssystem_path_resolution_filesystem.hpp index d0d99e2b4..939085970 100644 --- a/stratosphere/libstratosphere/include/stratosphere/fssystem/fssystem_path_resolution_filesystem.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/fssystem/impl/fssystem_path_resolution_filesystem.hpp @@ -14,27 +14,29 @@ * along with this program. If not, see . */ #pragma once -#include "../fs/fs_common.hpp" -#include "../fs/fsa/fs_ifile.hpp" -#include "../fs/fsa/fs_idirectory.hpp" -#include "../fs/fsa/fs_ifilesystem.hpp" +#include "../../fs/fs_common.hpp" +#include "../../fs/fsa/fs_ifile.hpp" +#include "../../fs/fsa/fs_idirectory.hpp" +#include "../../fs/fsa/fs_ifilesystem.hpp" -namespace ams::fssystem { +namespace ams::fssystem::impl { template class IPathResolutionFileSystem : public fs::fsa::IFileSystem { NON_COPYABLE(IPathResolutionFileSystem); private: std::shared_ptr shared_fs; - fs::fsa::IFileSystem *base_fs; + std::unique_ptr unique_fs; bool unc_preserved; + protected: + fs::fsa::IFileSystem * const base_fs; public: - IPathResolutionFileSystem(std::shared_ptr fs) : shared_fs(std::move(fs)), unc_preserved(false) { - this->base_fs = this->shared_fs.get(); + IPathResolutionFileSystem(std::shared_ptr fs, bool unc = false) : shared_fs(std::move(fs)), unc_preserved(unc), base_fs(shared_fs.get()) { + /* ... */ } - IPathResolutionFileSystem(std::shared_ptr fs, bool unc) : shared_fs(std::move(fs)), unc_preserved(unc) { - this->base_fs = this->shared_fs.get(); + IPathResolutionFileSystem(std::unique_ptr &&fs, bool unc = false) : unique_fs(std::move(fs)), unc_preserved(unc), base_fs(unique_fs.get()) { + /* ... */ } virtual ~IPathResolutionFileSystem() { /* ... */ } @@ -47,6 +49,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->CreateFile(full_path, size, option); } @@ -54,6 +57,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->DeleteFile(full_path); } @@ -61,6 +65,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->CreateDirectory(full_path); } @@ -68,6 +73,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->DeleteDirectory(full_path); } @@ -75,6 +81,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->DeleteDirectoryRecursively(full_path); } @@ -84,6 +91,7 @@ namespace ams::fssystem { R_TRY(static_cast(this)->ResolveFullPath(old_full_path, sizeof(old_full_path), old_path)); R_TRY(static_cast(this)->ResolveFullPath(new_full_path, sizeof(new_full_path), new_path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->RenameFile(old_path, new_path); } @@ -93,6 +101,7 @@ namespace ams::fssystem { R_TRY(static_cast(this)->ResolveFullPath(old_full_path, sizeof(old_full_path), old_path)); R_TRY(static_cast(this)->ResolveFullPath(new_full_path, sizeof(new_full_path), new_path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->RenameDirectory(old_path, new_path); } @@ -100,6 +109,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->GetEntryType(out, full_path); } @@ -107,6 +117,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->OpenFile(out_file, full_path, mode); } @@ -114,17 +125,20 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->OpenDirectory(out_dir, full_path, mode); } virtual Result CommitImpl() override { - return this->base_fs->Rollback(); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); + return this->base_fs->Commit(); } virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) override { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->GetFreeSpaceSize(out, full_path); } @@ -132,6 +146,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->GetTotalSpaceSize(out, full_path); } @@ -139,6 +154,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->CleanDirectoryRecursively(full_path); } @@ -146,6 +162,7 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->GetFileTimeStampRaw(out, full_path); } @@ -153,19 +170,23 @@ namespace ams::fssystem { char full_path[fs::EntryNameLengthMax + 1]; R_TRY(static_cast(this)->ResolveFullPath(full_path, sizeof(full_path), path)); + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->QueryEntry(dst, dst_size, src, src_size, query, full_path); } /* These aren't accessible as commands. */ virtual Result CommitProvisionallyImpl(s64 counter) override { + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->CommitProvisionally(counter); } virtual Result RollbackImpl() override { + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->Rollback(); } virtual Result FlushImpl() override { + std::optional optional_lock = static_cast(this)->GetAccessorLock(); return this->base_fs->Flush(); } }; diff --git a/stratosphere/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp b/stratosphere/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp index 77758190e..d5f86dd28 100644 --- a/stratosphere/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp @@ -56,6 +56,10 @@ namespace ams::sf::cmif { return this->impl_metadata.GetOutObjectCount(); } + constexpr size_t GetImplOutHeadersSize() const { + return this->impl_metadata.GetOutHeadersSize(); + } + constexpr size_t GetImplOutDataTotalSize() const { return this->impl_metadata.GetOutDataSize() + this->impl_metadata.GetOutHeadersSize(); } diff --git a/stratosphere/libstratosphere/source/cfg/cfg_flags.cpp b/stratosphere/libstratosphere/source/cfg/cfg_flags.cpp index 2f29a8655..8b5e04368 100644 --- a/stratosphere/libstratosphere/source/cfg/cfg_flags.cpp +++ b/stratosphere/libstratosphere/source/cfg/cfg_flags.cpp @@ -47,8 +47,8 @@ namespace ams::cfg { } /* Flag utilities. */ - bool HasFlag(ncm::ProgramId program_id, const char *flag) { - return HasContentSpecificFlag(program_id, flag) || (IsHblProgramId(program_id) && HasHblFlag(flag)); + bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag) { + return HasContentSpecificFlag(process_info.program_id, flag) || (process_info.override_status.IsHbl() && HasHblFlag(flag)); } bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag) { diff --git a/stratosphere/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp b/stratosphere/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp index 24d17928b..0da78f51c 100644 --- a/stratosphere/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp +++ b/stratosphere/libstratosphere/source/fssystem/fssystem_directory_redirection_filesystem.cpp @@ -17,13 +17,17 @@ namespace ams::fssystem { - DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr fs, const char *before, const char *after) : PathResolutionFileSystem(fs) { + DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr fs, const char *before, const char *after, bool unc) + : PathResolutionFileSystem(fs, unc) + { this->before_dir = nullptr; this->after_dir = nullptr; R_ASSERT(this->Initialize(before, after)); } - DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr fs, const char *before, const char *after, bool unc) : PathResolutionFileSystem(fs, unc) { + DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::unique_ptr &&fs, const char *before, const char *after, bool unc) + : PathResolutionFileSystem(std::forward>(fs), unc) + { this->before_dir = nullptr; this->after_dir = nullptr; R_ASSERT(this->Initialize(before, after)); diff --git a/stratosphere/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp b/stratosphere/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp new file mode 100644 index 000000000..c7d7530b8 --- /dev/null +++ b/stratosphere/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + constexpr size_t IdealWorkBufferSize = 0x100000; /* 1 MiB */ + + constexpr const char CommittedDirectoryPath[] = "/0/"; + constexpr const char WorkingDirectoryPath[] = "/1/"; + constexpr const char SynchronizingDirectoryPath[] = "/_/"; + + class DirectorySaveDataFile : public fs::fsa::IFile { + private: + std::unique_ptr base_file; + DirectorySaveDataFileSystem *parent_fs; + fs::OpenMode open_mode; + public: + DirectorySaveDataFile(std::unique_ptr f, DirectorySaveDataFileSystem *p, fs::OpenMode m) : base_file(std::move(f)), parent_fs(p), open_mode(m) { + /* ... */ + } + + virtual ~DirectorySaveDataFile() { + /* Observe closing of writable file. */ + if (this->open_mode & fs::OpenMode_Write) { + this->parent_fs->OnWritableFileClose(); + } + } + public: + virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + return this->base_file->Read(out, offset, buffer, size, option); + } + + virtual Result GetSizeImpl(s64 *out) override { + return this->base_file->GetSize(out); + } + + virtual Result FlushImpl() override { + return this->base_file->Flush(); + } + + virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + return this->base_file->Write(offset, buffer, size, option); + } + + virtual Result SetSizeImpl(s64 size) override { + return this->base_file->SetSize(size); + } + + virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + return this->base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + return this->base_file->GetDomainObjectId(); + } + }; + + } + + DirectorySaveDataFileSystem::DirectorySaveDataFileSystem(std::shared_ptr fs) + : PathResolutionFileSystem(fs) + { + /* ... */ + } + + DirectorySaveDataFileSystem::DirectorySaveDataFileSystem(std::unique_ptr fs) + : PathResolutionFileSystem(std::forward>(fs)) + { + /* ... */ + } + + DirectorySaveDataFileSystem::~DirectorySaveDataFileSystem() { + /* ... */ + } + + Result DirectorySaveDataFileSystem::Initialize() { + /* Nintendo does not acquire the lock here, but I think we probably should. */ + std::scoped_lock lk(this->accessor_mutex); + + fs::DirectoryEntryType type; + + /* Check that the working directory exists. */ + R_TRY_CATCH(this->base_fs->GetEntryType(&type, WorkingDirectoryPath)) { + /* If path isn't found, create working directory and committed directory. */ + R_CATCH(fs::ResultPathNotFound) { + R_TRY(this->base_fs->CreateDirectory(WorkingDirectoryPath)); + R_TRY(this->base_fs->CreateDirectory(CommittedDirectoryPath)); + } + } R_END_TRY_CATCH; + + /* Now check for the committed directory. */ + R_TRY_CATCH(this->base_fs->GetEntryType(&type, CommittedDirectoryPath)) { + /* Committed doesn't exist, so synchronize and rename. */ + R_CATCH(fs::ResultPathNotFound) { + R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath)); + R_TRY(this->base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath)); + return ResultSuccess(); + } + } R_END_TRY_CATCH; + + /* The committed directory exists, so synchronize it to the working directory. */ + return this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath); + } + + Result DirectorySaveDataFileSystem::AllocateWorkBuffer(std::unique_ptr *out, size_t *out_size, size_t size) { + /* Repeatedly try to allocate until success. */ + while (size > 0x200) { + /* Allocate the buffer. */ + if (auto mem = new (std::nothrow) u8[size]; mem != nullptr) { + out->reset(mem); + *out_size = size; + return ResultSuccess(); + } else { + /* Divide size by two. */ + size >>= 1; + } + } + + /* TODO: Return a result here? Nintendo does not, but they have other allocation failed results. */ + /* Consider returning ResultFsAllocationFailureInDirectorySaveDataFileSystem? */ + AMS_ASSERT(false); + } + + Result DirectorySaveDataFileSystem::SynchronizeDirectory(const char *dst, const char *src) { + /* Delete destination dir and recreate it. */ + R_TRY_CATCH(this->base_fs->DeleteDirectoryRecursively(dst)) { + R_CATCH(fs::ResultPathNotFound) { /* Nintendo returns error unconditionally, but I think that's a bug in their code. */} + } R_END_TRY_CATCH; + + R_TRY(this->base_fs->CreateDirectory(dst)); + + /* Get a work buffer to work with. */ + std::unique_ptr work_buf; + size_t work_buf_size; + R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize)); + + /* Copy the directory recursively. */ + return fssystem::CopyDirectoryRecursively(this->base_fs, dst, src, work_buf.get(), work_buf_size); + } + + Result DirectorySaveDataFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) { + R_UNLESS(strnlen(relative_path, fs::EntryNameLengthMax + 1) < fs::EntryNameLengthMax + 1, fs::ResultTooLongPath()); + R_UNLESS(PathTool::IsSeparator(relative_path[0]), fs::ResultInvalidPath()); + + /* Copy working directory path. */ + std::strncpy(out, WorkingDirectoryPath, out_size); + out[out_size - 1] = StringTraits::NullTerminator; + + /* Normalize it. */ + constexpr size_t WorkingDirectoryPathLength = sizeof(WorkingDirectoryPath) - 1; + size_t normalized_length; + return PathTool::Normalize(out + WorkingDirectoryPathLength - 1, &normalized_length, relative_path, out_size - (WorkingDirectoryPathLength - 1)); + } + + void DirectorySaveDataFileSystem::OnWritableFileClose() { + std::scoped_lock lk(this->accessor_mutex); + this->open_writable_files--; + + /* Nintendo does not check this, but I think it's sensible to do so. */ + AMS_ASSERT(this->open_writable_files >= 0); + } + + Result DirectorySaveDataFileSystem::CopySaveFromFileSystem(fs::fsa::IFileSystem *save_fs) { + /* If the input save is null, there's nothing to copy. */ + R_UNLESS(save_fs != nullptr, ResultSuccess()); + + /* Get a work buffer to work with. */ + std::unique_ptr work_buf; + size_t work_buf_size; + R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBufferSize)); + + /* Copy the directory recursively. */ + R_TRY(fssystem::CopyDirectoryRecursively(this->base_fs, save_fs, PathTool::RootPath, PathTool::RootPath, work_buf.get(), work_buf_size)); + + return this->Commit(); + } + + /* Overridden from IPathResolutionFileSystem */ + Result DirectorySaveDataFileSystem::OpenFileImpl(std::unique_ptr *out_file, const char *path, fs::OpenMode mode) { + char full_path[fs::EntryNameLengthMax + 1]; + R_TRY(this->ResolveFullPath(full_path, sizeof(full_path), path)); + + std::scoped_lock lk(this->accessor_mutex); + std::unique_ptr base_file; + R_TRY(this->base_fs->OpenFile(&base_file, full_path, mode)); + + std::unique_ptr file(new (std::nothrow) DirectorySaveDataFile(std::move(base_file), this, mode)); + R_UNLESS(file != nullptr, fs::ResultAllocationFailureInDirectorySaveDataFileSystem()); + + if (mode & fs::OpenMode_Write) { + this->open_writable_files++; + } + + *out_file = std::move(file); + return ResultSuccess(); + } + + Result DirectorySaveDataFileSystem::CommitImpl() { + /* Here, Nintendo does the following (with retries): */ + /* - Rename Committed -> Synchronizing. */ + /* - Synchronize Working -> Synchronizing (deleting Synchronizing). */ + /* - Rename Synchronizing -> Committed. */ + std::scoped_lock lk(this->accessor_mutex); + + R_UNLESS(this->open_writable_files == 0, fs::ResultPreconditionViolation()); + + const auto RenameCommitedDir = [&]() { return this->base_fs->RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath); }; + const auto SynchronizeWorkingDir = [&]() { return this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); }; + const auto RenameSynchronizingDir = [&]() { return this->base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); }; + + /* Rename Committed -> Synchronizing. */ + R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(RenameCommitedDir))); + + /* - Synchronize Working -> Synchronizing (deleting Synchronizing). */ + R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(SynchronizeWorkingDir))); + + /* - Rename Synchronizing -> Committed. */ + R_TRY(fssystem::RetryFinitelyForTargetLocked(std::move(RenameSynchronizingDir))); + + /* TODO: Should I call this->base_fs->Commit()? Nintendo does not. */ + return ResultSuccess(); + } + + /* Overridden from IPathResolutionFileSystem but not commands. */ + Result DirectorySaveDataFileSystem::CommitProvisionallyImpl(s64 counter) { + /* Nintendo does nothing here. */ + return ResultSuccess(); + } + + Result DirectorySaveDataFileSystem::RollbackImpl() { + /* Initialize overwrites the working directory with the committed directory. */ + return this->Initialize(); + } + + /* Explicitly overridden to be not implemented. */ + Result DirectorySaveDataFileSystem::GetFreeSpaceSizeImpl(s64 *out, const char *path) { + return fs::ResultNotImplemented(); + } + + Result DirectorySaveDataFileSystem::GetTotalSpaceSizeImpl(s64 *out, const char *path) { + return fs::ResultNotImplemented(); + } + + Result DirectorySaveDataFileSystem::GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) { + return fs::ResultNotImplemented(); + } + + Result DirectorySaveDataFileSystem::QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) { + return fs::ResultNotImplemented(); + } + + Result DirectorySaveDataFileSystem::FlushImpl() { + return fs::ResultNotImplemented(); + } + +} diff --git a/stratosphere/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp b/stratosphere/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp index bcfafcac4..f359b10fa 100644 --- a/stratosphere/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp +++ b/stratosphere/libstratosphere/source/fssystem/fssystem_subdirectory_filesystem.cpp @@ -17,12 +17,16 @@ namespace ams::fssystem { - SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr fs, const char *bp) : PathResolutionFileSystem(fs) { + SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr fs, const char *bp, bool unc) + : PathResolutionFileSystem(fs, unc) + { this->base_path = nullptr; R_ASSERT(this->Initialize(bp)); } - SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr fs, const char *bp, bool unc) : PathResolutionFileSystem(fs, unc) { + SubDirectoryFileSystem::SubDirectoryFileSystem(std::unique_ptr &&fs, const char *bp, bool unc) + : PathResolutionFileSystem(std::forward>(fs), unc) + { this->base_path = nullptr; R_ASSERT(this->Initialize(bp)); } diff --git a/stratosphere/libstratosphere/source/fssystem/fssystem_utility.cpp b/stratosphere/libstratosphere/source/fssystem/fssystem_utility.cpp new file mode 100644 index 000000000..6b8254dd6 --- /dev/null +++ b/stratosphere/libstratosphere/source/fssystem/fssystem_utility.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + inline Result EnsureDirectoryExists(fs::fsa::IFileSystem *fs, const char *path) { + R_TRY_CATCH(fs->CreateDirectory(path)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* If path already exists, there's no problem. */ } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + } + + Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_parent_path, const char *src_path, const fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size) { + /* Open source file. */ + std::unique_ptr src_file; + R_TRY(src_fs->OpenFile(&src_file, src_path, fs::OpenMode_Read)); + + /* Open dst file. */ + std::unique_ptr dst_file; + { + char dst_path[fs::EntryNameLengthMax + 1]; + const size_t original_size = static_cast(std::snprintf(dst_path, sizeof(dst_path), "%s%s", dst_parent_path, entry->name)); + /* TODO: Error code? N aborts here. */ + AMS_ASSERT(original_size < sizeof(dst_path)); + + R_TRY(dst_fs->CreateFile(dst_path, entry->file_size)); + R_TRY(dst_fs->OpenFile(&dst_file, dst_path, fs::OpenMode_Write)); + } + + /* Read/Write file in work buffer sized chunks. */ + s64 remaining = entry->file_size; + s64 offset = 0; + while (remaining > 0) { + size_t read_size; + R_TRY(src_file->Read(&read_size, offset, work_buf, work_buf_size, fs::ReadOption())); + R_TRY(dst_file->Write(offset, work_buf, read_size, fs::WriteOption())); + + remaining -= read_size; + offset += read_size; + } + + return ResultSuccess(); + } + + Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const char *dst_path, const char *src_path, void *work_buf, size_t work_buf_size) { + char dst_path_buf[fs::EntryNameLengthMax + 1]; + const size_t original_size = static_cast(std::snprintf(dst_path_buf, sizeof(dst_path_buf), "%s", dst_path)); + AMS_ASSERT(original_size < sizeof(dst_path_buf)); + + return IterateDirectoryRecursively(src_fs, src_path, + [&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On Enter Directory */ + /* Update path, create new dir. */ + std::strncat(dst_path_buf, entry.name, sizeof(dst_path_buf) - strnlen(dst_path_buf, sizeof(dst_path_buf) - 1) - 1); + std::strncat(dst_path_buf, "/", sizeof(dst_path_buf) - strnlen(dst_path_buf, sizeof(dst_path_buf) - 1) - 1); + return dst_fs->CreateDirectory(dst_path_buf); + }, + [&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On Exit Directory */ + /* Check we have a parent directory. */ + const size_t len = strnlen(dst_path_buf, sizeof(dst_path_buf)); + R_UNLESS(len >= 2, fs::ResultInvalidPathFormat()); + + /* Find previous separator, add null terminator */ + char *cur = &dst_path_buf[len - 2]; + while (!PathTool::IsSeparator(*cur) && cur > dst_path_buf) { + cur--; + } + cur[1] = StringTraits::NullTerminator; + + return ResultSuccess(); + }, + [&](const char *path, const fs::DirectoryEntry &entry) -> Result { /* On File */ + return CopyFile(dst_fs, src_fs, dst_path_buf, path, &entry, work_buf, work_buf_size); + } + ); + } + + Result EnsureDirectoryExistsRecursively(fs::fsa::IFileSystem *fs, const char *path) { + /* Normalize the path. */ + char normalized_path[fs::EntryNameLengthMax + 1]; + size_t normalized_path_len; + R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, path, sizeof(normalized_path))); + + /* Repeatedly call CreateDirectory on each directory leading to the target. */ + for (size_t i = 1; i < normalized_path_len; i++) { + /* If we detect a separator, create the directory. */ + if (PathTool::IsSeparator(normalized_path[i])) { + normalized_path[i] = StringTraits::NullTerminator; + R_TRY(EnsureDirectoryExists(fs, normalized_path)); + normalized_path[i] = StringTraits::DirectorySeparator; + } + } + + /* Call CreateDirectory on the final path. */ + R_TRY(EnsureDirectoryExists(fs, normalized_path)); + + return ResultSuccess(); + } + +} diff --git a/stratosphere/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp b/stratosphere/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp index 05705538b..15325a3ad 100644 --- a/stratosphere/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp +++ b/stratosphere/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp @@ -149,8 +149,8 @@ namespace ams::sf::cmif { /* Write out header. */ constexpr size_t out_header_size = sizeof(CmifDomainOutHeader); - const size_t impl_out_data_total_size = this->GetImplOutDataTotalSize(); - AMS_ASSERT(out_header_size + impl_out_data_total_size <= raw_data.GetSize()); + const size_t impl_out_headers_size = this->GetImplOutHeadersSize(); + AMS_ASSERT(out_header_size + impl_out_headers_size <= raw_data.GetSize()); *reinterpret_cast(raw_data.GetPointer()) = CmifDomainOutHeader{ .num_out_objects = 0, }; /* Set output raw data. */