From b62014554ce4ee5dc5483ca9a58dbc01181a5814 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 22 Mar 2019 11:28:09 -0700 Subject: [PATCH] fs.mitm: Implement SubDirectoryFileSystem --- .../ams_mitm/source/fs_mitm/fs_path_utils.cpp | 95 +++++++ .../ams_mitm/source/fs_mitm/fs_path_utils.hpp | 10 +- .../ams_mitm/source/fs_mitm/fs_results.hpp | 13 +- .../fs_mitm/fs_subdirectory_filesystem.cpp | 248 ++++++++++++++++++ .../fs_mitm/fs_subdirectory_filesystem.hpp | 77 ++++++ .../source/fs_mitm/fsmitm_service.cpp | 2 +- 6 files changed, 438 insertions(+), 7 deletions(-) create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.cpp create mode 100644 stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.hpp diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.cpp b/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.cpp index 0faa04fcd..d200ef4bc 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.cpp @@ -71,3 +71,98 @@ Result FsPathUtils::ConvertPathForServiceObject(FsPath *out, const char *path) { const size_t max_len = (FS_MAX_PATH-1) - prefix_len; return FsPathUtils::VerifyPath(out->str + prefix_len, max_len, max_len); } + +Result FsPathUtils::IsNormalized(bool *out, const char *path) { + /* Nintendo uses a state machine here. */ + enum class PathState { + Start, + Normal, + FirstSeparator, + Separator, + CurrentDir, + ParentDir, + WindowsDriveLetter, + }; + + PathState state = PathState::Start; + + for (const char *cur = path; *cur != 0; cur++) { + const char c = *cur; + switch (state) { + case PathState::Start: + if (IsWindowsDriveLetter(c)) { + state = PathState::WindowsDriveLetter; + } else if (c == '/') { + state = PathState::FirstSeparator; + } else { + return ResultFsInvalidPathFormat; + } + break; + case PathState::Normal: + if (c == '/') { + state = PathState::Separator; + } + break; + case PathState::FirstSeparator: + case PathState::Separator: + /* It is unclear why first separator and separator are separate states... */ + if (c == '/') { + *out = false; + return 0; + } else if (c == '.') { + state = PathState::CurrentDir; + } else { + state = PathState::Normal; + } + break; + case PathState::CurrentDir: + if (c == '/') { + *out = false; + return 0; + } else if (c == '.') { + state = PathState::ParentDir; + } else { + state = PathState::Normal; + } + break; + case PathState::ParentDir: + if (c == '/') { + *out = false; + return 0; + } else { + state = PathState::Normal; + } + break; + case PathState::WindowsDriveLetter: + if (c == ':') { + *out = true; + return 0; + } else { + return ResultFsInvalidPathFormat; + } + break; + } + } + + switch (state) { + case PathState::Start: + case PathState::WindowsDriveLetter: + return ResultFsInvalidPathFormat; + case PathState::FirstSeparator: + case PathState::Separator: + *out = false; + break; + case PathState::Normal: + case PathState::CurrentDir: + case PathState::ParentDir: + *out = true; + break; + } + + return 0; +} + +Result FsPathUtils::Normalize(char *out, size_t max_out_size, const char *src, size_t *out_size) { + /* TODO */ + return ResultFsNotImplemented; +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.hpp b/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.hpp index 975fe0337..fbd073c13 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.hpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_path_utils.hpp @@ -25,10 +25,16 @@ class FsPathUtils { public: static Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len); static Result ConvertPathForServiceObject(FsPath *out, const char *path); + + static Result IsNormalized(bool *out, const char *path); + static Result Normalize(char *out, size_t max_out_size, const char *src, size_t *out_size); + static bool IsWindowsDriveLetter(const char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); + } + static bool IsWindowsAbsolutePath(const char *path) { /* Nintendo uses this in path comparisons... */ - return (('a' <= path[0] && path[0] <= 'z') || (('A' <= path[0] && path[0] <= 'Z'))) && - path[0] != 0 && path[1] == ':'; + return IsWindowsDriveLetter(path[0]) && path[1] == ':'; } }; diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_results.hpp b/stratosphere/ams_mitm/source/fs_mitm/fs_results.hpp index 6efecd17d..9dd12dd60 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_results.hpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_results.hpp @@ -22,10 +22,15 @@ static constexpr u32 Module_Fs = 2; static constexpr Result ResultFsNotImplemented = MAKERESULT(Module_Fs, 3001); static constexpr Result ResultFsOutOfRange = MAKERESULT(Module_Fs, 3005); -static constexpr Result ResultFsInvalidArgument = MAKERESULT(Module_Fs, 6001); -static constexpr Result ResultFsInvalidPath = MAKERESULT(Module_Fs, 6002); -static constexpr Result ResultFsTooLongPath = MAKERESULT(Module_Fs, 6003); -static constexpr Result ResultFsInvalidCharacter = MAKERESULT(Module_Fs, 6004); +static constexpr Result ResultFsAllocationFailureInSubDirectoryFileSystem = MAKERESULT(Module_Fs, 3355); + +static constexpr Result ResultFsInvalidArgument = MAKERESULT(Module_Fs, 6001); +static constexpr Result ResultFsInvalidPath = MAKERESULT(Module_Fs, 6002); +static constexpr Result ResultFsTooLongPath = MAKERESULT(Module_Fs, 6003); +static constexpr Result ResultFsInvalidCharacter = MAKERESULT(Module_Fs, 6004); +static constexpr Result ResultFsInvalidPathFormat = MAKERESULT(Module_Fs, 6005); +static constexpr Result ResultFsDirectoryUnobtainable = MAKERESULT(Module_Fs, 6006); +static constexpr Result ResultFsNotNormalized = MAKERESULT(Module_Fs, 6007); static constexpr Result ResultFsInvalidOffset = MAKERESULT(Module_Fs, 6061); static constexpr Result ResultFsInvalidSize = MAKERESULT(Module_Fs, 6062); diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.cpp b/stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.cpp new file mode 100644 index 000000000..0b17a5e8c --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2018 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 +#include +#include + +#include "fs_subdirectory_filesystem.hpp" +#include "fs_path_utils.hpp" + +Result SubDirectoryFileSystem::Initialize(const char *bp) { + if (strnlen(bp, FS_MAX_PATH) >= FS_MAX_PATH) { + return ResultFsTooLongPath; + } + + /* Normalize the path. */ + char normal_path[FS_MAX_PATH + 1]; + size_t normal_path_len; + Result rc = FsPathUtils::Normalize(normal_path, sizeof(normal_path), bp, &normal_path_len); + if (R_FAILED(rc)) { + /* N calls svcBreak here. */ + std::abort(); + } + + /* Ensure terminating '/' */ + if (normal_path[normal_path_len-1] != '/') { + if (normal_path_len + 2 > sizeof(normal_path)) { + std::abort(); + } + + strncat(normal_path, "/", 2); + normal_path[sizeof(normal_path)-1] = 0; + normal_path_len++; + } + + this->base_path_len = normal_path_len + 1; + this->base_path = reinterpret_cast(malloc(this->base_path_len)); + if (this->base_path == nullptr) { + return ResultFsAllocationFailureInSubDirectoryFileSystem; + } + + std::strncpy(this->base_path, normal_path, this->base_path_len); + this->base_path[this->base_path_len-1] = 0; + return 0; +} + +Result SubDirectoryFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) { + if (this->base_path_len + strnlen(relative_path, FS_MAX_PATH) > out_size) { + return ResultFsTooLongPath; + } + + /* Copy base path. */ + std::strncpy(out, this->base_path, out_size); + out[out_size-1] = 0; + + /* Normalize it. */ + return FsPathUtils::Normalize(out + this->base_path_len - 2, out_size - (this->base_path_len - 2), relative_path, nullptr); +} + +Result SubDirectoryFileSystem::CreateFileImpl(FsPath &path, uint64_t size, int flags) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->CreateFile(full_path, size, flags); +} + +Result SubDirectoryFileSystem::DeleteFileImpl(FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->DeleteFile(full_path); +} + +Result SubDirectoryFileSystem::CreateDirectoryImpl(FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->CreateDirectory(full_path); +} + +Result SubDirectoryFileSystem::DeleteDirectoryImpl(FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->DeleteDirectory(full_path); +} + +Result SubDirectoryFileSystem::DeleteDirectoryRecursivelyImpl(FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->DeleteDirectoryRecursively(full_path); +} + +Result SubDirectoryFileSystem::RenameFileImpl(FsPath &old_path, FsPath &new_path) { + Result rc; + FsPath full_old_path, full_new_path; + + if (R_FAILED((rc = GetFullPath(full_old_path, old_path)))) { + return rc; + } + + if (R_FAILED((rc = GetFullPath(full_new_path, new_path)))) { + return rc; + } + + return this->base_fs->RenameFile(full_old_path, full_new_path); +} + +Result SubDirectoryFileSystem::RenameDirectoryImpl(FsPath &old_path, FsPath &new_path) { + Result rc; + FsPath full_old_path, full_new_path; + + if (R_FAILED((rc = GetFullPath(full_old_path, old_path)))) { + return rc; + } + + if (R_FAILED((rc = GetFullPath(full_new_path, new_path)))) { + return rc; + } + + return this->base_fs->RenameDirectory(full_old_path, full_new_path); +} + +Result SubDirectoryFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->GetEntryType(out, full_path); +} + +Result SubDirectoryFileSystem::OpenFileImpl(std::unique_ptr &out_file, FsPath &path, OpenMode mode) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->OpenFile(out_file, full_path, mode); +} + +Result SubDirectoryFileSystem::OpenDirectoryImpl(std::unique_ptr &out_dir, FsPath &path, DirectoryOpenMode mode) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->OpenDirectory(out_dir, full_path, mode); +} + +Result SubDirectoryFileSystem::CommitImpl() { + return this->base_fs->Commit(); +} + +Result SubDirectoryFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->GetFreeSpaceSize(out, full_path); +} + +Result SubDirectoryFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->GetTotalSpaceSize(out, full_path); +} + +Result SubDirectoryFileSystem::CleanDirectoryRecursivelyImpl(FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->CleanDirectoryRecursively(full_path); +} + +Result SubDirectoryFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->GetFileTimeStampRaw(out, full_path); +} + +Result SubDirectoryFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) { + Result rc; + FsPath full_path; + + if (R_FAILED((rc = GetFullPath(full_path, path)))) { + return rc; + } + + return this->base_fs->QueryEntry(out, out_size, in, in_size, query, full_path); +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.hpp b/stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.hpp new file mode 100644 index 000000000..e6c406fa1 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_subdirectory_filesystem.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 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 "fs_ifilesystem.hpp" +#include "fs_path_utils.hpp" + +class SubDirectoryFileSystem : public IFileSystem { + private: + std::shared_ptr base_fs; + char *base_path = nullptr; + size_t base_path_len = 0; + + public: + SubDirectoryFileSystem(IFileSystem *fs, const char *bp) : base_fs(fs) { + Result rc = this->Initialize(bp); + if (R_FAILED(rc)) { + fatalSimple(rc); + } + } + + SubDirectoryFileSystem(std::shared_ptr fs, const char *bp) : base_fs(fs) { + Result rc = this->Initialize(bp); + if (R_FAILED(rc)) { + fatalSimple(rc); + } + } + + + virtual ~SubDirectoryFileSystem() { + if (this->base_path != nullptr) { + free(this->base_path); + } + } + + private: + Result Initialize(const char *bp); + protected: + Result GetFullPath(char *out, size_t out_size, const char *relative_path); + Result GetFullPath(FsPath &full_path, FsPath &relative_path) { + return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str); + } + + public: + virtual Result CreateFileImpl(FsPath &path, uint64_t size, int flags) override; + virtual Result DeleteFileImpl(FsPath &path) override; + virtual Result CreateDirectoryImpl(FsPath &path) override; + virtual Result DeleteDirectoryImpl(FsPath &path) override; + virtual Result DeleteDirectoryRecursivelyImpl(FsPath &path) override; + virtual Result RenameFileImpl(FsPath &old_path, FsPath &new_path) override; + virtual Result RenameDirectoryImpl(FsPath &old_path, FsPath &new_path) override; + virtual Result GetEntryTypeImpl(DirectoryEntryType *out, FsPath &path) override; + virtual Result OpenFileImpl(std::unique_ptr &out_file, FsPath &path, OpenMode mode) override; + virtual Result OpenDirectoryImpl(std::unique_ptr &out_dir, FsPath &path, DirectoryOpenMode mode) override; + virtual Result CommitImpl() override; + virtual Result GetFreeSpaceSizeImpl(uint64_t *out, FsPath &path) override; + virtual Result GetTotalSpaceSizeImpl(uint64_t *out, FsPath &path) override; + virtual Result CleanDirectoryRecursivelyImpl(FsPath &path) override; + virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, FsPath &path) override; + virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, FsPath &path) override; +}; \ No newline at end of file diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_service.cpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_service.cpp index 79d490981..0732c0652 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_service.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_service.cpp @@ -28,7 +28,7 @@ #include "fsmitm_romstorage.hpp" #include "fsmitm_layeredrom.hpp" -#include "fs_ifilesystem.hpp" +#include "fs_subdirectory_filesystem.hpp" #include "../debug.hpp"