From dc1404061cf3e28e7e170a9e14aef4f64ed00e26 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Tue, 25 Feb 2020 16:44:36 -0800 Subject: [PATCH] fs.mitm: implement layered html redirection (closes #814) --- .../include/stratosphere/fs.hpp | 23 +-- .../fs/fs_readonly_filesystem_adapter.hpp | 152 ++++++++++++++++++ .../include/stratosphere/fs/fsa/fs_ifile.hpp | 21 ++- .../source/fs_mitm/fs_mitm_service.cpp | 43 ++++- .../ams_mitm/source/fs_mitm/fs_shim.c | 26 +++ .../ams_mitm/source/fs_mitm/fs_shim.h | 3 + .../fsmitm_readonly_layered_filesystem.hpp | 95 +++++++++++ 7 files changed, 343 insertions(+), 20 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/fs/fs_readonly_filesystem_adapter.hpp create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index 60e62d90e..76c844a92 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -15,14 +15,15 @@ */ #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_remote_filesystem.hpp" -#include "fs/fs_istorage.hpp" -#include "fs/fs_remote_storage.hpp" -#include "fs/fs_file_storage.hpp" -#include "fs/fs_query_range.hpp" -#include "fs/fs_path_tool.hpp" -#include "fs/fs_path_utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_readonly_filesystem_adapter.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_readonly_filesystem_adapter.hpp new file mode 100644 index 000000000..9c3c031d3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_readonly_filesystem_adapter.hpp @@ -0,0 +1,152 @@ +/* + * 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 + +namespace ams::fs { + + class ReadOnlyFileAdapter : public fsa::IFile { + NON_COPYABLE(ReadOnlyFileAdapter); + private: + std::unique_ptr base_file; + public: + ReadOnlyFileAdapter(fsa::IFile *f) : base_file(f) { /* ... */ } + ReadOnlyFileAdapter(std::unique_ptr f) : base_file(std::move(f)) { /* ... */ } + virtual ~ReadOnlyFileAdapter() { /* ... */ } + public: + virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final { + /* Ensure that we can read these extents. */ + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, size, option, fs::OpenMode_Read)); + + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buffer != nullptr || size == 0); + + return this->base_file->Read(out, offset, buffer, size, option); + } + + virtual Result GetSizeImpl(s64 *out) override final { + return this->base_file->GetSize(out); + } + + virtual Result FlushImpl() override final { + return ResultSuccess(); + } + + virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result SetSizeImpl(s64 size) override final { + return fs::ResultUnsupportedOperation(); + } + + 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(); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + return this->base_file->GetDomainObjectId(); + } + }; + + class ReadOnlyFileSystemAdapter : public fsa::IFileSystem { + NON_COPYABLE(ReadOnlyFileSystemAdapter); + private: + std::shared_ptr shared_fs; + std::unique_ptr unique_fs; + protected: + fsa::IFileSystem * const base_fs; + public: + template + explicit ReadOnlyFileSystemAdapter(std::shared_ptr fs) : shared_fs(std::move(fs)), base_fs(shared_fs.get()) { static_assert(std::is_base_of::value); } + + template + explicit ReadOnlyFileSystemAdapter(std::unique_ptr fs) : unique_fs(std::move(fs)), base_fs(unique_fs.get()) { static_assert(std::is_base_of::value); } + + virtual ~ReadOnlyFileSystemAdapter() { /* ... */ } + public: + virtual Result CreateFileImpl(const char *path, s64 size, int flags) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result DeleteFileImpl(const char *path) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result CreateDirectoryImpl(const char *path) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result DeleteDirectoryImpl(const char *path) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result DeleteDirectoryRecursivelyImpl(const char *path) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result RenameFileImpl(const char *old_path, const char *new_path) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) override final { + return fs::ResultUnsupportedOperation(); + } + + virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const char *path) override final { + return this->base_fs->GetEntryType(out, path); + } + + virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, OpenMode mode) override final { + std::unique_ptr f; + R_TRY(this->base_fs->OpenFile(std::addressof(f), path, mode)); + + *out_file = std::make_unique(std::move(f)); + return ResultSuccess(); + } + + virtual Result OpenDirectoryImpl(std::unique_ptr *out_dir, const char *path, OpenDirectoryMode mode) override final { + return this->base_fs->OpenDirectory(out_dir, path, mode); + } + + virtual Result CommitImpl() override final { + return ResultSuccess(); + } + + virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) { + return fs::ResultUnsupportedOperation(); + } + + virtual Result GetTotalSpaceSizeImpl(s64 *out, const char *path) { + return fs::ResultUnsupportedOperation(); + } + + virtual Result CleanDirectoryRecursivelyImpl(const char *path) { + return fs::ResultUnsupportedOperation(); + } + + virtual Result GetFileTimeStampRawImpl(FileTimeStampRaw *out, const char *path) { + return this->base_fs->GetFileTimeStampRaw(out, path); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp index 77e59d6f9..0d474604e 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp @@ -16,6 +16,7 @@ #pragma once #include "../fs_common.hpp" #include "../fs_file.hpp" +#include "../fs_filesystem.hpp" #include "../fs_operate_range.hpp" namespace ams::fs::fsa { @@ -82,7 +83,25 @@ namespace ams::fs::fsa { /* TODO: This is a hack to allow the mitm API to work. Find a better way? */ virtual sf::cmif::DomainObjectId GetDomainObjectId() const = 0; protected: - /* ...? */ + Result DryRead(size_t *out, s64 offset, size_t size, const fs::ReadOption &option, OpenMode open_mode) { + /* Check that we can read. */ + R_UNLESS((open_mode & OpenMode_Read) != 0, fs::ResultInvalidOperationForOpenMode()); + + /* Get the file size, and validate our offset. */ + s64 file_size = 0; + R_TRY(this->GetSize(&file_size)); + R_UNLESS(offset <= file_size, fs::ResultOutOfRange()); + + *out = static_cast(std::min(file_size - offset, static_cast(size))); + return ResultSuccess(); + } + + Result DrySetSize(s64 size, OpenMode open_mode) { + /* Check that we can write. */ + R_UNLESS((open_mode & OpenMode_Write) != 0, fs::ResultInvalidOperationForOpenMode()); + + return ResultSuccess(); + } private: virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) = 0; virtual Result GetSizeImpl(s64 *out) = 0; 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 30f3b2aea..158abadf5 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp @@ -19,6 +19,7 @@ #include "fsmitm_boot0storage.hpp" #include "fsmitm_layered_romfs_storage.hpp" #include "fsmitm_save_utils.hpp" +#include "fsmitm_readonly_layered_filesystem.hpp" namespace ams::mitm::fs { @@ -84,11 +85,11 @@ namespace ams::mitm::fs { const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&sd_fs.s)}; std::unique_ptr sd_ifs = std::make_unique(sd_fs); - out.SetValue(std::make_shared(std::make_shared(std::move(sd_ifs), AtmosphereHblWebContentDir), false), target_object_id); + out.SetValue(std::make_shared(std::make_shared(std::make_unique(std::move(sd_ifs), AtmosphereHblWebContentDir)), false), target_object_id); return ResultSuccess(); } - Result OpenProgramSpecificWebContentFileSystem(sf::Out> &out, ncm::ProgramId client_program_id, ncm::ProgramId program_id, FsFileSystemType filesystem_type) { + Result OpenProgramSpecificWebContentFileSystem(sf::Out> &out, ncm::ProgramId client_program_id, ncm::ProgramId program_id, FsFileSystemType filesystem_type, Service *fwd, const fssrv::sf::Path *path, bool with_id) { /* Directory must exist. */ { FsDir d; @@ -106,12 +107,35 @@ namespace ams::mitm::fs { char program_web_content_path[fs::EntryNameLengthMax + 1]; FormatAtmosphereSdPath(program_web_content_path, sizeof(program_web_content_path), program_id, ProgramWebContentDir); - /* Set the output directory. */ - out.SetValue(std::make_shared(std::make_shared(std::move(sd_ifs), program_web_content_path), false), target_object_id); + /* Make a new filesystem. */ + { + std::unique_ptr subdir_fs = std::make_unique(std::move(sd_ifs), program_web_content_path); + std::shared_ptr new_fs = nullptr; + + /* Try to open the existing fs. */ + FsFileSystem base_fs; + bool opened_base_fs = false; + if (with_id) { + opened_base_fs = R_SUCCEEDED(fsOpenFileSystemWithIdFwd(fwd, std::addressof(base_fs), static_cast(program_id), filesystem_type, path->str)); + } else { + opened_base_fs = R_SUCCEEDED(fsOpenFileSystemWithPatchFwd(fwd, std::addressof(base_fs), static_cast(program_id), filesystem_type)); + } + + if (opened_base_fs) { + /* Create a layered adapter. */ + new_fs = std::make_shared(std::move(subdir_fs), std::make_unique(base_fs)); + } else { + /* Without an existing FS, just make a read only adapter to the subdirectory. */ + new_fs = std::make_shared(std::move(subdir_fs)); + } + + out.SetValue(std::make_shared(std::move(new_fs), false), target_object_id); + } + return ResultSuccess(); } - Result OpenWebContentFileSystem(sf::Out> &out, ncm::ProgramId client_program_id, ncm::ProgramId program_id, FsFileSystemType filesystem_type) { + Result OpenWebContentFileSystem(sf::Out> &out, ncm::ProgramId client_program_id, ncm::ProgramId program_id, FsFileSystemType filesystem_type, Service *fwd, const fssrv::sf::Path *path, bool with_id, bool try_program_specific) { /* Check first that we're a web applet opening web content. */ R_UNLESS(ncm::IsWebAppletProgramId(client_program_id), sm::mitm::ResultShouldForwardToSession()); R_UNLESS(filesystem_type == FsFileSystemType_ContentManual, sm::mitm::ResultShouldForwardToSession()); @@ -119,18 +143,21 @@ namespace ams::mitm::fs { /* Try to mount the HBL web filesystem. If this succeeds then we're done. */ R_UNLESS(R_FAILED(OpenHblWebContentFileSystem(out, client_program_id, program_id, filesystem_type)), ResultSuccess()); + /* If program specific override shouldn't be attempted, fall back. */ + R_UNLESS(try_program_specific, sm::mitm::ResultShouldForwardToSession()); + /* If we're not opening a HBL filesystem, just try to open a generic one. */ - return OpenProgramSpecificWebContentFileSystem(out, client_program_id, program_id, filesystem_type); + return OpenProgramSpecificWebContentFileSystem(out, client_program_id, program_id, filesystem_type, fwd, path, with_id); } } Result FsMitmService::OpenFileSystemWithPatch(sf::Out> out, ncm::ProgramId program_id, u32 _filesystem_type) { - return OpenWebContentFileSystem(out, this->client_info.program_id, program_id, static_cast(_filesystem_type)); + return OpenWebContentFileSystem(out, this->client_info.program_id, program_id, static_cast(_filesystem_type), this->forward_service.get(), nullptr, false, this->client_info.override_status.IsProgramSpecific()); } Result FsMitmService::OpenFileSystemWithId(sf::Out> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type) { - return OpenWebContentFileSystem(out, this->client_info.program_id, program_id, static_cast(_filesystem_type)); + return OpenWebContentFileSystem(out, this->client_info.program_id, program_id, static_cast(_filesystem_type), this->forward_service.get(), std::addressof(path), true, this->client_info.override_status.IsProgramSpecific()); } Result FsMitmService::OpenSdCardFileSystem(sf::Out> out) { diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c index 2a89fb939..38324f069 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c @@ -63,3 +63,29 @@ Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpac .out_objects = &out->s, ); } + +Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType) { + const struct { + u32 fsType; + u64 id; + } in = { fsType, id }; + + return serviceDispatchIn(s, 7, in, + .out_num_objects = 1, + .out_objects = &out->s + ); +} + +Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType, const char* contentPath) { + const struct { + u32 fsType; + u64 id; + } in = { fsType, id }; + + return serviceDispatchIn(s, 8, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { contentPath, FS_MAX_PATH } }, + .out_num_objects = 1, + .out_objects = &out->s + ); +} \ No newline at end of file diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h index a39278a28..5af0f4c09 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h @@ -19,6 +19,9 @@ Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, Ncm Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr); +Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType); +Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType, const char* contentPath); + #ifdef __cplusplus } diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp new file mode 100644 index 000000000..de07e1358 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp @@ -0,0 +1,95 @@ +/* + * 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 + +namespace ams::mitm::fs { + + class ReadOnlyLayeredFileSystem : public ams::fs::fsa::IFileSystem { + private: + ams::fs::ReadOnlyFileSystemAdapter fs_1; + ams::fs::ReadOnlyFileSystemAdapter fs_2; + public: + explicit ReadOnlyLayeredFileSystem(std::unique_ptr a, std::unique_ptr b) : fs_1(std::move(a)), fs_2(std::move(b)) { /* ... */ } + + virtual ~ReadOnlyLayeredFileSystem() { /* ... */ } + private: + virtual Result CreateFileImpl(const char *path, s64 size, int flags) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result DeleteFileImpl(const char *path) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result CreateDirectoryImpl(const char *path) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result DeleteDirectoryImpl(const char *path) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result DeleteDirectoryRecursivelyImpl(const char *path) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result RenameFileImpl(const char *old_path, const char *new_path) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) override final { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result GetEntryTypeImpl(ams::fs::DirectoryEntryType *out, const char *path) override final { + R_UNLESS(R_FAILED(this->fs_1.GetEntryType(out, path)), ResultSuccess()); + return this->fs_2.GetEntryType(out, path); + } + + virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, ams::fs::OpenMode mode) override final { + R_UNLESS(R_FAILED(this->fs_1.OpenFile(out_file, path, mode)), ResultSuccess()); + return this->fs_2.OpenFile(out_file, path, mode); + } + + virtual Result OpenDirectoryImpl(std::unique_ptr *out_dir, const char *path, ams::fs::OpenDirectoryMode mode) override final { + R_UNLESS(R_FAILED(this->fs_1.OpenDirectory(out_dir, path, mode)), ResultSuccess()); + return this->fs_2.OpenDirectory(out_dir, path, mode); + } + + virtual Result CommitImpl() override final { + return ResultSuccess(); + } + + virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result GetTotalSpaceSizeImpl(s64 *out, const char *path) { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result CleanDirectoryRecursivelyImpl(const char *path) { + return ams::fs::ResultUnsupportedOperation(); + } + + virtual Result GetFileTimeStampRawImpl(ams::fs::FileTimeStampRaw *out, const char *path) { + R_UNLESS(R_FAILED(this->fs_1.GetFileTimeStampRaw(out, path)), ResultSuccess()); + return this->fs_2.GetFileTimeStampRaw(out, path); + } + }; + +}