From 8d1ada2a1b8bb4c3b397f7cb076c22b52af039be Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Sat, 4 Apr 2020 02:37:21 -0700 Subject: [PATCH] fssystem: add RomFsFileSystem --- .../fs/fs_dbm_rom_key_value_storage.hpp | 3 +- .../fs/fs_read_only_filesystem.hpp | 2 +- .../stratosphere/fs/fsa/fs_ifilesystem.hpp | 8 +- .../include/stratosphere/fssystem.hpp | 18 +- .../buffers/fssystem_buffer_manager_utils.hpp | 90 +++ ...system_dbm_hierarchical_rom_file_table.hpp | 684 ++++++++++++++++++ .../fssystem_dbm_rom_key_value_storage.hpp | 367 ++++++++++ .../fssystem/fssystem_dbm_rom_path_tool.hpp | 114 +++ .../fssystem/fssystem_dbm_rom_types.hpp | 59 ++ .../fssystem/fssystem_romfs_file_system.hpp | 71 ++ .../libstratosphere/source/fs/fs_code.cpp | 4 +- .../source/fs/fs_romfs_filesystem.cpp | 12 +- .../buffers/fssystem_buffer_manager_utils.cpp | 52 ++ .../fssystem/fssystem_dbm_rom_path_tool.cpp | 189 +++++ .../fssystem/fssystem_romfs_filesystem.cpp | 396 ++++++++++ .../libvapours/include/vapours/defines.hpp | 2 + .../include/vapours/results/fs_results.hpp | 7 +- 17 files changed, 2057 insertions(+), 21 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_hierarchical_rom_file_table.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_key_value_storage.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_path_tool.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_types.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp create mode 100644 libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp create mode 100644 libraries/libstratosphere/source/fssystem/fssystem_dbm_rom_path_tool.cpp create mode 100644 libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_dbm_rom_key_value_storage.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_dbm_rom_key_value_storage.hpp index e52b260e5..044e17b78 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_dbm_rom_key_value_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_dbm_rom_key_value_storage.hpp @@ -115,12 +115,11 @@ namespace ams::fs { return this->GetImpl(std::addressof(pos), out, key, hash_key, aux, aux_size); } - Result FindOpen(FindIndex *out) const { + void FindOpen(FindIndex *out) const { AMS_ASSERT(out != nullptr); out->ind = static_cast(-1); out->pos = InvalidPosition; - return ResultSuccess(); } Result FindNext(Key *out_key, Value *out_val, FindIndex *find) { diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp index d2a73ff58..0cd726b50 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp @@ -82,7 +82,7 @@ namespace ams::fs { private: virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, OpenMode mode) override final { /* Only allow opening files with mode = read. */ - R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidArgument()); + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); std::unique_ptr base_file; R_TRY(this->base_fs->OpenFile(std::addressof(base_file), path, mode)); diff --git a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp index f785b18fe..f6c91d557 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp @@ -83,16 +83,16 @@ namespace ams::fs::fsa { Result OpenFile(std::unique_ptr *out_file, const char *path, OpenMode mode) { R_UNLESS(path != nullptr, fs::ResultInvalidPath()); R_UNLESS(out_file != nullptr, fs::ResultNullptrArgument()); - R_UNLESS((mode & OpenMode_ReadWrite) != 0, fs::ResultInvalidArgument()); - R_UNLESS((mode & ~OpenMode_All) == 0, fs::ResultInvalidArgument()); + R_UNLESS((mode & OpenMode_ReadWrite) != 0, fs::ResultInvalidOpenMode()); + R_UNLESS((mode & ~OpenMode_All) == 0, fs::ResultInvalidOpenMode()); return this->OpenFileImpl(out_file, path, mode); } Result OpenDirectory(std::unique_ptr *out_dir, const char *path, OpenDirectoryMode mode) { R_UNLESS(path != nullptr, fs::ResultInvalidPath()); R_UNLESS(out_dir != nullptr, fs::ResultNullptrArgument()); - R_UNLESS((mode & OpenDirectoryMode_All) != 0, fs::ResultInvalidArgument()); - R_UNLESS((mode & ~(OpenDirectoryMode_All | OpenDirectoryMode_NotRequireFileSize)) == 0, fs::ResultInvalidArgument()); + R_UNLESS((mode & OpenDirectoryMode_All) != 0, fs::ResultInvalidOpenMode()); + R_UNLESS((mode & ~(OpenDirectoryMode_All | OpenDirectoryMode_NotRequireFileSize)) == 0, fs::ResultInvalidOpenMode()); return this->OpenDirectoryImpl(out_dir, path, mode); } diff --git a/libraries/libstratosphere/include/stratosphere/fssystem.hpp b/libraries/libstratosphere/include/stratosphere/fssystem.hpp index 3eabf62c8..09097e8c5 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem.hpp @@ -15,11 +15,13 @@ */ #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" -#include "fssystem/fssystem_directory_savedata_filesystem.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp new file mode 100644 index 000000000..a2a69ac6c --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp @@ -0,0 +1,90 @@ +/* + * 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::fssystem::buffers { + + namespace impl { + + constexpr inline auto RetryWait = TimeSpan::FromMilliSeconds(10); + + } + + template + Result DoContinuouslyUntilBufferIsAllocated(F f, OnFailure on_failure, const char *function_name) { + constexpr auto BufferAllocationRetryLogCountMax = 10; + constexpr auto BufferAllocationRetryLogInterval = 100; + for (auto count = 1; true; count++) { + R_TRY_CATCH(f()) { + R_CATCH(fs::ResultBufferAllocationFailed) { + if ((1 <= count && count <= BufferAllocationRetryLogCountMax) || ((count % BufferAllocationRetryLogInterval) == 0)) { + /* TODO: Log */ + } + R_TRY(on_failure()); + + /* TODO: os::SleepThread */ + svc::SleepThread(impl::RetryWait.GetNanoSeconds()); + + continue; + } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + } + + template + Result DoContinuouslyUntilBufferIsAllocated(F f, const char *function_name) { + R_TRY(DoContinuouslyUntilBufferIsAllocated(f, []() ALWAYS_INLINE_LAMBDA { return ResultSuccess(); }, function_name)); + return ResultSuccess(); + } + + class BufferManagerContext { + private: + bool needs_blocking; + public: + constexpr BufferManagerContext() : needs_blocking(false) { /* ... */ } + public: + bool IsNeedBlocking() const { return this->needs_blocking; } + + void SetNeedBlocking(bool need) { this->needs_blocking = need; } + }; + + void RegisterBufferManagerContext(const BufferManagerContext *context); + BufferManagerContext *GetBufferManagerContext(); + void EnableBlockingBufferManagerAllocation(); + + class ScopedBufferManagerContextRegistration { + private: + BufferManagerContext cur_context; + const BufferManagerContext *old_context; + public: + ALWAYS_INLINE explicit ScopedBufferManagerContextRegistration() { + this->old_context = GetBufferManagerContext(); + if (this->old_context != nullptr) { + this->cur_context = *this->old_context; + } + RegisterBufferManagerContext(std::addressof(this->cur_context)); + } + + ALWAYS_INLINE ~ScopedBufferManagerContextRegistration() { + RegisterBufferManagerContext(this->old_context); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_hierarchical_rom_file_table.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_hierarchical_rom_file_table.hpp new file mode 100644 index 000000000..98d27b059 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_hierarchical_rom_file_table.hpp @@ -0,0 +1,684 @@ +/* + * 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::fssystem { + + template + class HierarchicalRomFileTable { + private: + using DirectoryBucketStorage = DBS; + using DirectoryEntryStorage = DES; + using FileBucketStorage = FBS; + using FileEntryStorage = FES; + public: + using Position = u32; + + struct FindPosition { + Position next_dir; + Position next_file; + }; + static_assert(std::is_pod::value); + + using DirectoryInfo = RomDirectoryInfo; + using FileInfo = RomFileInfo; + + static constexpr RomFileId ConvertToFileId(Position pos) { + return static_cast(pos); + } + private: + static constexpr inline Position InvalidPosition = ~Position(); + static constexpr inline Position RootPosition = 0; + static constexpr inline size_t ReservedDirectoryCount = 1; + + static constexpr RomDirectoryId ConvertToDirectoryId(Position pos) { + return static_cast(pos); + } + + static constexpr Position ConvertToPosition(RomDirectoryId id) { + return static_cast(id); + } + + static_assert(std::is_same::value); + + struct RomDirectoryEntry { + Position next; + Position dir; + Position file; + }; + static_assert(std::is_pod::value); + + struct RomFileEntry { + Position next; + FileInfo info; + }; + static_assert(std::is_pod::value); + + static constexpr inline u32 MaxKeyLength = RomPathTool::MaxPathLength; + + template + class EntryMapTable : public RomKeyValueStorage { + private: + using BucketStorage = BucketStorageType; + using EntryStorage = EntryStorageType; + public: + using ImplKey = ImplKeyType; + using ClientKey = ClientKeyType; + using Value = ValueType; + using Position = HierarchicalRomFileTable::Position; + using Base = RomKeyValueStorage; + public: + Result Add(Position *out, const ClientKeyType &key, const Value &value) { + return Base::AddImpl(out, key.key, key.Hash(), key.name.path, key.name.length * sizeof(RomPathChar), value); + } + + Result Get(Position *out_pos, Value *out_val, const ClientKeyType &key) const { + return Base::GetImpl(out_pos, out_val, key.key, key.Hash(), key.name.path, key.name.length * sizeof(RomPathChar)); + } + + Result GetByPosition(ImplKey *out_key, Value *out_val, void *out_aux, size_t *out_aux_size, Position pos) const { + return Base::GetByPosition(out_key, out_val, out_aux, out_aux_size, pos); + } + + Result SetByPosition(Position pos, const Value &value, fs::WriteOption option) const { + return Base::SetByPosition(pos, value, option); + } + }; + + struct RomEntryKey { + Position parent; + + bool IsEqual(const RomEntryKey &rhs, const void *aux_lhs, size_t aux_lhs_size, const void *aux_rhs, size_t aux_rhs_size) const { + if (this->parent != rhs.parent) { + return false; + } + if (aux_lhs_size != aux_rhs_size) { + return false; + } + return RomPathTool::IsEqualPath(reinterpret_cast(aux_lhs), reinterpret_cast(aux_rhs), aux_lhs_size / sizeof(RomPathChar)); + } + }; + static_assert(std::is_pod::value); + + struct EntryKey { + RomEntryKey key; + RomPathTool::RomEntryName name; + + constexpr u32 Hash() const { + u32 hash = this->key.parent ^ 123456789; + const RomPathChar *name = this->name.path; + const RomPathChar *end = name + this->name.length; + while (name < end) { + const u32 cur = static_cast(static_cast::type>(*(name++))); + hash = ((hash >> 5) | (hash << 27)) ^ cur; + } + return hash; + } + }; + static_assert(std::is_pod::value); + + using DirectoryEntryMapTable = EntryMapTable; + using FileEntryMapTable = EntryMapTable; + private: + DirectoryEntryMapTable dir_table; + FileEntryMapTable file_table; + public: + static u32 QueryDirectoryEntrySize(u32 name_len) { + AMS_ABORT_UNLESS(name_len <= RomPathTool::MaxPathLength); + return DirectoryEntryMapTable::QueryEntrySize(name_len * sizeof(RomPathChar)); + } + + static u32 QueryFileEntrySize(u32 name_len) { + AMS_ABORT_UNLESS(name_len <= RomPathTool::MaxPathLength); + return FileEntryMapTable::QueryEntrySize(name_len * sizeof(RomPathChar)); + } + + static u32 QueryDirectoryEntryBucketStorageSize(u32 count) { return DirectoryEntryMapTable::QueryBucketStorageSize(count); } + static u32 QueryFileEntryBucketStorageSize(u32 count) { return FileEntryMapTable::QueryBucketStorageSize(count); } + + static Result Format(DirectoryBucketStorage *dir_bucket, s64 dir_bucket_ofs, u32 dir_bucket_size, DirectoryEntryStorage *dir_entry, s64 dir_entry_ofs, u32 dir_entry_size, FileBucketStorage *file_bucket, s64 file_bucket_ofs, u32 file_bucket_size, FileEntryStorage *file_entry, s64 file_entry_ofs, u32 file_entry_size) { + R_TRY(DirectoryEntryMapTable::Format(dir_bucket, dir_bucket_ofs, DirectoryEntryMapTable::QueryBucketCount(dir_bucket_size), dir_entry, dir_entry_ofs, dir_entry_size)); + R_TRY(FileEntryMapTable::Format(file_bucket, file_bucket_ofs, FileEntryMapTable::QueryBucketCount(file_bucket_size), file_entry, file_entry_ofs, file_entry_size)); + return ResultSuccess(); + } + public: + HierarchicalRomFileTable() { /* ... */ } + + constexpr u32 GetDirectoryEntryCount() const { + return this->dir_table.GetEntryCount(); + } + + constexpr u32 GetFileEntryCount() const { + return this->file_table.GetEntryCount(); + } + + Result Initialize(DirectoryBucketStorage *dir_bucket, s64 dir_bucket_ofs, u32 dir_bucket_size, DirectoryEntryStorage *dir_entry, s64 dir_entry_ofs, u32 dir_entry_size, FileBucketStorage *file_bucket, s64 file_bucket_ofs, u32 file_bucket_size, FileEntryStorage *file_entry, s64 file_entry_ofs, u32 file_entry_size) { + AMS_ASSERT(dir_bucket != nullptr); + AMS_ASSERT(dir_entry != nullptr); + AMS_ASSERT(file_bucket != nullptr); + AMS_ASSERT(file_entry != nullptr); + + R_TRY(this->dir_table.Initialize(dir_bucket, dir_bucket_ofs, DirectoryEntryMapTable::QueryBucketCount(dir_bucket_size), dir_entry, dir_entry_ofs, dir_entry_size)); + R_TRY(this->file_table.Initialize(file_bucket, file_bucket_ofs, FileEntryMapTable::QueryBucketCount(file_bucket_size), file_entry, file_entry_ofs, file_entry_size)); + + return ResultSuccess(); + } + + void Finalize() { + this->dir_table.Finalize(); + this->file_table.Finalize(); + } + + Result CreateRootDirectory() { + Position root_pos = RootPosition; + EntryKey root_key = {}; + root_key.key.parent = root_pos; + RomPathTool::InitializeRomEntryName(std::addressof(root_key.name)); + RomDirectoryEntry root_entry = { + .next = InvalidPosition, + .dir = InvalidPosition, + .file = InvalidPosition, + }; + return this->dir_table.Add(std::addressof(root_pos), root_key, root_entry); + } + + Result CreateDirectory(RomDirectoryId *out, const RomPathChar *path, const DirectoryInfo &info) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey new_key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(new_key), path)); + + R_TRY(this->CheckSameEntryExists(new_key, fs::ResultDbmAlreadyExists())); + + RomDirectoryEntry new_entry = { + .next = InvalidPosition, + .dir = InvalidPosition, + .file = InvalidPosition, + }; + + Position new_pos = 0; + R_TRY_CATCH(this->dir_table.Add(std::addressof(new_pos), new_key, new_entry)) { + R_CONVERT(fs::ResultDbmKeyFull, fs::ResultDbmDirectoryEntryFull()) + } R_END_TRY_CATCH; + + *out = ConvertToDirectoryId(new_pos); + + if (parent_entry.dir == InvalidPosition) { + parent_entry.dir = new_pos; + + R_TRY(this->dir_table.SetByPosition(new_key.key.parent, parent_entry, fs::WriteOption::None)); + } else { + Position cur_pos = parent_entry.dir; + while (true) { + RomEntryKey cur_key = {}; + RomDirectoryEntry cur_entry = {}; + R_TRY(this->dir_table.GetByPosition(std::addressof(cur_key), std::addressof(cur_entry), nullptr, nullptr, cur_pos)); + + if (cur_entry.next == InvalidPosition) { + cur_entry.next = new_pos; + + R_TRY(this->dir_table.SetByPosition(cur_pos, cur_entry, fs::WriteOption::None)); + break; + } + + cur_pos = cur_entry.next; + } + } + + return ResultSuccess(); + } + + Result CreateFile(RomFileId *out, const RomPathChar *path, const FileInfo &info) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey new_key = {}; + R_TRY(this->FindFileRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(new_key), path)); + + R_TRY(this->CheckSameEntryExists(new_key, fs::ResultDbmAlreadyExists())); + + RomFileEntry new_entry = { + .next = InvalidPosition, + .info = info, + }; + + Position new_pos = 0; + R_TRY_CATCH(this->file_table.Add(std::addressof(new_pos), new_key, new_entry)) { + R_CONVERT(fs::ResultDbmKeyFull, fs::ResultDbmFileEntryFull()) + } R_END_TRY_CATCH; + + *out = ConvertToFileId(new_pos); + + if (parent_entry.file == InvalidPosition) { + parent_entry.file = new_pos; + + R_TRY(this->dir_table.SetByPosition(new_key.key.parent, parent_entry, fs::WriteOption::None)); + } else { + Position cur_pos = parent_entry.file; + while (true) { + RomEntryKey cur_key = {}; + RomFileEntry cur_entry = {}; + R_TRY(this->file_table.GetByPosition(std::addressof(cur_key), std::addressof(cur_entry), nullptr, nullptr, cur_pos)); + + if (cur_entry.next == InvalidPosition) { + cur_entry.next = new_pos; + + R_TRY(this->file_table.SetByPosition(cur_pos, cur_entry, fs::WriteOption::None)); + break; + } + + cur_pos = cur_entry.next; + } + } + + return ResultSuccess(); + } + + Result ConvertPathToDirectoryId(RomDirectoryId *out, const RomPathChar *path) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(key), path)); + + Position pos = 0; + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key)); + + *out = ConvertToDirectoryId(pos); + return ResultSuccess(); + } + + Result ConvertPathToFileId(RomFileId *out, const RomPathChar *path) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(key), path)); + + Position pos = 0; + RomFileEntry entry = {}; + R_TRY(this->GetFileEntry(std::addressof(pos), std::addressof(entry), key)); + + *out = ConvertToFileId(pos); + return ResultSuccess(); + } + + Result GetDirectoryInformation(DirectoryInfo *out, const RomPathChar *path) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(key), path)); + + return this->GetDirectoryInformation(out, key); + } + + Result GetDirectoryInformation(DirectoryInfo *out, RomDirectoryId id) const { + AMS_ASSERT(out != nullptr); + + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(entry), id)); + + return ResultSuccess(); + } + + Result OpenFile(FileInfo *out, const RomPathChar *path) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindFileRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(key), path)); + + return this->OpenFile(out, key); + } + + Result OpenFile(FileInfo *out, RomFileId id) const { + AMS_ASSERT(out != nullptr); + + RomFileEntry entry = {}; + R_TRY(this->GetFileEntry(std::addressof(entry), id)); + + *out = entry.info; + return ResultSuccess(); + } + + Result FindOpen(FindPosition *out, const RomPathChar *path) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + EntryKey parent_key = {}; + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(parent_key), std::addressof(parent_entry), std::addressof(key), path)); + + return this->FindOpen(out, key); + } + + Result FindOpen(FindPosition *out, RomDirectoryId id) const { + AMS_ASSERT(out != nullptr); + + out->next_dir = InvalidPosition; + out->next_file = InvalidPosition; + + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(entry), id)); + + out->next_dir = entry.dir; + out->next_file = entry.file; + + return ResultSuccess(); + } + + Result FindNextDirectory(RomPathChar *out, FindPosition *find) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(find != nullptr); + + R_UNLESS(find->next_dir != InvalidPosition, fs::ResultDbmFindFinished()); + + RomEntryKey key = {}; + RomDirectoryEntry entry = {}; + size_t aux_size = 0; + R_TRY(this->dir_table.GetByPosition(std::addressof(key), std::addressof(entry), out, std::addressof(aux_size), find->next_dir)); + + out[aux_size / sizeof(RomPathChar)] = RomStringTraits::NullTerminator; + + find->next_dir = entry.next; + return ResultSuccess(); + } + + Result FindNextFile(RomPathChar *out, FindPosition *find) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(find != nullptr); + + R_UNLESS(find->next_file != InvalidPosition, fs::ResultDbmFindFinished()); + + RomEntryKey key = {}; + RomFileEntry entry = {}; + size_t aux_size = 0; + R_TRY(this->file_table.GetByPosition(std::addressof(key), std::addressof(entry), out, std::addressof(aux_size), find->next_file)); + + out[aux_size / sizeof(RomPathChar)] = RomStringTraits::NullTerminator; + + find->next_file = entry.next; + return ResultSuccess(); + } + + Result QueryRomFileSystemSize(u32 *out_dir_entry_size, u32 *out_file_entry_size) { + AMS_ASSERT(out_dir_entry_size != nullptr); + AMS_ASSERT(out_file_entry_size != nullptr); + + *out_dir_entry_size = this->dir_table.GetTotalEntrySize(); + *out_file_entry_size = this->file_table.GetTotalEntrySize(); + return ResultSuccess(); + } + private: + Result GetGrandParent(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, Position pos, RomPathTool::RomEntryName name, const RomPathChar *path) const { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_dir_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + + RomEntryKey gp_key = {}; + RomDirectoryEntry gp_entry = {}; + R_TRY(this->dir_table.GetByPosition(std::addressof(gp_key), std::addressof(gp_entry), nullptr, nullptr, pos)); + out_dir_key->key.parent = gp_key.parent; + + R_TRY(RomPathTool::GetParentDirectoryName(std::addressof(out_dir_key->name), name, path)); + + R_TRY(this->GetDirectoryEntry(out_pos, out_dir_entry, *out_dir_key)); + + return ResultSuccess(); + } + + Result FindParentDirectoryRecursive(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, RomPathTool::PathParser &parser, const RomPathChar *path) const { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_dir_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + + Position dir_pos = RootPosition; + EntryKey dir_key = {}; + RomDirectoryEntry dir_entry = {}; + dir_key.key.parent = RootPosition; + + R_TRY(parser.GetNextDirectoryName(std::addressof(dir_key.name))); + R_TRY(this->GetDirectoryEntry(std::addressof(dir_pos), std::addressof(dir_entry), dir_key)); + + Position parent_pos = dir_pos; + while (!parser.IsFinished()) { + EntryKey old_key = dir_key; + + R_TRY(parser.GetNextDirectoryName(std::addressof(dir_key.name))); + + if (RomPathTool::IsCurrentDirectory(dir_key.name)) { + dir_key = old_key; + continue; + } else if (RomPathTool::IsParentDirectory(dir_key.name)) { + R_UNLESS(parent_pos != RootPosition, fs::ResultDbmInvalidOperation()); + + R_TRY(this->GetGrandParent(std::addressof(parent_pos), std::addressof(dir_key), std::addressof(dir_entry), dir_key.key.parent, dir_key.name, path)); + } else { + dir_key.key.parent = parent_pos; + R_TRY(this->GetDirectoryEntry(std::addressof(dir_pos), std::addressof(dir_entry), dir_key)); + + parent_pos = dir_pos; + } + } + + *out_pos = parent_pos; + *out_dir_key = dir_key; + *out_dir_entry = dir_entry; + return ResultSuccess(); + } + + Result FindPathRecursive(EntryKey *out_parent_key, RomDirectoryEntry *out_parent_dir_entry, EntryKey *out_key, bool is_dir, const RomPathChar *path) const { + AMS_ASSERT(out_parent_key != nullptr); + AMS_ASSERT(out_parent_dir_entry != nullptr); + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(path != nullptr); + + RomPathTool::PathParser parser; + R_TRY(parser.Initialize(path)); + + Position parent_pos = 0; + R_TRY(this->FindParentDirectoryRecursive(std::addressof(parent_pos), out_parent_key, out_parent_dir_entry, parser, path)); + + if (is_dir) { + RomPathTool::RomEntryName name = {}; + R_TRY(parser.GetAsDirectoryName(std::addressof(name))); + + if (RomPathTool::IsCurrentDirectory(name)) { + *out_key = *out_parent_key; + if (out_key->key.parent != RootPosition) { + Position pos = 0; + R_TRY(this->GetGrandParent(std::addressof(pos), out_parent_key, out_parent_dir_entry, out_key->key.parent, out_key->name, path)); + } + } else if (RomPathTool::IsParentDirectory(name)) { + R_UNLESS(parent_pos != RootPosition, fs::ResultDbmInvalidOperation()); + + Position pos = 0; + RomDirectoryEntry cur_entry = {}; + R_TRY(this->GetGrandParent(std::addressof(pos), out_key, std::addressof(cur_entry), out_parent_key->key.parent, out_parent_key->name, path)); + + if (out_key->key.parent != RootPosition) { + R_TRY(this->GetGrandParent(std::addressof(pos), out_parent_key, out_parent_dir_entry, out_key->key.parent, out_key->name, path)); + } + } else { + out_key->name = name; + out_key->key.parent = (out_key->name.length > 0) ? parent_pos : RootPosition; + } + } else { + R_UNLESS(!parser.IsDirectoryPath(), fs::ResultDbmInvalidOperation()); + + out_key->key.parent = parent_pos; + R_TRY(parser.GetAsFileName(std::addressof(out_key->name))); + } + + return ResultSuccess(); + } + + Result FindDirectoryRecursive(EntryKey *out_parent_key, RomDirectoryEntry *out_parent_dir_entry, EntryKey *out_key, const RomPathChar *path) const { + return this->FindPathRecursive(out_parent_key, out_parent_dir_entry, out_key, true, path); + } + + Result FindFileRecursive(EntryKey *out_parent_key, RomDirectoryEntry *out_parent_dir_entry, EntryKey *out_key, const RomPathChar *path) const { + return this->FindPathRecursive(out_parent_key, out_parent_dir_entry, out_key, false, path); + } + + Result CheckSameEntryExists(const EntryKey &key, Result if_exists) const { + /* Check dir */ + { + Position pos = InvalidPosition; + RomDirectoryEntry entry = {}; + const Result get_res = this->dir_table.Get(std::addressof(pos), std::addressof(entry), key); + if (!fs::ResultDbmKeyNotFound::Includes(get_res)) { + R_TRY(get_res); + return if_exists; + } + } + + /* Check file */ + { + Position pos = InvalidPosition; + RomFileEntry entry = {}; + const Result get_res = this->file_table.Get(std::addressof(pos), std::addressof(entry), key); + if (!fs::ResultDbmKeyNotFound::Includes(get_res)) { + R_TRY(get_res); + return if_exists; + } + } + return ResultSuccess(); + } + + Result GetDirectoryEntry(Position *out_pos, RomDirectoryEntry *out_entry, const EntryKey &key) const { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_entry != nullptr); + + const Result dir_res = this->dir_table.Get(out_pos, out_entry, key); + R_UNLESS(R_FAILED(dir_res), dir_res); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), dir_res); + + Position pos = 0; + RomFileEntry entry = {}; + const Result file_res = this->file_table.Get(std::addressof(pos), std::addressof(entry), key); + R_UNLESS(R_FAILED(file_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), fs::ResultDbmDirectoryNotFound()); + return file_res; + } + + Result GetDirectoryEntry(RomDirectoryEntry *out_entry, RomDirectoryId id) const { + AMS_ASSERT(out_entry != nullptr); + Position pos = ConvertToPosition(id); + + RomEntryKey key = {}; + const Result dir_res = this->dir_table.GetByPosition(std::addressof(key), out_entry, nullptr, nullptr, pos); + R_UNLESS(R_FAILED(dir_res), dir_res); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), dir_res); + + RomFileEntry entry = {}; + const Result file_res = this->file_table.GetByPosition(std::addressof(key), std::addressof(entry), nullptr, nullptr, pos); + R_UNLESS(R_FAILED(file_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), fs::ResultDbmDirectoryNotFound()); + return file_res; + } + + Result GetFileEntry(Position *out_pos, RomFileEntry *out_entry, const EntryKey &key) const { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_entry != nullptr); + + const Result file_res = this->file_table.Get(out_pos, out_entry, key); + R_UNLESS(R_FAILED(file_res), file_res); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), file_res); + + Position pos = 0; + RomDirectoryEntry entry = {}; + const Result dir_res = this->dir_table.Get(std::addressof(pos), std::addressof(entry), key); + R_UNLESS(R_FAILED(dir_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), fs::ResultDbmFileNotFound()); + return dir_res; + } + + Result GetFileEntry(RomFileEntry *out_entry, RomFileId id) const { + AMS_ASSERT(out_entry != nullptr); + Position pos = ConvertToPosition(id); + + RomEntryKey key = {}; + const Result file_res = this->file_table.GetByPosition(std::addressof(key), out_entry, nullptr, nullptr, pos); + R_UNLESS(R_FAILED(file_res), file_res); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), file_res); + + RomDirectoryEntry entry = {}; + const Result dir_res = this->dir_table.GetByPosition(std::addressof(key), std::addressof(entry), nullptr, nullptr, pos); + R_UNLESS(R_FAILED(dir_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), fs::ResultDbmFileNotFound()); + return dir_res; + } + + Result GetDirectoryInformation(DirectoryInfo *out, const EntryKey &key) const { + AMS_ASSERT(out != nullptr); + + Position pos = 0; + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key)); + + return ResultSuccess(); + } + + Result OpenFile(FileInfo *out, const EntryKey &key) const { + AMS_ASSERT(out != nullptr); + + Position pos = 0; + RomFileEntry entry = {}; + R_TRY(this->GetFileEntry(std::addressof(pos), std::addressof(entry), key)); + + *out = entry.info; + return ResultSuccess(); + } + + Result FindOpen(FindPosition *out, const EntryKey &key) const { + AMS_ASSERT(out != nullptr); + + out->next_dir = InvalidPosition; + out->next_file = InvalidPosition; + + Position pos = 0; + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key)); + + out->next_dir = entry.dir; + out->next_file = entry.file; + + return ResultSuccess(); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_key_value_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_key_value_storage.hpp new file mode 100644 index 000000000..f47569f68 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_key_value_storage.hpp @@ -0,0 +1,367 @@ +/* + * 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::fssystem { + + constexpr ALWAYS_INLINE u32 AlignRomAddress(u32 addr) { + return util::AlignUp(addr, sizeof(addr)); + } + + template + class RomKeyValueStorage { + public: + using BucketStorage = BucketStorageType; + using EntryStorage = EntryStorageType; + using Key = KeyType; + using Value = ValueType; + using Position = u32; + using BucketIndex = u32; + + struct FindIndex { + BucketIndex ind; + Position pos; + }; + static_assert(std::is_pod::value); + private: + static constexpr inline Position InvalidPosition = ~Position(); + + struct Element { + Key key; + Value value; + Position next; + u32 size; + }; + static_assert(std::is_pod::value); + private: + s64 bucket_offset; + u32 bucket_count; + BucketStorage *bucket_storage; + s64 kv_offset; + u32 kv_size; + EntryStorage *kv_storage; + u32 total_entry_size; + u32 entry_count; + public: + static constexpr u32 QueryEntrySize(u32 aux_size) { + return AlignRomAddress(sizeof(Element) + aux_size); + } + + static constexpr u32 QueryBucketStorageSize(u32 num) { + return num * sizeof(Position); + } + + static constexpr u32 QueryBucketCount(u32 size) { + return size / sizeof(Position); + } + + static constexpr u32 QueryKeyValueStorageSize(u32 num) { + return num * sizeof(Element); + } + + static Result Format(BucketStorage *bucket, s64 bucket_ofs, u32 bucket_count, EntryStorage *kv, s64 kv_ofs, u32 kv_size) { + AMS_ASSERT(bucket != nullptr); + AMS_ASSERT(kv != nullptr); + AMS_ASSERT(kv_size >= 0); + + const Position pos = InvalidPosition; + for (s64 i = 0; i < bucket_count; i++) { + R_TRY(bucket->Write(bucket_ofs + i * sizeof(pos), std::addressof(pos), sizeof(pos))); + } + return ResultSuccess(); + } + public: + RomKeyValueStorage() : bucket_offset(), bucket_count(), bucket_storage(), kv_offset(), kv_size(), kv_storage(), total_entry_size(), entry_count() { /* ... */ } + + Result Initialize(BucketStorage *bucket, s64 bucket_ofs, u32 bucket_count, EntryStorage *kv, s64 kv_ofs, u32 kv_size) { + AMS_ASSERT(bucket != nullptr); + AMS_ASSERT(kv != nullptr); + AMS_ASSERT(bucket_count > 0); + + this->bucket_storage = bucket; + this->bucket_offset = bucket_ofs; + this->bucket_count = bucket_count; + + this->kv_storage = kv; + this->kv_offset = kv_ofs; + this->kv_size = kv_size; + + return ResultSuccess(); + } + + void Finalize() { + this->bucket_storage = nullptr; + this->bucket_offset = 0; + this->bucket_count = 0; + + this->kv_storage = nullptr; + this->kv_offset = 0; + this->kv_size = 0; + } + + constexpr u32 GetTotalEntrySize() const { + return this->total_entry_size; + } + + constexpr u32 GetFreeSize() const { + return (this->kv_size - this->total_entry_size); + } + + constexpr u32 GetEntryCount() const { + return this->entry_count; + } + + Result Add(const Key &key, u32 hash_key, const void *aux, size_t aux_size, const Value &value) { + AMS_ASSERT(aux != nullptr); + AMS_ASSERT(aux_size <= MaxAuxiliarySize); + Position pos; + return this->AddImpl(std::addressof(pos), key, hash_key, aux, aux_size, value); + } + + Result Get(Value *out, const Key &key, u32 hash_key, const void *aux, size_t aux_size) { + AMS_ASSERT(aux != nullptr); + AMS_ASSERT(aux_size <= MaxAuxiliarySize); + Position pos; + return this->GetImpl(std::addressof(pos), out, key, hash_key, aux, aux_size); + } + + void FindOpen(FindIndex *out) const { + AMS_ASSERT(out != nullptr); + + out->ind = static_cast(-1); + out->pos = InvalidPosition; + } + + Result FindNext(Key *out_key, Value *out_val, FindIndex *find) { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_val != nullptr); + AMS_ASSERT(find != nullptr); + + Element elem; + + BucketIndex ind = find->ind; + R_UNLESS((ind < this->bucket_count) || ind == static_cast(-1), fs::ResultDbmFindKeyFinished()); + + while (true) { + if (find->pos != InvalidPosition) { + R_TRY(this->ReadKeyValue(std::addressof(elem), nullptr, nullptr, find->pos)); + + AMS_ASSERT(elem.next == InvalidPosition || elem.next < this->kv_size); + find->pos = elem.next; + *out_key = elem.key; + *out_val = elem.val; + return ResultSuccess(); + } + + while (true) { + ind++; + if (ind == this->bucket_count) { + find->ind = ind; + find->pos = InvalidPosition; + return fs::ResultDbmFindKeyFinished(); + } + + Position pos; + R_TRY(this->ReadBucket(std::addressof(pos), ind)); + AMS_ASSERT(pos == InvalidPosition || pos < this->kv_size); + + if (pos != InvalidPosition) { + find->ind = ind; + find->pos = pos; + break; + } + } + } + } + protected: + Result AddImpl(Position *out, const Key &key, u32 hash_key, const void *aux, size_t aux_size, const Value &value) { + Position pos, prev_pos; + Element elem; + + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->bucket_count > 0); + AMS_ASSERT(this->kv_size >= 0); + + const Result find_res = this->FindImpl(std::addressof(pos), std::addressof(prev_pos), std::addressof(elem), key, hash_key, aux, aux_size); + R_UNLESS(R_FAILED(find_res), fs::ResultDbmAlreadyExists()); + R_UNLESS(fs::ResultDbmKeyNotFound::Includes(find_res), find_res); + + R_TRY(this->AllocateEntry(std::addressof(pos), aux_size)); + + Position next_pos; + R_TRY(this->LinkEntry(std::addressof(next_pos), pos, hash_key)); + + elem = { key, value, next_pos, static_cast(aux_size) }; + *out = pos; + R_TRY(this->WriteKeyValue(std::addressof(elem), pos, aux, aux_size, fs::WriteOption::None)); + + this->entry_count++; + + return ResultSuccess(); + } + + Result GetImpl(Position *out_pos, Value *out_val, const Key &key, u32 hash_key, const void *aux, size_t aux_size) const { + Position pos, prev_pos; + Element elem; + + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_val != nullptr); + + R_TRY(this->FindImpl(std::addressof(pos), std::addressof(prev_pos), std::addressof(elem), key, hash_key, aux, aux_size)); + + *out_pos = pos; + *out_val = elem.value; + return ResultSuccess(); + } + + Result GetByPosition(Key *out_key, Value *out_val, void *out_aux, size_t *out_aux_size, Position pos) const { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_val != nullptr); + + Element elem; + R_TRY(this->ReadKeyValue(std::addressof(elem), out_aux, out_aux_size, pos)); + + *out_key = elem.key; + *out_val = elem.value; + return ResultSuccess(); + } + + Result SetByPosition(Position pos, const Value &value, fs::WriteOption option) const { + Element elem; + R_TRY(this->ReadKeyValue(std::addressof(elem), nullptr, nullptr, pos)); + elem.value = value; + return this->WriteKeyValue(std::addressof(elem), pos, nullptr, 0, option); + } + private: + BucketIndex HashToBucket(u32 hash_key) const { + return hash_key % this->bucket_count; + } + + Result FindImpl(Position *out_pos, Position *out_prev, Element *out_elem, const Key &key, u32 hash_key, const void *aux, size_t aux_size) const { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_prev != nullptr); + AMS_ASSERT(out_elem != nullptr); + AMS_ASSERT(this->bucket_count > 0); + AMS_ASSERT(this->kv_size >= 0); + + *out_pos = 0; + *out_prev = 0; + + const BucketIndex ind = HashToBucket(hash_key); + + Position cur; + R_TRY(this->ReadBucket(std::addressof(cur), ind)); + AMS_ASSERT(cur == InvalidPosition || cur < this->kv_size); + + R_UNLESS(cur != InvalidPosition, fs::ResultDbmKeyNotFound()); + + u8 buf[MaxAuxiliarySize]; + + while (true) { + size_t cur_aux_size; + R_TRY(this->ReadKeyValue(out_elem, buf, std::addressof(cur_aux_size), cur)); + + if (key.IsEqual(out_elem->key, aux, aux_size, buf, cur_aux_size)) { + *out_pos = cur; + return ResultSuccess(); + } + + *out_prev = cur; + cur = out_elem->next; + R_UNLESS(cur != InvalidPosition, fs::ResultDbmKeyNotFound()); + } + } + + Result AllocateEntry(Position *out, size_t aux_size) { + AMS_ASSERT(out != nullptr); + + R_UNLESS(this->total_entry_size + sizeof(Element) + aux_size <= this->kv_size, fs::ResultDbmKeyFull()); + + *out = static_cast(this->total_entry_size); + + this->total_entry_size = AlignRomAddress(this->total_entry_size + sizeof(Element) + static_cast(aux_size)); + return ResultSuccess(); + } + + Result LinkEntry(Position *out, Position pos, u32 hash_key) { + AMS_ASSERT(out != nullptr); + + const BucketIndex ind = HashToBucket(hash_key); + + Position next; + R_TRY(this->ReadBucket(std::addressof(next), ind)); + AMS_ASSERT(next == InvalidPosition || next < this->kv_size); + + R_TRY(this->WriteBucket(pos, ind, fs::WriteOption::None)); + + *out = next; + return ResultSuccess(); + } + + Result ReadBucket(Position *out, BucketIndex ind) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->bucket_storage != nullptr); + AMS_ASSERT(ind < this->bucket_count); + + const s64 offset = this->bucket_offset + ind * sizeof(Position); + return this->bucket_storage->Read(offset, out, sizeof(*out)); + } + + Result WriteBucket(Position pos, BucketIndex ind, fs::WriteOption option) const { + AMS_ASSERT(this->bucket_storage != nullptr); + AMS_ASSERT(ind < this->bucket_count); + + const s64 offset = this->bucket_offset + ind * sizeof(Position); + return this->bucket_storage.Write(offset, std::addressof(pos), sizeof(pos)); + } + + Result ReadKeyValue(Element *out, void *out_aux, size_t *out_aux_size, Position pos) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->kv_storage != nullptr); + AMS_ASSERT(pos < this->kv_size); + + const s64 offset = this->kv_offset + pos; + R_TRY(this->kv_storage->Read(offset, out, sizeof(*out))); + + if (out_aux != nullptr && out_aux_size != nullptr) { + *out_aux_size = out->size; + if (out->size > 0) { + R_TRY(this->kv_storage->Read(offset + sizeof(*out), out_aux, out->size)); + } + } + + return ResultSuccess(); + } + + Result WriteKeyValue(const Element *elem, Position pos, const void *aux, size_t aux_size, fs::WriteOption option) const { + AMS_ASSERT(elem != nullptr); + AMS_ASSERT(this->kv_storage != nullptr); + AMS_ASSERT(pos < this->kv_size); + + const s64 offset = this->kv_offset + pos; + R_TRY(this->kv_storage->Write(offset, elem, sizeof(*elem))); + + if (aux != nullptr && aux_size > 0) { + R_TRY(this->kv_storage->Write(offset + sizeof(*elem), aux, aux_size)); + } + + return ResultSuccess(); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_path_tool.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_path_tool.hpp new file mode 100644 index 000000000..d6cd961ac --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_path_tool.hpp @@ -0,0 +1,114 @@ +/* + * 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::fssystem { + + namespace RomPathTool { + + constexpr inline u32 MaxPathLength = 0x300; + + struct RomEntryName { + size_t length; + const RomPathChar *path; + }; + static_assert(std::is_pod::value); + + constexpr void InitializeRomEntryName(RomEntryName *entry) { + entry->length = 0; + } + + constexpr inline bool IsSeparator(RomPathChar c) { + return c == RomStringTraits::DirectorySeparator; + } + + constexpr inline bool IsNullTerminator(RomPathChar c) { + return c == RomStringTraits::NullTerminator; + } + + constexpr inline bool IsDot(RomPathChar c) { + return c == RomStringTraits::Dot; + } + + constexpr inline bool IsCurrentDirectory(const RomEntryName &name) { + return name.length == 1 && IsDot(name.path[0]); + } + + constexpr inline bool IsCurrentDirectory(const RomPathChar *p, size_t length) { + return length == 1 && IsDot(p[0]); + } + + constexpr inline bool IsCurrentDirectory(const RomPathChar *p) { + return IsDot(p[0]) && IsNullTerminator(p[1]); + } + + constexpr inline bool IsParentDirectory(const RomEntryName &name) { + return name.length == 2 && IsDot(name.path[0]) && IsDot(name.path[1]); + } + + constexpr inline bool IsParentDirectory(const RomPathChar *p) { + return IsDot(p[0]) && IsDot(p[1]) && IsNullTerminator(p[2]); + } + + constexpr inline bool IsParentDirectory(const RomPathChar *p, size_t length) { + return length == 2 && IsDot(p[0]) && IsDot(p[1]); + } + + constexpr inline bool IsEqualPath(const RomPathChar *lhs, const RomPathChar *rhs, size_t length) { + return std::strncmp(lhs, rhs, length) == 0; + } + + constexpr inline bool IsEqualName(const RomEntryName &lhs, const RomPathChar *rhs) { + if (strnlen(rhs, MaxPathLength) != lhs.length) { + return false; + } + return IsEqualPath(lhs.path, rhs, lhs.length); + } + + constexpr inline bool IsEqualName(const RomEntryName &lhs, const RomEntryName &rhs) { + if (lhs.length != rhs.length) { + return false; + } + return IsEqualPath(lhs.path, rhs.path, lhs.length); + } + + Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p); + + class PathParser { + private: + const RomPathChar *prev_path_start; + const RomPathChar *prev_path_end; + const RomPathChar *next_path; + bool finished; + public: + constexpr PathParser() : prev_path_start(), prev_path_end(), next_path(), finished() { /* ... */ } + + Result Initialize(const RomPathChar *path); + void Finalize(); + + bool IsFinished() const; + bool IsDirectoryPath() const; + + Result GetAsDirectoryName(RomEntryName *out) const; + Result GetAsFileName(RomEntryName *out) const; + + Result GetNextDirectoryName(RomEntryName *out); + }; + + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_types.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_types.hpp new file mode 100644 index 000000000..304cf9dd5 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_dbm_rom_types.hpp @@ -0,0 +1,59 @@ +/* + * 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::fssystem { + + using RomPathChar = char; + using RomFileId = s32; + using RomDirectoryId = s32; + + struct RomFileSystemInformation { + s64 size; + s64 directory_bucket_offset; + s64 directory_bucket_size; + s64 directory_entry_offset; + s64 directory_entry_size; + s64 file_bucket_offset; + s64 file_bucket_size; + s64 file_entry_offset; + s64 file_entry_size; + s64 body_offset; + }; + static_assert(std::is_pod::value); + static_assert(sizeof(RomFileSystemInformation) == 0x50); + + struct RomDirectoryInfo { + /* ... */ + }; + static_assert(std::is_pod::value); + + struct RomFileInfo { + fs::Int64 offset; + fs::Int64 size; + }; + static_assert(std::is_pod::value); + + namespace RomStringTraits { + + constexpr inline char DirectorySeparator = '/'; + constexpr inline char NullTerminator = '\x00'; + constexpr inline char Dot = '.'; + + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp new file mode 100644 index 000000000..34dd76003 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp @@ -0,0 +1,71 @@ +/* + * 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::fssystem { + + class RomFsFileSystem : public fs::fsa::IFileSystem, public fs::impl::Newable { + NON_COPYABLE(RomFsFileSystem); + public: + using RomFileTable = HierarchicalRomFileTable; + private: + RomFileTable rom_file_table; + fs::IStorage *base_storage; + std::shared_ptr shared_storage; + std::unique_ptr dir_bucket_storage; + std::unique_ptr dir_entry_storage; + std::unique_ptr file_bucket_storage; + std::unique_ptr file_entry_storage; + s64 entry_size; + private: + Result GetFileInfo(RomFileTable::FileInfo *out, const char *path); + public: + static Result GetRequiredWorkingMemorySize(size_t *out, fs::IStorage *storage); + public: + RomFsFileSystem(); + virtual ~RomFsFileSystem() override; + + Result Initialize(fs::IStorage *base, void *work, size_t work_size, bool use_cache); + Result Initialize(std::shared_ptr base, void *work, size_t work_size, bool use_cache); + + fs::IStorage *GetBaseStorage(); + RomFileTable *GetRomFileTable(); + Result GetFileBaseOffset(s64 *out, const char *path); + public: + virtual Result CreateFileImpl(const char *path, s64 size, int flags) 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 GetFreeSpaceSizeImpl(s64 *out, const char *path) override; + virtual Result CleanDirectoryRecursivelyImpl(const char *path) override; + + /* These aren't accessible as commands. */ + virtual Result CommitProvisionallyImpl(s64 counter) override; + }; + +} diff --git a/libraries/libstratosphere/source/fs/fs_code.cpp b/libraries/libstratosphere/source/fs/fs_code.cpp index e6d10aef3..fdc2bd126 100644 --- a/libraries/libstratosphere/source/fs/fs_code.cpp +++ b/libraries/libstratosphere/source/fs/fs_code.cpp @@ -202,7 +202,7 @@ namespace ams::fs { virtual Result OpenFileImpl(std::unique_ptr *out_file, const char *path, OpenMode mode) override final { /* Only allow opening files with mode = read. */ - R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidArgument()); + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); /* If we support redirection, we'd like to prefer a file from the sd card. */ if (this->is_redirect) { @@ -252,7 +252,7 @@ namespace ams::fs { R_UNLESS(this->initialized, fs::ResultNotInitialized()); /* Only allow opening files with mode = read. */ - R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidArgument()); + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); /* First, check if there's an external code. */ { diff --git a/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp b/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp index 95b24835a..ca6de5ba8 100644 --- a/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp +++ b/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp @@ -233,6 +233,13 @@ namespace ams::fs { { R_UNLESS(offset >= 0, fs::ResultOutOfRange()); R_UNLESS(this->GetSize() >= 0, fs::ResultOutOfRange()); + + auto operate_size = size; + if (offset + operate_size > this->GetSize() || offset + operate_size < offset) { + operate_size = this->GetSize() - offset; + } + + return this->GetStorage()->OperateRange(dst, dst_size, op_id, this->start + offset, operate_size, src, src_size); } default: return fs::ResultUnsupportedOperationInRomFsFileB(); @@ -481,7 +488,7 @@ namespace ams::fs { AMS_ASSERT(out_file != nullptr); AMS_ASSERT(path != nullptr); - R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidArgument()); + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); RomFileTable::FileInfo file_info; R_TRY(this->GetFileInfo(std::addressof(file_info), path)); @@ -515,7 +522,8 @@ namespace ams::fs { } Result RomFsFileSystem::GetFreeSpaceSizeImpl(s64 *out, const char *path) { - return fs::ResultUnsupportedOperationInRomFsFileSystemC(); + *out = 0; + return ResultSuccess(); } Result RomFsFileSystem::GetTotalSpaceSizeImpl(s64 *out, const char *path) { diff --git a/libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp b/libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp new file mode 100644 index 000000000..a0dee2e9c --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp @@ -0,0 +1,52 @@ +/* + * 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 . + */ +#include + +namespace ams::fssystem::buffers { + + namespace { + + /* TODO: os::SdkThreadLocalStorage g_buffer_manager_context_tls_slot; */ + + class ThreadLocalStorageWrapper { + private: + os::TlsSlot tls_slot; + public: + ThreadLocalStorageWrapper() { R_ABORT_UNLESS(os::AllocateTlsSlot(std::addressof(this->tls_slot), nullptr)); } + ~ThreadLocalStorageWrapper() { os::FreeTlsSlot(this->tls_slot); } + + void SetValue(uintptr_t value) { os::SetTlsValue(this->tls_slot, value); } + uintptr_t GetValue() const { return os::GetTlsValue(this->tls_slot); } + os::TlsSlot GetTlsSlot() const { return this->tls_slot; } + } g_buffer_manager_context_tls_slot; + + } + + void RegisterBufferManagerContext(const BufferManagerContext *context) { + g_buffer_manager_context_tls_slot.SetValue(reinterpret_cast(context)); + } + + BufferManagerContext *GetBufferManagerContext() { + return reinterpret_cast(g_buffer_manager_context_tls_slot.GetValue()); + } + + void EnableBlockingBufferManagerAllocation() { + if (auto context = GetBufferManagerContext(); context != nullptr) { + context->SetNeedBlocking(true); + } + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_dbm_rom_path_tool.cpp b/libraries/libstratosphere/source/fssystem/fssystem_dbm_rom_path_tool.cpp new file mode 100644 index 000000000..6d4e6e1e5 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_dbm_rom_path_tool.cpp @@ -0,0 +1,189 @@ +/* + * 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 . + */ +#include + +namespace ams::fssystem::RomPathTool { + + Result PathParser::Initialize(const RomPathChar *path) { + AMS_ASSERT(path != nullptr); + + /* Require paths start with a separator, and skip repeated separators. */ + R_UNLESS(IsSeparator(path[0]), fs::ResultDbmInvalidPathFormat()); + while (IsSeparator(path[1])) { + path++; + } + + this->prev_path_start = path; + this->prev_path_end = path; + this->next_path = path + 1; + while (IsSeparator(this->next_path[0])) { + this->next_path++; + } + + return ResultSuccess(); + } + + void PathParser::Finalize() { + /* ... */ + } + + bool PathParser::IsFinished() const { + return this->finished; + } + + bool PathParser::IsDirectoryPath() const { + AMS_ASSERT(this->next_path != nullptr); + if (IsNullTerminator(this->next_path[0]) && IsSeparator(this->next_path[-1])) { + return true; + } + + if (IsCurrentDirectory(this->next_path)) { + return true; + } + + if (IsParentDirectory(this->next_path)) { + return true; + } + + return false; + } + + Result PathParser::GetAsDirectoryName(RomEntryName *out) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->prev_path_start != nullptr); + AMS_ASSERT(this->prev_path_end != nullptr); + AMS_ASSERT(this->next_path != nullptr); + + const size_t len = this->prev_path_end - this->prev_path_start; + R_UNLESS(len <= MaxPathLength, fs::ResultDbmDirectoryNameTooLong()); + + out->length = len; + out->path = this->prev_path_start; + return ResultSuccess(); + } + + Result PathParser::GetAsFileName(RomEntryName *out) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->prev_path_start != nullptr); + AMS_ASSERT(this->prev_path_end != nullptr); + AMS_ASSERT(this->next_path != nullptr); + + const size_t len = this->prev_path_end - this->prev_path_start; + R_UNLESS(len <= MaxPathLength, fs::ResultDbmFileNameTooLong()); + + out->length = len; + out->path = this->prev_path_start; + return ResultSuccess(); + } + + Result PathParser::GetNextDirectoryName(RomEntryName *out) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->prev_path_start != nullptr); + AMS_ASSERT(this->prev_path_end != nullptr); + AMS_ASSERT(this->next_path != nullptr); + + /* Set the current path to output. */ + out->length = this->prev_path_end - this->prev_path_start; + out->path = this->prev_path_start; + + /* Parse the next path. */ + this->prev_path_start = this->next_path; + const RomPathChar *cur = this->next_path; + for (size_t name_len = 0; true; name_len++) { + if (IsSeparator(cur[name_len])) { + R_UNLESS(name_len < MaxPathLength, fs::ResultDbmDirectoryNameTooLong()); + + this->prev_path_end = cur + name_len; + this->next_path = this->prev_path_end + 1; + + while (IsSeparator(this->next_path[0])) { + this->next_path++; + } + if (IsNullTerminator(this->next_path[0])) { + this->finished = true; + } + break; + } + + if (IsNullTerminator(cur[name_len])) { + this->finished = true; + this->prev_path_end = this->next_path = cur + name_len; + break; + } + } + + return ResultSuccess(); + } + + Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p) { + const RomPathChar *start = cur.path; + const RomPathChar *end = cur.path + cur.length - 1; + + s32 depth = 1; + if (IsParentDirectory(cur)) { + depth++; + } + + if (cur.path > p) { + size_t len = 0; + const RomPathChar *head = cur.path - 1; + while (head >= p) { + if (IsSeparator(*head)) { + if (IsCurrentDirectory(head + 1, len)) { + depth++; + } + + if (IsParentDirectory(head + 1, len)) { + depth += 2; + } + + if (depth == 0) { + start = head + 1; + break; + } + + while (IsSeparator(*head)) { + head--; + } + + end = head; + len = 0; + depth--; + } + + len++; + head--; + } + + R_UNLESS(depth == 0, fs::ResultDbmInvalidPathFormat()); + + if (head == p) { + start = p + 1; + } + } + + if (end <= p) { + out->path = p; + out->length = 0; + } else { + out->path = start; + out->length = end - start + 1; + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp b/libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp new file mode 100644 index 000000000..93659d160 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp @@ -0,0 +1,396 @@ +/* + * 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 . + */ +#include + +namespace ams::fssystem { + + namespace { + + constexpr size_t CalculateRequiredWorkingMemorySize(const RomFileSystemInformation &header) { + return header.directory_bucket_size + header.directory_entry_size + header.file_bucket_size + header.file_entry_size; + } + + class RomFsFile : public ams::fs::fsa::IFile, public ams::fs::impl::Newable { + private: + RomFsFileSystem *parent; + s64 start; + s64 end; + private: + s64 GetSize() const { + return this->end - this->start; + } + public: + RomFsFile(RomFsFileSystem *p, s64 s, s64 e) : parent(p), start(s), end(e) { /* ... */ } + virtual ~RomFsFile() { /* ... */ } + public: + virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([=]() -> Result { + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, size, option, fs::OpenMode_Read)); + + R_TRY(this->parent->GetBaseStorage()->Read(offset + this->start, buffer, read_size)); + *out = read_size; + + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + + return ResultSuccess(); + } + + virtual Result GetSizeImpl(s64 *out) override { + *out = this->GetSize(); + return ResultSuccess(); + } + + virtual Result FlushImpl() override { + return ResultSuccess(); + } + + virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + bool needs_append; + R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, fs::OpenMode_Read)); + AMS_ASSERT(needs_append == false); + return fs::ResultUnsupportedOperationInRomFsFileA(); + } + + virtual Result SetSizeImpl(s64 size) override { + R_TRY(this->DrySetSize(size, fs::OpenMode_Read)); + return fs::ResultUnsupportedOperationInRomFsFileA(); + } + + 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 { + switch (op_id) { + case fs::OperationId::InvalidateCache: + case fs::OperationId::QueryRange: + { + R_UNLESS(offset >= 0, fs::ResultOutOfRange()); + R_UNLESS(this->GetSize() >= 0, fs::ResultOutOfRange()); + + auto operate_size = size; + if (offset + operate_size > this->GetSize() || offset + operate_size < offset) { + operate_size = this->GetSize() - offset; + } + + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([=]() -> Result { + R_TRY(this->parent->GetBaseStorage()->OperateRange(dst, dst_size, op_id, this->start + offset, operate_size, src, src_size)); + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInRomFsFileB(); + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT(); + } + }; + + class RomFsDirectory : public ams::fs::fsa::IDirectory, public ams::fs::impl::Newable { + private: + using FindPosition = RomFsFileSystem::RomFileTable::FindPosition; + private: + RomFsFileSystem *parent; + FindPosition current_find; + FindPosition first_find; + fs::OpenDirectoryMode mode; + public: + RomFsDirectory(RomFsFileSystem *p, const FindPosition &f, fs::OpenDirectoryMode m) : parent(p), current_find(f), first_find(f), mode(m) { /* ... */ } + virtual ~RomFsDirectory() override { /* ... */ } + public: + virtual Result ReadImpl(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([=]() -> Result { + return this->ReadImpl(out_count, std::addressof(this->current_find), out_entries, max_entries); + }, AMS_CURRENT_FUNCTION_NAME)); + return ResultSuccess(); + } + + virtual Result GetEntryCountImpl(s64 *out) { + FindPosition find = this->first_find; + + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY(this->ReadImpl(out, std::addressof(find), nullptr, 0)); + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + return ResultSuccess(); + } + private: + Result ReadImpl(s64 *out_count, FindPosition *find, fs::DirectoryEntry *out_entries, s64 max_entries) { + constexpr size_t NameBufferSize = fs::EntryNameLengthMax + 1; + RomPathChar name[NameBufferSize]; + s32 i = 0; + + if (this->mode & fs::OpenDirectoryMode_Directory) { + while (i < max_entries || out_entries == nullptr) { + R_TRY_CATCH(this->parent->GetRomFileTable()->FindNextDirectory(name, find)) { + R_CATCH(fs::ResultDbmFindFinished) { break; } + } R_END_TRY_CATCH; + + if (out_entries) { + R_UNLESS(strnlen(name, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath()); + strncpy(out_entries[i].name, name, fs::EntryNameLengthMax); + out_entries[i].name[fs::EntryNameLengthMax] = '\x00'; + out_entries[i].type = fs::DirectoryEntryType_Directory; + out_entries[i].file_size = 0; + } + + i++; + } + } + + if (this->mode & fs::OpenDirectoryMode_File) { + while (i < max_entries || out_entries == nullptr) { + auto file_pos = find->next_file; + + R_TRY_CATCH(this->parent->GetRomFileTable()->FindNextFile(name, find)) { + R_CATCH(fs::ResultDbmFindFinished) { break; } + } R_END_TRY_CATCH; + + if (out_entries) { + R_UNLESS(strnlen(name, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath()); + strncpy(out_entries[i].name, name, fs::EntryNameLengthMax); + out_entries[i].name[fs::EntryNameLengthMax] = '\x00'; + out_entries[i].type = fs::DirectoryEntryType_File; + + RomFsFileSystem::RomFileTable::FileInfo file_info; + R_TRY(this->parent->GetRomFileTable()->OpenFile(std::addressof(file_info), this->parent->GetRomFileTable()->ConvertToFileId(file_pos))); + out_entries[i].file_size = file_info.size.Get(); + } + + i++; + } + } + + *out_count = i; + return ResultSuccess(); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT(); + } + }; + + } + + + RomFsFileSystem::RomFsFileSystem() : base_storage() { + /* ... */ + } + + RomFsFileSystem::~RomFsFileSystem() { + /* ... */ + } + + fs::IStorage *RomFsFileSystem::GetBaseStorage() { + return this->base_storage; + } + + RomFsFileSystem::RomFileTable *RomFsFileSystem::GetRomFileTable() { + return std::addressof(this->rom_file_table); + } + + Result RomFsFileSystem::GetRequiredWorkingMemorySize(size_t *out, fs::IStorage *storage) { + RomFileSystemInformation header; + + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY(storage->Read(0, std::addressof(header), sizeof(header))); + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + + *out = CalculateRequiredWorkingMemorySize(header); + return ResultSuccess(); + } + + Result RomFsFileSystem::Initialize(fs::IStorage *base, void *work, size_t work_size, bool use_cache) { + AMS_ABORT_UNLESS(!use_cache || work != nullptr); + AMS_ABORT_UNLESS(base != nullptr); + + /* Register blocking context for the scope. */ + buffers::ScopedBufferManagerContextRegistration _sr; + buffers::EnableBlockingBufferManagerAllocation(); + + /* Read the header. */ + RomFileSystemInformation header; + R_TRY(base->Read(0, std::addressof(header), sizeof(header))); + + /* Set up our storages. */ + if (use_cache) { + const size_t needed_size = CalculateRequiredWorkingMemorySize(header); + R_UNLESS(work_size >= needed_size, fs::ResultAllocationFailureInRomFsFileSystemA()); + + u8 *buf = static_cast(work); + auto dir_bucket_buf = buf; buf += header.directory_bucket_size; + auto dir_entry_buf = buf; buf += header.directory_entry_size; + auto file_bucket_buf = buf; buf += header.file_bucket_size; + auto file_entry_buf = buf; buf += header.file_entry_size; + + R_TRY(base->Read(header.directory_bucket_offset, dir_bucket_buf, static_cast(header.directory_bucket_size))); + R_TRY(base->Read(header.directory_entry_offset, dir_entry_buf, static_cast(header.directory_entry_size))); + R_TRY(base->Read(header.file_bucket_offset, file_bucket_buf, static_cast(header.file_bucket_size))); + R_TRY(base->Read(header.file_entry_offset, file_entry_buf, static_cast(header.file_entry_size))); + + this->dir_bucket_storage.reset(new fs::MemoryStorage(dir_bucket_buf, header.directory_bucket_size)); + this->dir_entry_storage.reset(new fs::MemoryStorage(dir_entry_buf, header.directory_entry_size)); + this->file_bucket_storage.reset(new fs::MemoryStorage(file_bucket_buf, header.file_bucket_size)); + this->file_entry_storage.reset(new fs::MemoryStorage(file_entry_buf, header.file_entry_size)); + } else { + this->dir_bucket_storage.reset(new fs::SubStorage(base, header.directory_bucket_offset, header.directory_bucket_size)); + this->dir_entry_storage.reset(new fs::SubStorage(base, header.directory_entry_offset, header.directory_entry_size)); + this->file_bucket_storage.reset(new fs::SubStorage(base, header.file_bucket_offset, header.file_bucket_size)); + this->file_entry_storage.reset(new fs::SubStorage(base, header.file_entry_offset, header.file_entry_size)); + } + + /* Ensure we allocated storages successfully. */ + R_UNLESS(this->dir_bucket_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemB()); + R_UNLESS(this->dir_entry_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemB()); + R_UNLESS(this->file_bucket_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemB()); + R_UNLESS(this->file_entry_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemB()); + + /* Initialize the rom table. */ + R_TRY(this->rom_file_table.Initialize(this->dir_bucket_storage.get(), 0, static_cast(header.directory_bucket_size), + this->dir_entry_storage.get(), 0, static_cast(header.directory_entry_size), + this->file_bucket_storage.get(), 0, static_cast(header.file_bucket_size), + this->file_entry_storage.get(), 0, static_cast(header.file_entry_size))); + + /* Set members. */ + this->entry_size = header.body_offset; + this->base_storage = base; + return ResultSuccess(); + } + + Result RomFsFileSystem::Initialize(std::shared_ptr base, void *work, size_t work_size, bool use_cache) { + this->shared_storage = std::move(base); + return this->Initialize(this->shared_storage.get(), work, work_size, use_cache); + } + + Result RomFsFileSystem::GetFileInfo(RomFileTable::FileInfo *out, const char *path) { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([=]() -> Result { + R_TRY_CATCH(this->rom_file_table.OpenFile(out, path)) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()); + } R_END_TRY_CATCH; + + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + + return ResultSuccess(); + } + + Result RomFsFileSystem::GetFileBaseOffset(s64 *out, const char *path) { + RomFileTable::FileInfo info; + R_TRY(this->GetFileInfo(std::addressof(info), path)); + *out = this->entry_size + info.offset.Get(); + return ResultSuccess(); + } + + Result RomFsFileSystem::CreateFileImpl(const char *path, s64 size, int flags) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::DeleteFileImpl(const char *path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::CreateDirectoryImpl(const char *path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::DeleteDirectoryImpl(const char *path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::DeleteDirectoryRecursivelyImpl(const char *path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::RenameFileImpl(const char *old_path, const char *new_path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::RenameDirectoryImpl(const char *old_path, const char *new_path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([=]() -> Result { + RomDirectoryInfo dir_info; + + R_TRY_CATCH(this->rom_file_table.GetDirectoryInformation(std::addressof(dir_info), path)) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()) + R_CATCH(fs::ResultDbmInvalidOperation) { + RomFileTable::FileInfo file_info; + R_TRY(this->GetFileInfo(std::addressof(file_info), path)); + + *out = fs::DirectoryEntryType_File; + return ResultSuccess(); + } + } R_END_TRY_CATCH; + + *out = fs::DirectoryEntryType_Directory; + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + + return ResultSuccess(); + } + + Result RomFsFileSystem::OpenFileImpl(std::unique_ptr *out_file, const char *path, fs::OpenMode mode) { + R_UNLESS(mode == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); + + RomFileTable::FileInfo file_info; + R_TRY(this->GetFileInfo(std::addressof(file_info), path)); + + auto file = std::make_unique(this, this->entry_size + file_info.offset.Get(), this->entry_size + file_info.offset.Get() + file_info.size.Get()); + R_UNLESS(file != nullptr, fs::ResultAllocationFailureInRomFsFileSystemC()); + + *out_file = std::move(file); + return ResultSuccess(); + } + + Result RomFsFileSystem::OpenDirectoryImpl(std::unique_ptr *out_dir, const char *path, fs::OpenDirectoryMode mode) { + RomFileTable::FindPosition find; + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY_CATCH(this->rom_file_table.FindOpen(std::addressof(find), path)) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()) + } R_END_TRY_CATCH; + + return ResultSuccess(); + }, AMS_CURRENT_FUNCTION_NAME)); + + auto dir = std::make_unique(this, find, mode); + R_UNLESS(dir != nullptr, fs::ResultAllocationFailureInRomFsFileSystemD()); + + *out_dir = std::move(dir); + return ResultSuccess(); + } + + Result RomFsFileSystem::CommitImpl() { + return ResultSuccess(); + } + + Result RomFsFileSystem::GetFreeSpaceSizeImpl(s64 *out, const char *path) { + *out = 0; + return ResultSuccess(); + } + + Result RomFsFileSystem::CleanDirectoryRecursivelyImpl(const char *path) { + return fs::ResultUnsupportedOperationInRomFsFileSystemA(); + } + + Result RomFsFileSystem::CommitProvisionallyImpl(s64 counter) { + return fs::ResultUnsupportedOperationInRomFsFileSystemB(); + } + +} diff --git a/libraries/libvapours/include/vapours/defines.hpp b/libraries/libvapours/include/vapours/defines.hpp index 2c937e15b..5f78e90fc 100644 --- a/libraries/libvapours/include/vapours/defines.hpp +++ b/libraries/libvapours/include/vapours/defines.hpp @@ -61,3 +61,5 @@ #define AMS_LIKELY(expr) AMS_PREDICT_TRUE(expr, 1.0) #define AMS_UNLIKELY(expr) AMS_PREDICT_FALSE(expr, 1.0) + +#define AMS_CURRENT_FUNCTION_NAME __FUNCTION__ diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 60b35b595..fed6b2453 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -240,6 +240,8 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(ExtensionSizeTooLarge, 6066); R_DEFINE_ERROR_RESULT(ExtensionSizeInvalid, 6067); + R_DEFINE_ERROR_RESULT(InvalidOpenMode, 6072); + R_DEFINE_ERROR_RANGE(InvalidEnumValue, 6080, 6099); R_DEFINE_ERROR_RESULT(InvalidSaveDataState, 6081); R_DEFINE_ERROR_RESULT(InvalidSaveDataSpaceId, 6082); @@ -286,8 +288,9 @@ namespace ams::fs { R_DEFINE_ERROR_RANGE(NotFound, 6600, 6699); R_DEFINE_ERROR_RANGE(OutOfResource, 6700, 6799); - R_DEFINE_ERROR_RESULT(MappingTableFull, 6706); - R_DEFINE_ERROR_RESULT(OpenCountLimit, 6709); + R_DEFINE_ERROR_RESULT(BufferAllocationFailed, 6705); + R_DEFINE_ERROR_RESULT(MappingTableFull, 6706); + R_DEFINE_ERROR_RESULT(OpenCountLimit, 6709); R_DEFINE_ERROR_RANGE(MappingFailed, 6800, 6899); R_DEFINE_ERROR_RESULT(MapFull, 6811);