diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index be40bb263..d19df40b3 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -22,6 +22,7 @@ #include "fs/fsa/fs_registrar.hpp" #include "fs/fs_remote_filesystem.hpp" #include "fs/fs_istorage.hpp" +#include "fs/fs_substorage.hpp" #include "fs/fs_remote_storage.hpp" #include "fs/fs_file_storage.hpp" #include "fs/fs_query_range.hpp" @@ -29,6 +30,7 @@ #include "fs/fs_mount.hpp" #include "fs/fs_path_tool.hpp" #include "fs/fs_path_utils.hpp" +#include "fs/fs_rom_path_tool.hpp" #include "fs/fs_content_storage.hpp" #include "fs/fs_game_card.hpp" #include "fs/fs_sd_card.hpp" diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp index 28c180558..8eb601cb0 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_path_tool.hpp @@ -26,7 +26,7 @@ namespace ams::fs { constexpr inline char Dot = '.'; constexpr inline char NullTerminator = '\x00'; - constexpr inline char UnsupportedDirectorySeparator = '/'; + constexpr inline char UnsupportedDirectorySeparator = '\\'; } class PathTool { diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_rom_path_tool.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_path_tool.hpp new file mode 100644 index 000000000..dd57aba5f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_path_tool.hpp @@ -0,0 +1,122 @@ +/* + * 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 "fs_rom_types.hpp" + +namespace ams::fs { + + 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) { + AMS_ABORT_UNLESS(entry != nullptr); + 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) { + AMS_ABORT_UNLESS(p != nullptr); + return length == 1 && IsDot(p[0]); + } + + constexpr inline bool IsCurrentDirectory(const RomPathChar *p) { + AMS_ABORT_UNLESS(p != nullptr); + 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) { + AMS_ABORT_UNLESS(p != nullptr); + return IsDot(p[0]) && IsDot(p[1]) && IsNullTerminator(p[2]); + } + + constexpr inline bool IsParentDirectory(const RomPathChar *p, size_t length) { + AMS_ABORT_UNLESS(p != nullptr); + return length == 2 && IsDot(p[0]) && IsDot(p[1]); + } + + constexpr inline bool IsEqualPath(const RomPathChar *lhs, const RomPathChar *rhs, size_t length) { + AMS_ABORT_UNLESS(lhs != nullptr); + AMS_ABORT_UNLESS(rhs != nullptr); + return std::strncmp(lhs, rhs, length) == 0; + } + + constexpr inline bool IsEqualName(const RomEntryName &lhs, const RomPathChar *rhs) { + AMS_ABORT_UNLESS(rhs != nullptr); + 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/fs/fs_rom_types.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_rom_types.hpp new file mode 100644 index 000000000..cfffd658f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_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 "fs_common.hpp" + +namespace ams::fs { + + 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 { + s64 offset; + s64 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/fs/fs_substorage.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp new file mode 100644 index 000000000..1bcebd337 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp @@ -0,0 +1,124 @@ +/* + * 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 "impl/fs_newable.hpp" +#include "fs_istorage.hpp" + +namespace ams::fs { + + class SubStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + private: + std::shared_ptr shared_base_storage; + fs::IStorage *base_storage; + s64 offset; + s64 size; + bool resizable; + private: + constexpr bool IsValid() const { + return this->base_storage != nullptr; + } + public: + SubStorage() : shared_base_storage(), base_storage(nullptr), offset(0), size(0), resizable(false) { /* ... */ } + + SubStorage(const SubStorage &rhs) : shared_base_storage(), base_storage(rhs.base_storage), offset(rhs.offset), size(rhs.size), resizable(rhs.resizable) { /* ... */} + SubStorage &operator=(const SubStorage &rhs) { + if (this != std::addressof(rhs)) { + this->base_storage = rhs.base_storage; + this->offset = rhs.offset; + this->size = rhs.size; + this->resizable = rhs.resizable; + } + return *this; + } + + SubStorage(IStorage *storage, s64 o, s64 sz) : shared_base_storage(), base_storage(storage), offset(o), size(sz) { + AMS_ABORT_UNLESS(this->IsValid()); + AMS_ABORT_UNLESS(this->offset >= 0); + AMS_ABORT_UNLESS(this->size >= 0); + } + + SubStorage(std::shared_ptr storage, s64 o, s64 sz) : shared_base_storage(storage), base_storage(storage.get()), offset(o), size(sz) { + AMS_ABORT_UNLESS(this->IsValid()); + AMS_ABORT_UNLESS(this->offset >= 0); + AMS_ABORT_UNLESS(this->size >= 0); + } + + SubStorage(SubStorage *sub, s64 o, s64 sz) : shared_base_storage(), base_storage(sub->base_storage), offset(o + sub->offset), size(sz) { + AMS_ABORT_UNLESS(this->IsValid()); + AMS_ABORT_UNLESS(this->offset >= 0); + AMS_ABORT_UNLESS(this->size >= 0); + AMS_ABORT_UNLESS(sub->size >= o + sz); + } + + public: + void SetResizable(bool rsz) { + this->resizable = rsz; + } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + R_UNLESS(size != 0, ResultSuccess()); + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange()); + return this->base_storage->Read(this->offset + offset, buffer, size); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override{ + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + R_UNLESS(size != 0, ResultSuccess()); + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IStorage::IsRangeValid(offset, size, this->size), fs::ResultOutOfRange()); + return this->base_storage->Write(this->offset + offset, buffer, size); + } + + virtual Result Flush() override { + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + return this->base_storage->Flush(); + } + + virtual Result SetSize(s64 size) override { + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + R_UNLESS(this->resizable, fs::ResultUnsupportedOperation()); + R_UNLESS(IStorage::IsOffsetAndSizeValid(this->offset, size), fs::ResultInvalidSize()); + + s64 cur_size; + R_TRY(this->base_storage->GetSize(std::addressof(cur_size))); + + R_UNLESS(cur_size == this->offset + this->size, fs::ResultUnsupportedOperation()); + + R_TRY(this->base_storage->SetSize(this->offset + size)); + + this->size = size; + return ResultSuccess(); + } + + virtual Result GetSize(s64 *out) override { + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + *out = this->size; + return ResultSuccess(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + R_UNLESS(size != 0, ResultSuccess()); + R_UNLESS(IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange()); + return this->base_storage->OperateRange(dst, dst_size, op_id, this->offset + offset, size, src, src_size); + } + + using IStorage::OperateRange; + }; + +} diff --git a/libraries/libstratosphere/source/fs/fs_rom_path_tool.cpp b/libraries/libstratosphere/source/fs/fs_rom_path_tool.cpp new file mode 100644 index 000000000..c758c86dc --- /dev/null +++ b/libraries/libstratosphere/source/fs/fs_rom_path_tool.cpp @@ -0,0 +1,194 @@ +/* + * 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::fs::RomPathTool { + + Result PathParser::Initialize(const RomPathChar *path) { + AMS_ABORT_UNLESS(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() { + this->prev_path_start = nullptr; + this->prev_path_end = nullptr; + this->next_path = nullptr; + this->finished = false; + } + + 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_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(this->prev_path_start != nullptr); + AMS_ABORT_UNLESS(this->prev_path_end != nullptr); + AMS_ABORT_UNLESS(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_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(this->prev_path_start != nullptr); + AMS_ABORT_UNLESS(this->prev_path_end != nullptr); + AMS_ABORT_UNLESS(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_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(this->prev_path_start != nullptr); + AMS_ABORT_UNLESS(this->prev_path_end != nullptr); + AMS_ABORT_UNLESS(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) { + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(p != nullptr); + + 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; + } + + while (IsSeparator(*head)) { + head--; + } + + end = head; + len = 0; + depth--; + } + + len++; + head--; + } + + R_UNLESS(depth == 0, fs::ResultDirectoryUnobtainable()); + + 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/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 74ed9aafe..e7a72155a 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -140,6 +140,12 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(MapFull, 6811); R_DEFINE_ERROR_RANGE(BadState, 6900, 6999); - R_DEFINE_ERROR_RESULT(NotMounted, 6905); + R_DEFINE_ERROR_RESULT(NotInitialized, 6902); + R_DEFINE_ERROR_RESULT(NotMounted, 6905); + + R_DEFINE_ERROR_RESULT(DbmInvalidOperation, 7914); + R_DEFINE_ERROR_RESULT(DbmInvalidPathFormat, 7915); + R_DEFINE_ERROR_RESULT(DbmDirectoryNameTooLong, 7916); + R_DEFINE_ERROR_RESULT(DbmFileNameTooLong, 7917); }