From a6218ed814556f6435b02df859a6b637aba4bb4f Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 26 Jun 2020 02:24:52 -0700 Subject: [PATCH] sysupdater: implement content meta mounting --- .../include/stratosphere/fs.hpp | 1 + .../fs/fs_shared_filesystem_holder.hpp | 54 +++++ .../include/vapours/results/fs_results.hpp | 2 + .../include/vapours/util/util_string_util.hpp | 48 +++++ stratosphere/ams_mitm/source/amsmitm_main.cpp | 4 +- .../source/sysupdater/sysupdater_fs_utils.cpp | 200 +++++++++++++++++- .../source/sysupdater/sysupdater_fs_utils.hpp | 2 +- 7 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/fs/fs_shared_filesystem_holder.hpp diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index 96093da58..faa1f851a 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_shared_filesystem_holder.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_shared_filesystem_holder.hpp new file mode 100644 index 000000000..9a4c5931f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_shared_filesystem_holder.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-2020 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 +#include +#include +#include +#include + +namespace ams::fs { + + class SharedFileSystemHolder : public fsa::IFileSystem, public impl::Newable { + NON_COPYABLE(SharedFileSystemHolder); + NON_MOVEABLE(SharedFileSystemHolder); + private: + std::shared_ptr fs; + public: + SharedFileSystemHolder(std::shared_ptr f) : fs(std::move(f)) { /* ... */ } + public: + virtual Result CreateFileImpl(const char *path, s64 size, int flags) override { return this->fs->CreateFile(path, size, flags); } + virtual Result DeleteFileImpl(const char *path) override { return this->fs->DeleteFile(path); } + virtual Result CreateDirectoryImpl(const char *path) override { return this->fs->CreateDirectory(path); } + virtual Result DeleteDirectoryImpl(const char *path) override { return this->fs->DeleteDirectory(path); } + virtual Result DeleteDirectoryRecursivelyImpl(const char *path) override { return this->fs->DeleteDirectoryRecursively(path); } + virtual Result RenameFileImpl(const char *old_path, const char *new_path) override { return this->fs->RenameFile(old_path, new_path); } + virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) override { return this->fs->RenameDirectory(old_path, new_path); } + virtual Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) override { return this->fs->GetEntryType(out, path); } + virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, fs::OpenMode mode) override { return this->fs->OpenFile(out_file, path, mode); } + virtual Result OpenDirectoryImpl(std::unique_ptr *out_dir, const char *path, fs::OpenDirectoryMode mode) override { return this->fs->OpenDirectory(out_dir, path, mode); } + virtual Result CommitImpl() override { return this->fs->Commit(); } + virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) override { return this->fs->GetFreeSpaceSize(out, path); } + virtual Result GetTotalSpaceSizeImpl(s64 *out, const char *path) override { return this->fs->GetTotalSpaceSize(out, path); } + virtual Result CleanDirectoryRecursivelyImpl(const char *path) override { return this->fs->CleanDirectoryRecursively(path); } + + /* These aren't accessible as commands. */ + virtual Result CommitProvisionallyImpl(s64 counter) override { return this->fs->CommitProvisionally(counter); } + virtual Result RollbackImpl() override { return this->fs->Rollback(); } + virtual Result FlushImpl() override { return this->fs->Flush(); } + }; + +} diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 0eeb9ada7..88987e73b 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -78,6 +78,8 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemA, 3247); R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemB, 3248); R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemC, 3249); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemProxyCoreImplD, 3256); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemProxyCoreImplE, 3257); R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemCreatorA, 3280); R_DEFINE_ERROR_RESULT(AllocationFailureInRomFileSystemCreatorA, 3281); R_DEFINE_ERROR_RESULT(AllocationFailureInStorageOnNcaCreatorA, 3288); diff --git a/libraries/libvapours/include/vapours/util/util_string_util.hpp b/libraries/libvapours/include/vapours/util/util_string_util.hpp index a15367690..d06d96a7d 100644 --- a/libraries/libvapours/include/vapours/util/util_string_util.hpp +++ b/libraries/libvapours/include/vapours/util/util_string_util.hpp @@ -20,6 +20,54 @@ namespace ams::util { + template + constexpr T ToLower(T c) { + return ('A' <= c && c <= 'Z') ? (c - 'A' + 'a') : c; + } + + template + constexpr T ToUpper(T c) { + return ('a' <= c && c <= 'z') ? (c - 'a' + 'A') : c; + } + + template + int Strncmp(const T *lhs, const T *rhs, int count) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + AMS_ABORT_UNLESS(count >= 0); + + if (count == 0) { + return 0; + } + + T l, r; + do { + l = *(lhs++); + r = *(rhs++); + } while (l && (l == r) && (--count)); + + return l - r; + } + + template + int Strnicmp(const T *lhs, const T *rhs, int count) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + AMS_ABORT_UNLESS(count >= 0); + + if (count == 0) { + return 0; + } + + T l, r; + do { + l = ::ams::util::ToLower(*(lhs++)); + r = ::ams::util::ToLower(*(rhs++)); + } while (l && (l == r) && (--count)); + + return l - r; + } + template constexpr int Strlcpy(T *dst, const T *src, int count) { AMS_ASSERT(dst != nullptr); diff --git a/stratosphere/ams_mitm/source/amsmitm_main.cpp b/stratosphere/ams_mitm/source/amsmitm_main.cpp index c9524af7a..c4a80d387 100644 --- a/stratosphere/ams_mitm/source/amsmitm_main.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_main.cpp @@ -88,8 +88,8 @@ void __appInit(void) { /* Initialize fssystem library. */ fssystem::InitializeForFileSystemProxy(); - /* Configure ncm to use fssystem library to mount content. */ - ncm::SetMountContentMetaFunction(mitm::sysupdater::MountContentMeta); + /* Configure ncm to use fssystem library to mount content from the sd card. */ + ncm::SetMountContentMetaFunction(mitm::sysupdater::MountSdCardContentMeta); ams::CheckApiVersion(); } diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp index 4bcd746ea..3c6a676a6 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp @@ -18,9 +18,203 @@ namespace ams::mitm::sysupdater { - Result MountContentMeta(const char *mount_name, const char *path) { - /* TODO: Implement */ - AMS_ABORT(); + namespace { + + constexpr inline const char * const NcaExtension = ".nca"; + constexpr inline const char * const NspExtension = ".nsp"; + constexpr inline const size_t NcaExtensionSize = 4; + constexpr inline const size_t NspExtensionSize = 4; + + static_assert(NcaExtensionSize == NspExtensionSize); + constexpr inline const size_t NcaNspExtensionSize = NcaExtensionSize; + + constexpr inline std::underlying_type::type SdCardContentMetaPathNormalizeOption = fssrv::PathNormalizer::Option_PreserveTailSeparator | + fssrv::PathNormalizer::Option_HasMountName; + + Result CheckNcaOrNsp(const char **path) { + /* Ensure that the path is currently at the mount name delimeter. */ + R_UNLESS(std::strncmp(*path, ams::fs::impl::MountNameDelimiter, strnlen(ams::fs::impl::MountNameDelimiter, ams::fs::EntryNameLengthMax)) == 0, fs::ResultPathNotFound()); + + /* Advance past the :. */ + static_assert(ams::fs::impl::MountNameDelimiter[0] == ':'); + *path += 1; + + /* Ensure path is long enough for the extension. */ + const auto path_len = strnlen(*path, ams::fs::EntryNameLengthMax); + R_UNLESS(path_len > NcaNspExtensionSize, fs::ResultPathNotFound()); + + /* Get the extension. */ + const char * const extension = *path + path_len - NcaNspExtensionSize; + + /* Ensure nca or nsp. */ + const bool is_nca = util::Strnicmp(extension, NcaExtension, NcaNspExtensionSize) == 0; + const bool is_nsp = util::Strnicmp(extension, NspExtension, NcaNspExtensionSize) == 0; + R_UNLESS(is_nca || is_nsp, fs::ResultPathNotFound()); + + return ResultSuccess(); + } + + Result ParseMountName(const char **path, std::shared_ptr *out) { + /* The equivalent function here supports all the common mount names; we'll only support the SD card. */ + if (const auto sd_mount_len = strnlen(ams::fs::impl::SdCardFileSystemMountName, ams::fs::MountNameLengthMax); std::strncmp(*path, ams::fs::impl::SdCardFileSystemMountName, sd_mount_len) == 0) { + /* Open an sd card fs. */ + *path += sd_mount_len; + + /* Open the SD card. This uses libnx bindings. */ + FsFileSystem fs; + R_TRY(fsOpenSdCardFileSystem(std::addressof(fs))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_shared(fs); + R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInSdCardA()); + + /* Set the output fs. */ + *out = std::move(fsa); + } else { + return fs::ResultPathNotFound(); + } + + /* Ensure that there's something that could be a mount name delimiter. */ + R_UNLESS(strnlen(*path, fs::EntryNameLengthMax) != 0, fs::ResultPathNotFound()); + + return ResultSuccess(); + } + + Result ParseNsp(const char **path, std::shared_ptr *out, std::shared_ptr base_fs) { + const char *work_path = *path; + + /* Advance to the nsp extension. */ + while (true) { + if (util::Strnicmp(work_path, NspExtension, NspExtensionSize) == 0) { + if (work_path[NspExtensionSize] == '\x00' || work_path[NspExtensionSize] == '/') { + break; + } + work_path += NspExtensionSize; + } else { + R_UNLESS(*work_path != '\x00', fs::ResultPathNotFound()); + + work_path += 1; + } + } + + /* Advance past the extension. */ + work_path += NspExtensionSize; + + /* Get the nsp path. */ + char nsp_path[fs::EntryNameLengthMax + 1]; + R_UNLESS(static_cast(work_path - *path) <= sizeof(nsp_path), fs::ResultTooLongPath()); + std::memcpy(nsp_path, *path, work_path - *path); + nsp_path[work_path - *path] = '\x00'; + + /* Open the file storage. */ + std::shared_ptr file_storage = fssystem::AllocateShared(); + R_UNLESS(file_storage != nullptr, fs::ResultAllocationFailureInFileSystemProxyCoreImplD()); + R_TRY(file_storage->Initialize(std::move(base_fs), nsp_path, ams::fs::OpenMode_Read)); + + /* Create a partition fs. */ + R_TRY(fssystem::GetFileSystemCreatorInterfaces()->partition_fs_creator->Create(out, std::move(file_storage))); + + /* Update the path. */ + *path = work_path; + + return ResultSuccess(); + } + + Result ParseNca(const char **path, std::shared_ptr *out, std::shared_ptr base_fs) { + /* Open the file storage. */ + std::shared_ptr file_storage = fssystem::AllocateShared(); + R_UNLESS(file_storage != nullptr, fs::ResultAllocationFailureInFileSystemProxyCoreImplE()); + R_TRY(file_storage->Initialize(std::move(base_fs), *path, ams::fs::OpenMode_Read)); + + /* Create the nca reader. */ + std::shared_ptr nca_reader; + R_TRY(fssystem::GetFileSystemCreatorInterfaces()->storage_on_nca_creator->CreateNcaReader(std::addressof(nca_reader), file_storage)); + + /* NOTE: Here Nintendo validates program ID, but this does not need checking in the meta case. */ + + /* Set output reader. */ + *out = std::move(nca_reader); + return ResultSuccess(); + } + + Result OpenMetaStorage(std::shared_ptr *out, std::shared_ptr nca_reader, fssystem::NcaFsHeader::FsType *out_fs_type) { + /* Ensure the nca is a meta nca. */ + R_UNLESS(nca_reader->GetContentType() == fssystem::NcaHeader::ContentType::Meta, fs::ResultPreconditionViolation()); + + /* We only support SD card ncas, so ensure this isn't a gamecard nca. */ + R_UNLESS(nca_reader->GetDistributionType() != fssystem::NcaHeader::DistributionType::GameCard, fs::ResultPermissionDenied()); + + /* Here Nintendo would call GetPartitionIndex(), but we don't need to, because it's meta. */ + constexpr int MetaPartitionIndex = 0; + + /* Open fs header reader. */ + fssystem::NcaFsHeaderReader fs_header_reader; + R_TRY(fssystem::GetFileSystemCreatorInterfaces()->storage_on_nca_creator->Create(out, std::addressof(fs_header_reader), std::move(nca_reader), MetaPartitionIndex, false)); + + /* Set the output fs type. */ + *out_fs_type = fs_header_reader.GetFsType(); + return ResultSuccess(); + } + + Result OpenContentMetaFileSystem(std::shared_ptr *out, const char *path) { + /* Parse the mount name to get a filesystem. */ + const char *cur_path = path; + std::shared_ptr base_fs; + R_TRY(ParseMountName(std::addressof(cur_path), std::addressof(base_fs))); + + /* Ensure the path is an nca or nsp. */ + R_TRY(CheckNcaOrNsp(std::addressof(cur_path))); + + /* Try to parse as nsp. */ + std::shared_ptr nsp_fs; + if (R_SUCCEEDED(ParseNsp(std::addressof(cur_path), std::addressof(nsp_fs), base_fs))) { + /* nsp target is only allowed for type package, and we're assuming type meta. */ + R_UNLESS(*path != '\x00', fs::ResultInvalidArgument()); + + /* Use the nsp fs as the base fs. */ + base_fs = std::move(nsp_fs); + } + + /* Parse as nca. */ + std::shared_ptr nca_reader; + R_TRY(ParseNca(std::addressof(cur_path), std::addressof(nca_reader), std::move(base_fs))); + + /* Open meta storage. */ + std::shared_ptr storage; + fssystem::NcaFsHeader::FsType fs_type; + R_TRY(OpenMetaStorage(std::addressof(storage), std::move(nca_reader), std::addressof(fs_type))); + + /* Open the appropriate interface. */ + const auto * const creator_intfs = fssystem::GetFileSystemCreatorInterfaces(); + switch (fs_type) { + case fssystem::NcaFsHeader::FsType::PartitionFs: return creator_intfs->partition_fs_creator->Create(out, std::move(storage)); + case fssystem::NcaFsHeader::FsType::RomFs: return creator_intfs->rom_fs_creator->Create(out, std::move(storage)); + default: + return fs::ResultInvalidNcaFileSystemType(); + } + } + + } + + Result MountSdCardContentMeta(const char *mount_name, const char *path) { + /* Sanitize input. */ + /* NOTE: This is an internal API, so we won't bother with mount name sanitization. */ + R_UNLESS(path != nullptr, fs::ResultInvalidPath()); + + /* Normalize the path. */ + fssrv::PathNormalizer normalized_path(path, SdCardContentMetaPathNormalizeOption); + R_TRY(normalized_path.GetResult()); + + /* Open the filesystem. */ + std::shared_ptr fs; + R_TRY(OpenContentMetaFileSystem(std::addressof(fs), normalized_path.GetPath())); + + /* Create a holder for the fs. */ + std::unique_ptr unique_fs = std::make_unique(std::move(fs)); + R_UNLESS(unique_fs != nullptr, fs::ResultAllocationFailureInNew()); + + /* Register the fs. */ + return ams::fs::fsa::Register(mount_name, std::move(unique_fs)); } } diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp index c63df8dbf..8f93deb7b 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp @@ -18,6 +18,6 @@ namespace ams::mitm::sysupdater { - Result MountContentMeta(const char *mount_name, const char *path); + Result MountSdCardContentMeta(const char *mount_name, const char *path); }