From 3d518759da190a7e27d5808f1685e7163e843a93 Mon Sep 17 00:00:00 2001 From: Adubbz Date: Fri, 27 Mar 2020 21:40:52 +1100 Subject: [PATCH] fssystem: Implement PartitionFileSystemCore (#856) * fssystem: implement PartitionFileSystemMetaCore * fssystem: PartitionFileSystemMetaCore cleanup * fs: add IFile::DryWrite, update results * fssystem: implement PartitionFileSystemCore * fssystem: cleanup PartitionFileSystemCore * fssystem: implement Sha256PartitionFileSystem Co-authored-by: Michael Scire --- .../include/stratosphere/fs/fsa/fs_ifile.hpp | 23 +- .../include/stratosphere/fssystem.hpp | 2 + .../fssystem_partition_file_system.hpp | 72 +++ .../fssystem_partition_file_system_meta.hpp | 113 +++++ .../fssystem_partition_file_system.cpp | 450 ++++++++++++++++++ .../fssystem_partition_file_system_meta.cpp | 219 +++++++++ libraries/libvapours/include/vapours.hpp | 1 + .../libvapours/include/vapours/allocator.hpp | 58 +++ .../include/vapours/results/fs_results.hpp | 13 + 9 files changed, 949 insertions(+), 2 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp create mode 100644 libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp create mode 100644 libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp create mode 100644 libraries/libvapours/include/vapours/allocator.hpp diff --git a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp index 5466dfe80..422240110 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp @@ -85,7 +85,7 @@ namespace ams::fs::fsa { 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()); + R_UNLESS((open_mode & OpenMode_Read) != 0, fs::ResultReadNotPermitted()); /* Get the file size, and validate our offset. */ s64 file_size = 0; @@ -98,12 +98,31 @@ namespace ams::fs::fsa { Result DrySetSize(s64 size, fs::OpenMode open_mode) { /* Check that we can write. */ - R_UNLESS((open_mode & OpenMode_Write) != 0, fs::ResultInvalidOperationForOpenMode()); + R_UNLESS((open_mode & OpenMode_Write) != 0, fs::ResultWriteNotPermitted()); AMS_ASSERT(size >= 0); return ResultSuccess(); } + + Result DryWrite(bool *out_append, s64 offset, size_t size, const fs::WriteOption &option, fs::OpenMode open_mode) { + /* Check that we can write. */ + R_UNLESS((open_mode & OpenMode_Write) != 0, fs::ResultWriteNotPermitted()); + + /* Get the file size. */ + s64 file_size = 0; + R_TRY(this->GetSize(&file_size)); + + /* Determine if we need to append. */ + if (file_size < offset + static_cast(size)) { + R_UNLESS((open_mode & OpenMode_AllowAppend) != 0, fs::ResultFileExtensionWithoutOpenModeAllowAppend()); + *out_append = true; + } else { + *out_append = false; + } + + 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/libraries/libstratosphere/include/stratosphere/fssystem.hpp b/libraries/libstratosphere/include/stratosphere/fssystem.hpp index de899e024..3eabf62c8 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem.hpp @@ -17,6 +17,8 @@ #pragma once #include "fssystem/fssystem_utility.hpp" #include "fssystem/fssystem_external_code.hpp" +#include "fssystem/fssystem_partition_file_system.hpp" +#include "fssystem/fssystem_partition_file_system_meta.hpp" #include "fssystem/fssystem_path_tool.hpp" #include "fssystem/fssystem_subdirectory_filesystem.hpp" #include "fssystem/fssystem_directory_redirection_filesystem.hpp" diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp new file mode 100644 index 000000000..1230665c3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2020 Adubbz, 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 "fssystem_partition_file_system_meta.hpp" +#include "../fs/fsa/fs_ifile.hpp" +#include "../fs/fsa/fs_idirectory.hpp" +#include "../fs/fsa/fs_ifilesystem.hpp" + +namespace ams::fssystem { + + template + class PartitionFileSystemCore : public fs::impl::Newable, public fs::fsa::IFileSystem { + NON_COPYABLE(PartitionFileSystemCore); + NON_MOVEABLE(PartitionFileSystemCore); + private: + class PartitionFile; + class PartitionDirectory; + private: + fs::IStorage *base_storage; + MetaType *meta_data; + bool initialized; + size_t meta_data_size; + std::unique_ptr unique_meta_data; + std::shared_ptr shared_storage; + private: + Result Initialize(fs::IStorage *base_storage, MemoryResource *allocator); + public: + PartitionFileSystemCore(); + virtual ~PartitionFileSystemCore() override; + + Result Initialize(std::unique_ptr &&meta_data, std::shared_ptr base_storage); + Result Initialize(MetaType *meta_data, std::shared_ptr base_storage); + Result Initialize(fs::IStorage *base_storage); + Result Initialize(std::shared_ptr base_storage); + Result Initialize(std::shared_ptr base_storage, MemoryResource *allocator); + + Result GetFileBaseOffset(s64 *out_offset, const char *path); + + virtual Result CreateFileImpl(const char *path, s64 size, int option) override; + virtual Result DeleteFileImpl(const char *path) override; + virtual Result CreateDirectoryImpl(const char *path) override; + virtual Result DeleteDirectoryImpl(const char *path) override; + virtual Result DeleteDirectoryRecursivelyImpl(const char *path) override; + virtual Result RenameFileImpl(const char *old_path, const char *new_path) override; + virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) override; + virtual Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) override; + virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, fs::OpenMode mode) override; + virtual Result OpenDirectoryImpl(std::unique_ptr *out_dir, const char *path, fs::OpenDirectoryMode mode) override; + virtual Result CommitImpl() override; + virtual Result CleanDirectoryRecursivelyImpl(const char *path) override; + + /* These aren't accessible as commands. */ + virtual Result CommitProvisionallyImpl(s64 counter) override; + }; + + using PartitionFileSystem = PartitionFileSystemCore; + using Sha256PartitionFileSystem = PartitionFileSystemCore; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp new file mode 100644 index 000000000..5e34906d6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2020 Adubbz, 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 + +namespace ams::fssystem { + + namespace impl { + + struct PartitionFileSystemFormat { + #pragma pack(push, 1) + struct PartitionEntry { + u64 offset; + u64 size; + u32 name_offset; + u32 reserved; + }; + static_assert(std::is_pod::value); + #pragma pack(pop) + + static constexpr const char VersionSignature[] = { 'P', 'F', 'S', '0' }; + + static constexpr size_t EntryNameLengthMax = ::ams::fs::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = 0x20; + + using ResultSignatureVerificationFailed = fs::ResultPartitionSignatureVerificationFailed; + }; + + struct Sha256PartitionFileSystemFormat { + static constexpr size_t HashSize = ::ams::crypto::Sha256Generator::HashSize; + + #pragma pack(push, 1) + struct PartitionEntry { + u64 offset; + u64 size; + u32 name_offset; + u32 hash_target_size; + u64 hash_target_offset; + char hash[HashSize]; + }; + static_assert(std::is_pod::value); + #pragma pack(pop) + + static constexpr const char VersionSignature[] = { 'H', 'F', 'S', '0' }; + + static constexpr size_t EntryNameLengthMax = ::ams::fs::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = 0x200; + + using ResultSignatureVerificationFailed = fs::ResultSha256PartitionSignatureVerificationFailed; + }; + + } + + template + class PartitionFileSystemMetaCore : public fs::impl::Newable { + public: + static constexpr size_t EntryNameLengthMax = Format::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = Format::FileDataAlignmentSize; + + /* Forward declare header. */ + struct PartitionFileSystemHeader; + + using PartitionEntry = typename Format::PartitionEntry; + protected: + bool initialized; + PartitionFileSystemHeader *header; + PartitionEntry *entries; + char *name_table; + size_t meta_data_size; + MemoryResource *allocator; + char *buffer; + public: + PartitionFileSystemMetaCore() : initialized(false), allocator(nullptr), buffer(nullptr) { /* ... */ } + ~PartitionFileSystemMetaCore(); + + Result Initialize(fs::IStorage *storage, MemoryResource *allocator); + Result Initialize(fs::IStorage *storage, void *header, size_t header_size); + + const PartitionEntry *GetEntry(s32 index) const; + s32 GetEntryCount() const; + s32 GetEntryIndex(const char *name) const; + const char *GetEntryName(s32 index) const; + size_t GetHeaderSize() const; + size_t GetMetaDataSize() const; + public: + static Result QueryMetaDataSize(size_t *out_size, fs::IStorage *storage); + protected: + void DeallocateBuffer(); + }; + + using PartitionFileSystemMeta = PartitionFileSystemMetaCore; + + class Sha256PartitionFileSystemMeta : public PartitionFileSystemMetaCore { + public: + using PartitionFileSystemMetaCore::Initialize; + Result Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, std::optional suffix = std::nullopt); + }; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp new file mode 100644 index 000000000..85632d939 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2018-2020 Adubbz, 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 { + + class PartitionFileSystemDefaultAllocator : public MemoryResource { + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + return ::ams::fs::impl::Allocate(size); + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + ::ams::fs::impl::Deallocate(buffer, size); + } + + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + return this == std::addressof(rhs); + } + }; + + PartitionFileSystemDefaultAllocator g_partition_filesystem_default_allocator; + + } + + template + class PartitionFileSystemCore::PartitionFile : public fs::fsa::IFile, public fs::impl::Newable { + private: + const typename MetaType::PartitionEntry *partition_entry; + const PartitionFileSystemCore *parent; + const fs::OpenMode mode; + public: + PartitionFile(PartitionFileSystemCore *parent, const typename MetaType::PartitionEntry *partition_entry, fs::OpenMode mode) : partition_entry(partition_entry), parent(parent), mode(mode) { /* ... */ } + private: + virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final; + + virtual Result GetSizeImpl(s64 *out) override final { + *out = this->partition_entry->size; + return ResultSuccess(); + } + + virtual Result FlushImpl() override final { + /* Nothing to do if writing disallowed. */ + R_SUCCEED_IF((this->mode & fs::OpenMode_Write) == 0); + + /* Flush base storage. */ + return this->parent->base_storage->Flush(); + } + + virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + /* Ensure appending is not required. */ + bool needs_append; + R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, this->mode)); + R_UNLESS(!needs_append, fs::ResultUnsupportedOperationInPartitionFileA()); + + /* Appending is prohibited. */ + AMS_ASSERT((this->mode & fs::OpenMode_AllowAppend) == 0); + + /* Validate offset and size. */ + R_UNLESS(offset <= static_cast(this->partition_entry->size), fs::ResultOutOfRange()); + R_UNLESS(static_cast(offset + size) <= static_cast(this->partition_entry->size), fs::ResultInvalidSize()); + + /* Write to the base storage. */ + return this->parent->base_storage->Write(this->parent->meta_data_size + this->partition_entry->offset + offset, buffer, size); + } + + virtual Result SetSizeImpl(s64 size) override final { + R_TRY(this->DrySetSize(size, this->mode)); + return fs::ResultUnsupportedOperationInPartitionFileA(); + } + + 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 { + /* Validate preconditions for operation. */ + switch (op_id) { + case fs::OperationId::InvalidateCache: + R_UNLESS((this->mode & fs::OpenMode_Read) != 0, fs::ResultReadNotPermitted()); + R_UNLESS((this->mode & fs::OpenMode_Write) == 0, fs::ResultUnsupportedOperationInPartitionFileB()); + break; + case fs::OperationId::QueryRange: + break; + default: + return fs::ResultUnsupportedOperationInPartitionFileB(); + } + + /* Validate offset and size. */ + R_UNLESS(offset >= 0, fs::ResultOutOfRange()); + R_UNLESS(offset <= static_cast(this->partition_entry->size), fs::ResultOutOfRange()); + R_UNLESS(static_cast(offset + size) <= static_cast(this->partition_entry->size), fs::ResultInvalidSize()); + R_UNLESS(static_cast(offset + size) >= offset, fs::ResultInvalidSize()); + + return this->parent->base_storage->OperateRange(dst, dst_size, op_id, this->parent->meta_data_size + this->partition_entry->offset + offset, size, src, src_size); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + /* TODO: How should this be handled? */ + return sf::cmif::InvalidDomainObjectId; + } + }; + + template<> + Result PartitionFileSystemCore::PartitionFile::ReadImpl(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) { + /* Perform a dry read. */ + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, this->mode)); + + /* Read from the base storage. */ + R_TRY(this->parent->base_storage->Read(this->parent->meta_data_size + this->partition_entry->offset + offset, dst, read_size)); + + /* Set output size. */ + *out = read_size; + return ResultSuccess(); + } + + template<> + Result PartitionFileSystemCore::PartitionFile::ReadImpl(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) { + /* Perform a dry read. */ + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, this->mode)); + + const s64 entry_start = this->parent->meta_data_size + this->partition_entry->offset; + const s64 read_end = static_cast(offset + read_size); + const s64 hash_start = static_cast(this->partition_entry->hash_target_offset); + const s64 hash_end = hash_start + this->partition_entry->hash_target_size; + + if (read_end <= hash_start || hash_end <= offset) { + /* We aren't reading hashed data, so we can just read from the base storage. */ + R_TRY(this->parent->base_storage->Read(entry_start + offset, dst, read_size)); + } else { + /* Only hash target offset == 0 is supported. */ + R_UNLESS(hash_start == 0, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Ensure that the hash region is valid. */ + R_UNLESS(this->partition_entry->hash_target_offset + this->partition_entry->hash_target_size <= this->partition_entry->size, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Validate our read offset. */ + const s64 read_offset = entry_start + offset; + R_UNLESS(read_offset >= offset, fs::ResultOutOfRange()); + + /* Prepare a buffer for our calculated hash. */ + char hash[crypto::Sha256Generator::HashSize]; + crypto::Sha256Generator generator; + + /* Ensure we can perform our read. */ + const bool hash_in_read = offset <= hash_start && hash_end <= read_end; + const bool read_in_hash = hash_start <= offset && read_end <= hash_end; + R_UNLESS(hash_in_read || read_in_hash, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Initialize the generator. */ + generator.Initialize(); + + if (hash_in_read) { + /* Easy case: hash region is contained within the bounds. */ + R_TRY(this->parent->base_storage->Read(entry_start + offset, dst, read_size)); + generator.Update(static_cast(dst) + hash_start - offset, this->partition_entry->hash_target_size); + } else /* if (read_in_hash) */ { + /* We're reading a portion of what's hashed. */ + s64 remaining_hash_size = this->partition_entry->hash_target_size; + s64 hash_offset = entry_start + hash_start; + s64 remaining_size = read_size; + s64 copy_offset = 0; + while (remaining_hash_size > 0) { + /* Read some portion of data into the buffer. */ + constexpr size_t HashBufferSize = 0x200; + char hash_buffer[HashBufferSize]; + size_t cur_size = static_cast(std::min(static_cast(HashBufferSize), remaining_hash_size)); + R_TRY(this->parent->base_storage->Read(hash_offset, hash_buffer, cur_size)); + + /* Update the hash. */ + generator.Update(hash_buffer, cur_size); + + /* If we need to copy, do so. */ + if (read_offset <= (hash_offset + static_cast(cur_size)) && remaining_size > 0) { + const s64 hash_buffer_offset = std::max(read_offset - hash_offset, 0); + const size_t copy_size = static_cast(std::min(cur_size - hash_buffer_offset, remaining_size)); + std::memcpy(static_cast(dst) + copy_offset, hash_buffer + hash_buffer_offset, copy_size); + remaining_size -= copy_size; + copy_offset += copy_size; + } + + /* Update offsets. */ + remaining_hash_size -= cur_size; + hash_offset += cur_size; + } + } + + /* Get the hash. */ + generator.GetHash(hash, sizeof(hash)); + + /* Validate the hash. */ + auto hash_guard = SCOPE_GUARD { std::memset(dst, 0, read_size); }; + R_UNLESS(crypto::IsSameBytes(this->partition_entry->hash, hash, sizeof(hash)), fs::ResultSha256PartitionHashVerificationFailed()); + + /* We successfully completed our read. */ + hash_guard.Cancel(); + } + + /* Set output size. */ + *out = read_size; + return ResultSuccess(); + } + + template + class PartitionFileSystemCore::PartitionDirectory : public fs::fsa::IDirectory, public fs::impl::Newable { + private: + u32 cur_index; + const PartitionFileSystemCore *parent; + const fs::OpenDirectoryMode mode; + public: + PartitionDirectory(PartitionFileSystemCore *parent, fs::OpenDirectoryMode mode) : cur_index(0), parent(parent), mode(mode) { /* ... */ } + public: + virtual Result ReadImpl(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override final { + /* There are no subdirectories. */ + if ((this->mode & fs::OpenDirectoryMode_File) == 0) { + *out_count = 0; + return ResultSuccess(); + } + + /* Calculate number of entries. */ + const s64 entry_count = std::min(max_entries, static_cast(this->parent->meta_data->GetEntryCount() - this->cur_index)); + + /* Populate output directory entries. */ + for (auto i = 0; i < entry_count; i++, this->cur_index++) { + fs::DirectoryEntry &dir_entry = out_entries[i]; + + /* Setup the output directory entry. */ + dir_entry.type = fs::DirectoryEntryType_File; + dir_entry.file_size = this->parent->meta_data->GetEntry(this->cur_index)->size; + std::strncpy(dir_entry.name, this->parent->meta_data->GetEntryName(this->cur_index), sizeof(dir_entry.name) - 1); + dir_entry.name[sizeof(dir_entry.name) - 1] = StringTraits::NullTerminator; + } + + *out_count = entry_count; + return ResultSuccess(); + } + + virtual Result GetEntryCountImpl(s64 *out) override final { + /* Output the parent meta data entry count for files, otherwise 0. */ + if (this->mode & fs::OpenDirectoryMode_File) { + *out = this->parent->meta_data->GetEntryCount(); + } else { + *out = 0; + } + + return ResultSuccess(); + } + + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + /* TODO: How should this be handled? */ + return sf::cmif::InvalidDomainObjectId; + } + }; + + template + PartitionFileSystemCore::PartitionFileSystemCore() : initialized(false) { + /* ... */ + } + + template + PartitionFileSystemCore::~PartitionFileSystemCore() { + /* ... */ + } + + template + Result PartitionFileSystemCore::Initialize(fs::IStorage *base_storage, MemoryResource *allocator) { + /* Validate preconditions. */ + R_UNLESS(!this->initialized, fs::ResultPreconditionViolation()); + + /* Allocate meta data. */ + this->unique_meta_data = std::make_unique(); + R_UNLESS(this->unique_meta_data != nullptr, fs::ResultAllocationFailureInPartitionFileSystemA()); + + /* Initialize meta data. */ + R_TRY(this->unique_meta_data->Initialize(base_storage, allocator)); + + /* Initialize members. */ + this->meta_data = this->unique_meta_data.get(); + this->base_storage = base_storage; + this->meta_data_size = this->meta_data->GetMetaDataSize(); + this->initialized = true; + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::Initialize(std::unique_ptr &&meta_data, std::shared_ptr base_storage) { + this->unique_meta_data = std::move(meta_data); + return this->Initialize(this->unique_meta_data.get(), base_storage); + } + + template + Result PartitionFileSystemCore::Initialize(MetaType *meta_data, std::shared_ptr base_storage) { + /* Validate preconditions. */ + R_UNLESS(!this->initialized, fs::ResultPreconditionViolation()); + + /* Initialize members. */ + this->shared_storage = std::move(base_storage); + this->base_storage = this->shared_storage.get(); + this->meta_data = meta_data; + this->meta_data_size = this->meta_data->GetMetaDataSize(); + this->initialized = true; + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::Initialize(fs::IStorage *base_storage) { + return this->Initialize(base_storage, std::addressof(g_partition_filesystem_default_allocator)); + } + + template + Result PartitionFileSystemCore::Initialize(std::shared_ptr base_storage) { + this->shared_storage = std::move(base_storage); + return this->Initialize(this->shared_storage.get()); + } + + template + Result PartitionFileSystemCore::Initialize(std::shared_ptr base_storage, MemoryResource *allocator) { + this->shared_storage = std::move(base_storage); + return this->Initialize(this->shared_storage.get(), allocator); + } + + template + Result PartitionFileSystemCore::GetFileBaseOffset(s64 *out_offset, const char *path) { + /* Validate preconditions. */ + R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); + + /* Obtain and validate the entry index. */ + const s32 entry_index = this->meta_data->GetEntryIndex(path + 1); + R_UNLESS(entry_index >= 0, fs::ResultPathNotFound()); + + /* Output offset. */ + *out_offset = this->meta_data_size + this->meta_data->GetEntry(entry_index)->offset; + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) { + /* Validate preconditions. */ + R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); + R_UNLESS(PathTool::IsSeparator(path[0]), fs::ResultInvalidPathFormat()); + + /* Check if the path is for a directory. */ + if (std::strncmp(path, PathTool::RootPath, sizeof(PathTool::RootPath)) == 0) { + *out = fs::DirectoryEntryType_Directory; + return ResultSuccess(); + } + + /* Ensure that path is for a file. */ + R_UNLESS(this->meta_data->GetEntryIndex(path + 1) >= 0, fs::ResultPathNotFound()); + + *out = fs::DirectoryEntryType_File; + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::OpenFileImpl(std::unique_ptr *out_file, const char *path, fs::OpenMode mode) { + /* Validate preconditions. */ + R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); + + /* Obtain and validate the entry index. */ + const s32 entry_index = this->meta_data->GetEntryIndex(path + 1); + R_UNLESS(entry_index >= 0, fs::ResultPathNotFound()); + + /* Create and output the file directory. */ + std::unique_ptr file = std::make_unique(this, this->meta_data->GetEntry(entry_index), mode); + R_UNLESS(file != nullptr, fs::ResultAllocationFailureInPartitionFileSystemB()); + *out_file = std::move(file); + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::OpenDirectoryImpl(std::unique_ptr *out_dir, const char *path, fs::OpenDirectoryMode mode) { + /* Validate preconditions. */ + R_UNLESS(this->initialized, fs::ResultPreconditionViolation()); + R_UNLESS(std::strncmp(path, PathTool::RootPath, sizeof(PathTool::RootPath)) == 0, fs::ResultPathNotFound()); + + /* Create and output the partition directory. */ + std::unique_ptr directory = std::make_unique(this, mode); + R_UNLESS(directory != nullptr, fs::ResultAllocationFailureInPartitionFileSystemC()); + *out_dir = std::move(directory); + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::CommitImpl() { + return ResultSuccess(); + } + + template + Result PartitionFileSystemCore::CleanDirectoryRecursivelyImpl(const char *path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::CreateDirectoryImpl(const char *path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::CreateFileImpl(const char *path, s64 size, int option) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::DeleteDirectoryImpl(const char *path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::DeleteDirectoryRecursivelyImpl(const char *path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::DeleteFileImpl(const char *path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::RenameDirectoryImpl(const char *old_path, const char *new_path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::RenameFileImpl(const char *old_path, const char *new_path) { + return fs::ResultUnsupportedOperationInPartitionFileSystemA(); + } + + template + Result PartitionFileSystemCore::CommitProvisionallyImpl(s64 counter) { + return fs::ResultUnsupportedOperationInPartitionFileSystemB(); + } + + template class PartitionFileSystemCore; + template class PartitionFileSystemCore; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp new file mode 100644 index 000000000..dc598e492 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018-2020 Adubbz, 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 { + + template + struct PartitionFileSystemMetaCore::PartitionFileSystemHeader { + char signature[sizeof(Format::VersionSignature)]; + s32 entry_count; + u32 name_table_size; + u32 reserved; + }; + static_assert(std::is_pod::value); + static_assert(sizeof(PartitionFileSystemMeta::PartitionFileSystemHeader) == 0x10); + + template + PartitionFileSystemMetaCore::~PartitionFileSystemMetaCore() { + this->DeallocateBuffer(); + } + + template + Result PartitionFileSystemMetaCore::Initialize(fs::IStorage *storage, MemoryResource *allocator) { + /* Validate preconditions. */ + AMS_ASSERT(allocator != nullptr); + + /* Determine the meta data size. */ + R_TRY(this->QueryMetaDataSize(std::addressof(this->meta_data_size), storage)); + + /* Deallocate any old meta buffer and allocate a new one. */ + this->DeallocateBuffer(); + this->allocator = allocator; + this->buffer = static_cast(this->allocator->Allocate(this->meta_data_size)); + R_UNLESS(this->buffer != nullptr, fs::ResultAllocationFailureInPartitionFileSystemMetaA()); + + /* Perform regular initialization. */ + return this->Initialize(storage, this->buffer, this->meta_data_size); + } + + template + Result PartitionFileSystemMetaCore::Initialize(fs::IStorage *storage, void *meta, size_t meta_size) { + /* Validate size for header. */ + R_UNLESS(meta_size >= sizeof(PartitionFileSystemHeader), fs::ResultInvalidSize()); + + /* Read the header. */ + R_TRY(storage->Read(0, meta, sizeof(PartitionFileSystemHeader))); + + /* Set and validate the header. */ + this->header = reinterpret_cast(meta); + R_UNLESS(crypto::IsSameBytes(this->header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Setup entries and name table. */ + const size_t entries_size = this->header->entry_count * sizeof(typename Format::PartitionEntry); + this->entries = reinterpret_cast(static_cast(meta) + sizeof(PartitionFileSystemHeader)); + this->name_table = static_cast(meta) + sizeof(PartitionFileSystemHeader) + entries_size; + + /* Validate size for header + entries + name table. */ + R_UNLESS(meta_size >= sizeof(PartitionFileSystemHeader) + entries_size + this->header->name_table_size, fs::ResultInvalidSize()); + + /* Read entries and name table. */ + R_TRY(storage->Read(sizeof(PartitionFileSystemHeader), this->entries, entries_size + this->header->name_table_size)); + + /* Mark as initialized. */ + this->initialized = true; + return ResultSuccess(); + } + + template + void PartitionFileSystemMetaCore::DeallocateBuffer() { + if (this->buffer != nullptr) { + AMS_ABORT_UNLESS(this->allocator != nullptr); + this->allocator->Deallocate(this->buffer, this->meta_data_size); + this->buffer = nullptr; + } + } + + template + const typename Format::PartitionEntry *PartitionFileSystemMetaCore::GetEntry(s32 index) const { + if (this->initialized && 0 <= index && index < static_cast(this->header->entry_count)) { + return std::addressof(this->entries[index]); + } + return nullptr; + } + + template + s32 PartitionFileSystemMetaCore::GetEntryCount() const { + if (this->initialized) { + return this->header->entry_count; + } + return 0; + } + + template + s32 PartitionFileSystemMetaCore::GetEntryIndex(const char *name) const { + /* Fail if not initialized. */ + if (!this->initialized) { + return 0; + } + + for (s32 i = 0; i < static_cast(this->header->entry_count); i++) { + const auto &entry = this->entries[i]; + + /* Name offset is invalid. */ + if (entry.name_offset >= this->header->name_table_size) { + return 0; + } + + /* Compare to input name. */ + const s32 max_name_len = this->header->name_table_size - entry.name_offset; + if (std::strncmp(std::addressof(this->name_table[entry.name_offset]), name, max_name_len) == 0) { + return i; + } + } + + /* Not found. */ + return -1; + } + + template + const char *PartitionFileSystemMetaCore::GetEntryName(s32 index) const { + if (this->initialized && index < static_cast(this->header->entry_count)) { + return std::addressof(this->name_table[this->GetEntry(index)->name_offset]); + } + return nullptr; + } + + template + size_t PartitionFileSystemMetaCore::GetHeaderSize() const { + return sizeof(PartitionFileSystemHeader); + } + + template + size_t PartitionFileSystemMetaCore::GetMetaDataSize() const { + return this->meta_data_size; + } + + template + Result PartitionFileSystemMetaCore::QueryMetaDataSize(size_t *out_size, fs::IStorage *storage) { + /* Read and validate the header. */ + PartitionFileSystemHeader header; + R_TRY(storage->Read(0, std::addressof(header), sizeof(PartitionFileSystemHeader))); + R_UNLESS(crypto::IsSameBytes(std::addressof(header), Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Output size. */ + *out_size = sizeof(PartitionFileSystemHeader) + header.entry_count * sizeof(typename Format::PartitionEntry) + header.name_table_size; + return ResultSuccess(); + } + + template class PartitionFileSystemMetaCore; + template class PartitionFileSystemMetaCore; + + Result Sha256PartitionFileSystemMeta::Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, std::optional suffix) { + /* Ensure preconditions. */ + R_UNLESS(hash_size == crypto::Sha256Generator::HashSize, fs::ResultPreconditionViolation()); + + /* Get metadata size. */ + R_TRY(QueryMetaDataSize(std::addressof(this->meta_data_size), base_storage)); + + /* Ensure we have no buffer. */ + this->DeallocateBuffer(); + + /* Set allocator and allocate buffer. */ + this->allocator = allocator; + this->buffer = static_cast(this->allocator->Allocate(this->meta_data_size)); + R_UNLESS(this->buffer != nullptr, fs::ResultAllocationFailureInPartitionFileSystemMetaB()); + + /* Read metadata. */ + R_TRY(base_storage->Read(0, this->buffer, this->meta_data_size)); + + /* Calculate hash. */ + char calc_hash[crypto::Sha256Generator::HashSize]; + { + crypto::Sha256Generator generator; + generator.Initialize(); + generator.Update(this->buffer, this->meta_data_size); + if (suffix) { + u8 suffix_val = *suffix; + generator.Update(std::addressof(suffix_val), 1); + } + generator.GetHash(calc_hash, sizeof(calc_hash)); + } + + /* Ensure hash is valid. */ + R_UNLESS(crypto::IsSameBytes(hash, calc_hash, sizeof(calc_hash)), fs::ResultSha256PartitionHashVerificationFailed()); + + /* Give access to Format */ + using Format = impl::Sha256PartitionFileSystemFormat; + + /* Set header. */ + this->header = reinterpret_cast(this->buffer); + R_UNLESS(crypto::IsSameBytes(this->header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Validate size for entries and name table. */ + const size_t entries_size = this->header->entry_count * sizeof(typename Format::PartitionEntry); + R_UNLESS(this->meta_data_size >= sizeof(PartitionFileSystemHeader) + entries_size + this->header->name_table_size, fs::ResultInvalidSha256PartitionMetaDataSize()); + + /* Set entries and name table. */ + this->entries = reinterpret_cast(this->buffer + sizeof(PartitionFileSystemHeader)); + this->name_table = this->buffer + sizeof(PartitionFileSystemHeader) + entries_size; + + /* We initialized. */ + this->initialized = true; + return ResultSuccess(); + } + +} diff --git a/libraries/libvapours/include/vapours.hpp b/libraries/libvapours/include/vapours.hpp index d51797e3a..9da48965b 100644 --- a/libraries/libvapours/include/vapours.hpp +++ b/libraries/libvapours/include/vapours.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include diff --git a/libraries/libvapours/include/vapours/allocator.hpp b/libraries/libvapours/include/vapours/allocator.hpp new file mode 100644 index 000000000..446706b56 --- /dev/null +++ b/libraries/libvapours/include/vapours/allocator.hpp @@ -0,0 +1,58 @@ +/* + * 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 + +namespace ams { + + constexpr inline size_t DefaultAlignment = alignof(max_align_t); + + using AllocateFunction = void *(*)(size_t); + using AllocateFunctionWithUserData = void *(*)(size_t, void *); + using AlignedAllocateFunction = void *(*)(size_t, size_t); + using AlignedAllocateFunctionWithUserData = void *(*)(size_t, size_t, void *); + using DeallocateFunction = void (*)(void *, size_t); + using FreeFunction = void (*)(void *); + using FreeFunctionWithUserData = void (*)(void *, void *); + + class MemoryResource { + public: + ALWAYS_INLINE void *allocate(size_t size, size_t alignment = DefaultAlignment) { + return this->AllocateImpl(size, alignment); + } + ALWAYS_INLINE void deallocate(void *buffer, size_t size, size_t alignment = DefaultAlignment) { + this->DeallocateImpl(buffer, size, alignment); + } + ALWAYS_INLINE bool is_equal(const MemoryResource &resource) const { + return this->IsEqualImpl(resource); + } + ALWAYS_INLINE void *Allocate(size_t size, size_t alignment = DefaultAlignment) { + return this->AllocateImpl(size, alignment); + } + ALWAYS_INLINE void Deallocate(void *buffer, size_t size, size_t alignment = DefaultAlignment) { + this->DeallocateImpl(buffer, size, alignment); + } + ALWAYS_INLINE bool IsEqual(const MemoryResource &resource) const { + return this->IsEqualImpl(resource); + } + protected: + virtual void *AllocateImpl(size_t size, size_t alignment) = 0; + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) = 0; + virtual bool IsEqualImpl(const MemoryResource &resource) const = 0; + }; + +} diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 2f5d74fd6..acc581d62 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -74,7 +74,14 @@ 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(AllocationFailureInPartitionFileSystemCreatorA, 3280); R_DEFINE_ERROR_RESULT(AllocationFailureInDirectorySaveDataFileSystem, 3321); + + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemA, 3347); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemB, 3348); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemC, 3349); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemMetaA, 3350); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemMetaB, 3351); R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemD, 3352); R_DEFINE_ERROR_RESULT(AllocationFailureInSubDirectoryFileSystem, 3355); R_DEFINE_ERROR_RESULT(AllocationFailureInRegisterA, 3365); @@ -238,6 +245,8 @@ namespace ams::fs { R_DEFINE_ERROR_RANGE(InvalidOperationForOpenMode, 6200, 6299); R_DEFINE_ERROR_RESULT(FileExtensionWithoutOpenModeAllowAppend, 6201); + R_DEFINE_ERROR_RESULT(ReadNotPermitted, 6202); + R_DEFINE_ERROR_RESULT(WriteNotPermitted, 6203); R_DEFINE_ERROR_RANGE(UnsupportedOperation, 6300, 6399); R_DEFINE_ERROR_RESULT(UnsupportedOperationInSubStorageA, 6302); @@ -256,6 +265,10 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateC, 6371); R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileA, 6372); R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileB, 6373); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileSystemA, 6374); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileSystemB, 6375); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileA, 6376); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileB, 6377); R_DEFINE_ERROR_RANGE(PermissionDenied, 6400, 6449);