From 4dd8a831bd5ea32108db837754289ab42a2fa6ca Mon Sep 17 00:00:00 2001 From: wwylele Date: Fri, 14 Oct 2016 15:29:09 +0800 Subject: [PATCH 01/13] FileSys: make Archive interfaces return error code and make the mode parameter a reference since it is a BitField union --- src/core/file_sys/archive_backend.h | 28 ++++++++-------- src/core/file_sys/disk_archive.cpp | 50 +++++++++++++++++++++-------- src/core/file_sys/disk_archive.h | 14 ++++---- src/core/file_sys/ivfc_archive.cpp | 31 ++++++++++-------- src/core/file_sys/ivfc_archive.h | 14 ++++---- src/core/hle/service/fs/archive.cpp | 41 ++++++----------------- 6 files changed, 91 insertions(+), 87 deletions(-) diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index 06b8f2ed7..58f6c150c 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h @@ -87,7 +87,7 @@ public: * @return Opened file, or error code */ virtual ResultVal> OpenFile(const Path& path, - const Mode mode) const = 0; + const Mode& mode) const = 0; /** * Delete a file specified by its path @@ -100,53 +100,53 @@ public: * Rename a File specified by its path * @param src_path Source path relative to the archive * @param dest_path Destination path relative to the archive - * @return Whether rename succeeded + * @return Result of the operation */ - virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0; + virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0; /** * Delete a directory specified by its path * @param path Path relative to the archive - * @return Whether the directory could be deleted + * @return Result of the operation */ - virtual bool DeleteDirectory(const Path& path) const = 0; + virtual ResultCode DeleteDirectory(const Path& path) const = 0; /** * Delete a directory specified by its path and anything under it * @param path Path relative to the archive - * @return Whether the directory could be deleted + * @return Result of the operation */ - virtual bool DeleteDirectoryRecursively(const Path& path) const = 0; + virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0; /** * Create a file specified by its path * @param path Path relative to the Archive * @param size The size of the new file, filled with zeroes - * @return File creation result code + * @return Result of the operation */ virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; /** * Create a directory specified by its path * @param path Path relative to the archive - * @return Whether the directory could be created + * @return Result of the operation */ - virtual bool CreateDirectory(const Path& path) const = 0; + virtual ResultCode CreateDirectory(const Path& path) const = 0; /** * Rename a Directory specified by its path * @param src_path Source path relative to the archive * @param dest_path Destination path relative to the archive - * @return Whether rename succeeded + * @return Result of the operation */ - virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; + virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; /** * Open a directory specified by its path * @param path Path relative to the archive - * @return Opened directory, or nullptr + * @return Opened directory, or error code */ - virtual std::unique_ptr OpenDirectory(const Path& path) const = 0; + virtual ResultVal> OpenDirectory(const Path& path) const = 0; /** * Get the free space diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index 2f05af361..ce6b9360b 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -16,7 +16,7 @@ namespace FileSys { ResultVal> DiskArchive::OpenFile(const Path& path, - const Mode mode) const { + const Mode& mode) const { LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); auto file = std::make_unique(*this, path, mode); ResultCode result = file->Open(); @@ -43,16 +43,28 @@ ResultCode DiskArchive::DeleteFile(const Path& path) const { ErrorLevel::Status); } -bool DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const { - return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()); +ResultCode DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const { + if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) + return RESULT_SUCCESS; + + // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't + // exist or similar. Verify. + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::NothingHappened, ErrorLevel::Status); } -bool DiskArchive::DeleteDirectory(const Path& path) const { - return FileUtil::DeleteDir(mount_point + path.AsString()); +ResultCode DiskArchive::DeleteDirectory(const Path& path) const { + if (FileUtil::DeleteDir(mount_point + path.AsString())) + return RESULT_SUCCESS; + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::Canceled, ErrorLevel::Status); } -bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const { - return FileUtil::DeleteDirRecursively(mount_point + path.AsString()); +ResultCode DiskArchive::DeleteDirectoryRecursively(const Path& path) const { + if (FileUtil::DeleteDirRecursively(mount_point + path.AsString())) + return RESULT_SUCCESS; + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::Canceled, ErrorLevel::Status); } ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const { @@ -81,20 +93,30 @@ ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const { ErrorLevel::Info); } -bool DiskArchive::CreateDirectory(const Path& path) const { - return FileUtil::CreateDir(mount_point + path.AsString()); +ResultCode DiskArchive::CreateDirectory(const Path& path) const { + if (FileUtil::CreateDir(mount_point + path.AsString())) + return RESULT_SUCCESS; + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::Canceled, ErrorLevel::Status); } -bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { - return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()); +ResultCode DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { + if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) + return RESULT_SUCCESS; + + // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't + // exist or similar. Verify. + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::NothingHappened, ErrorLevel::Status); } -std::unique_ptr DiskArchive::OpenDirectory(const Path& path) const { +ResultVal> DiskArchive::OpenDirectory(const Path& path) const { LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); auto directory = std::make_unique(*this, path); if (!directory->Open()) - return nullptr; - return std::move(directory); + return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, + ErrorLevel::Permanent); + return MakeResult>(std::move(directory)); } u64 DiskArchive::GetFreeBytes() const { diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index 59ebb2002..0edd87954 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -34,15 +34,15 @@ public: } ResultVal> OpenFile(const Path& path, - const Mode mode) const override; + const Mode& mode) const override; ResultCode DeleteFile(const Path& path) const override; - bool RenameFile(const Path& src_path, const Path& dest_path) const override; - bool DeleteDirectory(const Path& path) const override; - bool DeleteDirectoryRecursively(const Path& path) const override; + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; + ResultCode DeleteDirectory(const Path& path) const override; + ResultCode DeleteDirectoryRecursively(const Path& path) const override; ResultCode CreateFile(const Path& path, u64 size) const override; - bool CreateDirectory(const Path& path) const override; - bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; - std::unique_ptr OpenDirectory(const Path& path) const override; + ResultCode CreateDirectory(const Path& path) const override; + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; + ResultVal> OpenDirectory(const Path& path) const override; u64 GetFreeBytes() const override; protected: diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp index af59d296d..2735d2e3c 100644 --- a/src/core/file_sys/ivfc_archive.cpp +++ b/src/core/file_sys/ivfc_archive.cpp @@ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const { } ResultVal> IVFCArchive::OpenFile(const Path& path, - const Mode mode) const { + const Mode& mode) const { return MakeResult>( std::make_unique(romfs_file, data_offset, data_size)); } @@ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const { ErrorLevel::Status); } -bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { +ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", GetName().c_str()); - return false; + // TODO(wwylele): Use correct error code + return ResultCode(-1); } -bool IVFCArchive::DeleteDirectory(const Path& path) const { +ResultCode IVFCArchive::DeleteDirectory(const Path& path) const { LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", GetName().c_str()); - return false; + // TODO(wwylele): Use correct error code + return ResultCode(-1); } -bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { +ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", GetName().c_str()); - return false; + // TODO(wwylele): Use correct error code + return ResultCode(-1); } ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { @@ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { ErrorLevel::Permanent); } -bool IVFCArchive::CreateDirectory(const Path& path) const { +ResultCode IVFCArchive::CreateDirectory(const Path& path) const { LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", GetName().c_str()); - return false; + // TODO(wwylele): Use correct error code + return ResultCode(-1); } -bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { +ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", GetName().c_str()); - return false; + // TODO(wwylele): Use correct error code + return ResultCode(-1); } -std::unique_ptr IVFCArchive::OpenDirectory(const Path& path) const { - return std::make_unique(); +ResultVal> IVFCArchive::OpenDirectory(const Path& path) const { + return MakeResult>(std::make_unique()); } u64 IVFCArchive::GetFreeBytes() const { diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h index 2fbb3a568..af6297e1f 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/ivfc_archive.h @@ -33,15 +33,15 @@ public: std::string GetName() const override; ResultVal> OpenFile(const Path& path, - const Mode mode) const override; + const Mode& mode) const override; ResultCode DeleteFile(const Path& path) const override; - bool RenameFile(const Path& src_path, const Path& dest_path) const override; - bool DeleteDirectory(const Path& path) const override; - bool DeleteDirectoryRecursively(const Path& path) const override; + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; + ResultCode DeleteDirectory(const Path& path) const override; + ResultCode DeleteDirectoryRecursively(const Path& path) const override; ResultCode CreateFile(const Path& path, u64 size) const override; - bool CreateDirectory(const Path& path) const override; - bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; - std::unique_ptr OpenDirectory(const Path& path) const override; + ResultCode CreateDirectory(const Path& path) const override; + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; + ResultVal> OpenDirectory(const Path& path) const override; u64 GetFreeBytes() const override; protected: diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 7f9696bfb..891d7bc84 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -338,17 +338,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, return ERR_INVALID_ARCHIVE_HANDLE; if (src_archive == dest_archive) { - if (src_archive->RenameFile(src_path, dest_path)) - return RESULT_SUCCESS; + return src_archive->RenameFile(src_path, dest_path); } else { // TODO: Implement renaming across archives return UnimplementedFunction(ErrorModule::FS); } - - // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't - // exist or similar. Verify. - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::NothingHappened, ErrorLevel::Status); } ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { @@ -356,10 +350,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; - if (archive->DeleteDirectory(path)) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::Canceled, ErrorLevel::Status); + return archive->DeleteDirectory(path); } ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, @@ -368,10 +359,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; - if (archive->DeleteDirectoryRecursively(path)) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::Canceled, ErrorLevel::Status); + return archive->DeleteDirectoryRecursively(path); } ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, @@ -388,10 +376,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; - if (archive->CreateDirectory(path)) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::Canceled, ErrorLevel::Status); + return archive->CreateDirectory(path); } ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, @@ -404,17 +389,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, return ERR_INVALID_ARCHIVE_HANDLE; if (src_archive == dest_archive) { - if (src_archive->RenameDirectory(src_path, dest_path)) - return RESULT_SUCCESS; + return src_archive->RenameDirectory(src_path, dest_path); } else { // TODO: Implement renaming across archives return UnimplementedFunction(ErrorModule::FS); } - - // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't - // exist or similar. Verify. - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::NothingHappened, ErrorLevel::Status); } ResultVal> OpenDirectoryFromArchive(ArchiveHandle archive_handle, @@ -423,13 +402,11 @@ ResultVal> OpenDirectoryFromArchive(ArchiveHandle a if (archive == nullptr) return ERR_INVALID_ARCHIVE_HANDLE; - std::unique_ptr backend = archive->OpenDirectory(path); - if (backend == nullptr) { - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, - ErrorLevel::Permanent); - } + auto backend = archive->OpenDirectory(path); + if (backend.Failed()) + return backend.Code(); - auto directory = Kernel::SharedPtr(new Directory(std::move(backend), path)); + auto directory = Kernel::SharedPtr(new Directory(backend.MoveFrom(), path)); return MakeResult>(std::move(directory)); } From 75ee2f8c67022b0731e438b34fa65216531eccd1 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 16 Oct 2016 22:19:13 +0800 Subject: [PATCH 02/13] FileSys: add PathParser --- src/core/CMakeLists.txt | 2 + src/core/file_sys/path_parser.cpp | 98 +++++++++++++++++++++++++ src/core/file_sys/path_parser.h | 61 +++++++++++++++ src/tests/CMakeLists.txt | 1 + src/tests/core/file_sys/path_parser.cpp | 38 ++++++++++ 5 files changed, 200 insertions(+) create mode 100644 src/core/file_sys/path_parser.cpp create mode 100644 src/core/file_sys/path_parser.h create mode 100644 src/tests/core/file_sys/path_parser.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4a9c6fd2f..ae9e8dcea 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -24,6 +24,7 @@ set(SRCS file_sys/archive_systemsavedata.cpp file_sys/disk_archive.cpp file_sys/ivfc_archive.cpp + file_sys/path_parser.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/hle.cpp @@ -168,6 +169,7 @@ set(HEADERS file_sys/disk_archive.h file_sys/file_backend.h file_sys/ivfc_archive.h + file_sys/path_parser.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h diff --git a/src/core/file_sys/path_parser.cpp b/src/core/file_sys/path_parser.cpp new file mode 100644 index 000000000..5a89b02b8 --- /dev/null +++ b/src/core/file_sys/path_parser.cpp @@ -0,0 +1,98 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/file_sys/path_parser.h" + +namespace FileSys { + +PathParser::PathParser(const Path& path) { + if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) { + is_valid = false; + return; + } + + auto path_string = path.AsString(); + if (path_string.size() == 0 || path_string[0] != '/') { + is_valid = false; + return; + } + + // Filter out invalid characters for the host system. + // Although some of these characters are valid on 3DS, they are unlikely to be used by games. + if (std::find_if(path_string.begin(), path_string.end(), [](char c) { + static const std::set invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'}; + return invalid_chars.find(c) != invalid_chars.end(); + }) != path_string.end()) { + is_valid = false; + return; + } + + Common::SplitString(path_string, '/', path_sequence); + + auto begin = path_sequence.begin(); + auto end = path_sequence.end(); + end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; }); + path_sequence = std::vector(begin, end); + + // checks if the path is out of bounds. + int level = 0; + for (auto& node : path_sequence) { + if (node == "..") { + --level; + if (level < 0) { + is_valid = false; + return; + } + } else { + ++level; + } + } + + is_valid = true; + is_root = level == 0; +} + +PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const { + auto path = mount_point; + if (!FileUtil::IsDirectory(path)) + return InvalidMountPoint; + if (path_sequence.empty()) { + return DirectoryFound; + } + + for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) { + if (path.back() != '/') + path += '/'; + path += *iter; + + if (!FileUtil::Exists(path)) + return PathNotFound; + if (FileUtil::IsDirectory(path)) + continue; + return FileInPath; + } + + path += "/" + path_sequence.back(); + if (!FileUtil::Exists(path)) + return NotFound; + if (FileUtil::IsDirectory(path)) + return DirectoryFound; + return FileFound; +} + +std::string PathParser::BuildHostPath(const std::string& mount_point) const { + std::string path = mount_point; + for (auto& node : path_sequence) { + if (path.back() != '/') + path += '/'; + path += node; + } + return path; +} + +} // namespace FileSys diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h new file mode 100644 index 000000000..990802579 --- /dev/null +++ b/src/core/file_sys/path_parser.h @@ -0,0 +1,61 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "core/file_sys/archive_backend.h" + +namespace FileSys { + +/** + * A helper class parsing and verifying a string-type Path. + * Every archives with a sub file system should use this class to parse the path argument and check + * the status of the file / directory in question on the host file system. + */ +class PathParser { +public: + PathParser(const Path& path); + + /** + * Checks if the Path is valid. + * This function should be called once a PathParser is constructed. + * A Path is valid if: + * - it is a string path (with type LowPathType::Char or LowPathType::Wchar), + * - it starts with "/" (this seems a hard requirement in real 3DS), + * - it doesn't contain invalid characters, and + * - it doesn't go out of the root directory using "..". + */ + bool IsValid() const { + return is_valid; + } + + /// Checks if the Path represents the root directory. + bool IsRootDirectory() const { + return is_root; + } + + enum HostStatus { + InvalidMountPoint, + PathNotFound, // "/a/b/c" when "a" doesn't exist + FileInPath, // "/a/b/c" when "a" is a file + FileFound, // "/a/b/c" when "c" is a file + DirectoryFound, // "/a/b/c" when "c" is a directory + NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist + }; + + /// Checks the status of the specified file / directory by the Path on the host file system. + HostStatus GetHostStatus(const std::string& mount_point) const; + + /// Builds a full path on the host file system. + std::string BuildHostPath(const std::string& mount_point) const; + +private: + std::vector path_sequence; + bool is_valid{}; + bool is_root{}; +}; + +} // namespace FileSys diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 457c55571..47799e1c2 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,5 +1,6 @@ set(SRCS tests.cpp + core/file_sys/path_parser.cpp ) set(HEADERS diff --git a/src/tests/core/file_sys/path_parser.cpp b/src/tests/core/file_sys/path_parser.cpp new file mode 100644 index 000000000..2b543e438 --- /dev/null +++ b/src/tests/core/file_sys/path_parser.cpp @@ -0,0 +1,38 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/file_util.h" +#include "core/file_sys/path_parser.h" + +namespace FileSys { + +TEST_CASE("PathParser", "[core][file_sys]") { + REQUIRE(!PathParser(Path(std::vector{})).IsValid()); + REQUIRE(!PathParser(Path("a")).IsValid()); + REQUIRE(!PathParser(Path("/|")).IsValid()); + REQUIRE(PathParser(Path("/a")).IsValid()); + REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid()); + REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid()); + REQUIRE(PathParser(Path("/")).IsRootDirectory()); + REQUIRE(!PathParser(Path("/a")).IsRootDirectory()); + REQUIRE(PathParser(Path("/a/..")).IsRootDirectory()); +} + +TEST_CASE("PathParser - Host file system", "[core][file_sys]") { + std::string test_dir = "./test"; + FileUtil::CreateDir(test_dir); + FileUtil::CreateDir(test_dir + "/z"); + FileUtil::CreateEmptyFile(test_dir + "/a"); + + REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound); + REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound); + REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound); + REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath); + REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound); + + FileUtil::DeleteDirRecursively(test_dir); +} + +} // namespace FileSys From 0e754875d175bfec393a3f4c4f6ad73f45ed361f Mon Sep 17 00:00:00 2001 From: wwylele Date: Sun, 16 Oct 2016 23:09:36 +0800 Subject: [PATCH 03/13] FileSys: remove Open from DirectoryBackend Open should not be an interface exposed by Directory because it is the Archive thats implement the methed to open the directory. The service API of 3DS also implies this - Open is not a function of directory service, but is of FS main service --- src/core/file_sys/directory_backend.h | 6 ------ src/core/file_sys/disk_archive.cpp | 17 ++++------------- src/core/file_sys/disk_archive.h | 4 +--- src/core/file_sys/ivfc_archive.h | 3 --- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h index b55e382ef..0c93f2074 100644 --- a/src/core/file_sys/directory_backend.h +++ b/src/core/file_sys/directory_backend.h @@ -40,12 +40,6 @@ public: DirectoryBackend() {} virtual ~DirectoryBackend() {} - /** - * Open the directory - * @return true if the directory opened correctly - */ - virtual bool Open() = 0; - /** * List files contained in the directory * @param count Number of entries to return at once in entries diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index ce6b9360b..fef6e68a2 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -112,10 +112,11 @@ ResultCode DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_p ResultVal> DiskArchive::OpenDirectory(const Path& path) const { LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); - auto directory = std::make_unique(*this, path); - if (!directory->Open()) + auto full_path = mount_point + path.AsString(); + if (!FileUtil::IsDirectory(full_path)) return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, ErrorLevel::Permanent); + auto directory = std::make_unique(full_path); return MakeResult>(std::move(directory)); } @@ -211,21 +212,11 @@ bool DiskFile::Close() const { //////////////////////////////////////////////////////////////////////////////////////////////////// -DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() { - // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass - // the root directory we set while opening the archive. - // For example, opening /../../usr/bin can give the emulated program your installed programs. - this->path = archive.mount_point + path.AsString(); -} - -bool DiskDirectory::Open() { - if (!FileUtil::IsDirectory(path)) - return false; +DiskDirectory::DiskDirectory(const std::string& path) : directory() { unsigned size = FileUtil::ScanDirectoryTree(path, directory); directory.size = size; directory.isDirectory = true; children_iterator = directory.children.begin(); - return true; } u32 DiskDirectory::Read(const u32 count, Entry* entries) { diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index 0edd87954..c93c32eb2 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -75,13 +75,12 @@ protected: class DiskDirectory : public DirectoryBackend { public: - DiskDirectory(const DiskArchive& archive, const Path& path); + DiskDirectory(const std::string& path); ~DiskDirectory() override { Close(); } - bool Open() override; u32 Read(const u32 count, Entry* entries) override; bool Close() const override { @@ -89,7 +88,6 @@ public: } protected: - std::string path; u32 total_entries_in_directory; FileUtil::FSTEntry directory; diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h index af6297e1f..826261441 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/ivfc_archive.h @@ -75,9 +75,6 @@ private: class IVFCDirectory : public DirectoryBackend { public: - bool Open() override { - return false; - } u32 Read(const u32 count, Entry* entries) override { return 0; } From 9a0405858a5602a5e1e112b131ecdd8ca27e6d10 Mon Sep 17 00:00:00 2001 From: wwylele Date: Mon, 17 Oct 2016 10:10:23 +0800 Subject: [PATCH 04/13] FileSys: remove Open from FileBackend Same as directory, file shouldn't expose Open either. --- src/core/file_sys/disk_archive.cpp | 92 +++++++++++++----------------- src/core/file_sys/disk_archive.h | 7 ++- src/core/file_sys/file_backend.h | 6 -- src/core/file_sys/ivfc_archive.h | 3 - 4 files changed, 44 insertions(+), 64 deletions(-) diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index fef6e68a2..43d199434 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -18,11 +18,46 @@ namespace FileSys { ResultVal> DiskArchive::OpenFile(const Path& path, const Mode& mode) const { LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); - auto file = std::make_unique(*this, path, mode); - ResultCode result = file->Open(); - if (result.IsError()) - return result; - return MakeResult>(std::move(file)); + + auto full_path = mount_point + path.AsString(); + if (FileUtil::IsDirectory(full_path)) + return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, + ErrorLevel::Status); + + // Specifying only the Create flag is invalid + if (mode.create_flag && !mode.read_flag && !mode.write_flag) { + return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, + ErrorSummary::Canceled, ErrorLevel::Status); + } + + if (!FileUtil::Exists(full_path)) { + if (!mode.create_flag) { + LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", + full_path.c_str()); + return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status); + } else { + // Create the file + FileUtil::CreateEmptyFile(full_path); + } + } + + std::string mode_string = ""; + if (mode.write_flag) + mode_string += "r+"; // Files opened with Write access can be read from + else if (mode.read_flag) + mode_string += "r"; + + // Open the file in binary mode, to avoid problems with CR/LF on Windows systems + mode_string += "b"; + + FileUtil::IOFile file(full_path, mode_string.c_str()); + if (!file.IsOpen()) + return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, + ErrorLevel::Status); + + auto disk_file = std::make_unique(std::move(file), mode); + return MakeResult>(std::move(disk_file)); } ResultCode DiskArchive::DeleteFile(const Path& path) const { @@ -127,53 +162,6 @@ u64 DiskArchive::GetFreeBytes() const { //////////////////////////////////////////////////////////////////////////////////////////////////// -DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) { - // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass - // the root directory we set while opening the archive. - // For example, opening /../../etc/passwd can give the emulated program your users list. - this->path = archive.mount_point + path.AsString(); - this->mode.hex = mode.hex; -} - -ResultCode DiskFile::Open() { - if (FileUtil::IsDirectory(path)) - return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, - ErrorLevel::Status); - - // Specifying only the Create flag is invalid - if (mode.create_flag && !mode.read_flag && !mode.write_flag) { - return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, - ErrorSummary::Canceled, ErrorLevel::Status); - } - - if (!FileUtil::Exists(path)) { - if (!mode.create_flag) { - LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", - path.c_str()); - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, - ErrorSummary::NotFound, ErrorLevel::Status); - } else { - // Create the file - FileUtil::CreateEmptyFile(path); - } - } - - std::string mode_string = ""; - if (mode.write_flag) - mode_string += "r+"; // Files opened with Write access can be read from - else if (mode.read_flag) - mode_string += "r"; - - // Open the file in binary mode, to avoid problems with CR/LF on Windows systems - mode_string += "b"; - - file = std::make_unique(path, mode_string.c_str()); - if (file->IsOpen()) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, - ErrorLevel::Status); -} - ResultVal DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { if (!mode.read_flag && !mode.write_flag) return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index c93c32eb2..c2c3c3b23 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -54,9 +54,11 @@ protected: class DiskFile : public FileBackend { public: - DiskFile(const DiskArchive& archive, const Path& path, const Mode mode); + DiskFile(FileUtil::IOFile&& file_, const Mode& mode_) + : file(new FileUtil::IOFile(std::move(file_))) { + mode.hex = mode_.hex; + } - ResultCode Open() override; ResultVal Read(u64 offset, size_t length, u8* buffer) const override; ResultVal Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; u64 GetSize() const override; @@ -68,7 +70,6 @@ public: } protected: - std::string path; Mode mode; std::unique_ptr file; }; diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h index ed997537f..5e7c2bab4 100644 --- a/src/core/file_sys/file_backend.h +++ b/src/core/file_sys/file_backend.h @@ -18,12 +18,6 @@ public: FileBackend() {} virtual ~FileBackend() {} - /** - * Open the file - * @return Result of the file operation - */ - virtual ResultCode Open() = 0; - /** * Read data from the file * @param offset Offset in bytes to start reading data from diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h index 826261441..e6fbdfb1f 100644 --- a/src/core/file_sys/ivfc_archive.h +++ b/src/core/file_sys/ivfc_archive.h @@ -55,9 +55,6 @@ public: IVFCFile(std::shared_ptr file, u64 offset, u64 size) : romfs_file(file), data_offset(offset), data_size(size) {} - ResultCode Open() override { - return RESULT_SUCCESS; - } ResultVal Read(u64 offset, size_t length, u8* buffer) const override; ResultVal Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; u64 GetSize() const override; From 7166fdc49072d987d04e681de4d9e1558ba75c63 Mon Sep 17 00:00:00 2001 From: wwylele Date: Mon, 17 Oct 2016 14:54:48 +0800 Subject: [PATCH 05/13] FileSys: add SaveDataArchive The error checking of SaveDataArchive is completely different from DiskArchive, so it has to be a new class instead of a subclass of DiskArchive. --- src/core/CMakeLists.txt | 2 + src/core/file_sys/archive_savedata.cpp | 4 +- src/core/file_sys/archive_systemsavedata.cpp | 4 +- src/core/file_sys/errors.h | 29 ++ src/core/file_sys/savedata_archive.cpp | 283 +++++++++++++++++++ src/core/file_sys/savedata_archive.h | 43 +++ src/core/hle/result.h | 7 + 7 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 src/core/file_sys/errors.h create mode 100644 src/core/file_sys/savedata_archive.cpp create mode 100644 src/core/file_sys/savedata_archive.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ae9e8dcea..63a402ad0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -25,6 +25,7 @@ set(SRCS file_sys/disk_archive.cpp file_sys/ivfc_archive.cpp file_sys/path_parser.cpp + file_sys/savedata_archive.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/hle.cpp @@ -170,6 +171,7 @@ set(HEADERS file_sys/file_backend.h file_sys/ivfc_archive.h file_sys/path_parser.h + file_sys/savedata_archive.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp index 6711035ec..ecb44a215 100644 --- a/src/core/file_sys/archive_savedata.cpp +++ b/src/core/file_sys/archive_savedata.cpp @@ -9,7 +9,7 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "core/file_sys/archive_savedata.h" -#include "core/file_sys/disk_archive.h" +#include "core/file_sys/savedata_archive.h" #include "core/hle/kernel/process.h" #include "core/hle/service/fs/archive.h" @@ -54,7 +54,7 @@ ResultVal> ArchiveFactory_SaveData::Open(const P ErrorSummary::InvalidState, ErrorLevel::Status); } - auto archive = std::make_unique(std::move(concrete_mount_point)); + auto archive = std::make_unique(std::move(concrete_mount_point)); return MakeResult>(std::move(archive)); } diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index 48ebc0ed4..54e7793e0 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp @@ -9,7 +9,7 @@ #include "common/file_util.h" #include "common/string_util.h" #include "core/file_sys/archive_systemsavedata.h" -#include "core/file_sys/disk_archive.h" +#include "core/file_sys/savedata_archive.h" #include "core/hle/service/fs/archive.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -56,7 +56,7 @@ ResultVal> ArchiveFactory_SystemSaveData::Open(c return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, ErrorSummary::InvalidState, ErrorLevel::Status); } - auto archive = std::make_unique(fullpath); + auto archive = std::make_unique(fullpath); return MakeResult>(std::move(archive)); } diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h new file mode 100644 index 000000000..da7e82642 --- /dev/null +++ b/src/core/file_sys/errors.h @@ -0,0 +1,29 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/result.h" + +namespace FileSys { + +const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); +const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags, + ErrorModule::FS, ErrorSummary::NotSupported, + ErrorLevel::Usage); +const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status); +const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status); +const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory, + ErrorModule::FS, ErrorSummary::NotSupported, + ErrorLevel::Usage); +const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists, + ErrorModule::FS, ErrorSummary::NothingHappened, + ErrorLevel::Status); +const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS, + ErrorSummary::NothingHappened, ErrorLevel::Status); +const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS, + ErrorSummary::Canceled, ErrorLevel::Status); + +} // namespace FileSys diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp new file mode 100644 index 000000000..f2e6a06bc --- /dev/null +++ b/src/core/file_sys/savedata_archive.cpp @@ -0,0 +1,283 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/file_util.h" +#include "core/file_sys/disk_archive.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/path_parser.h" +#include "core/file_sys/savedata_archive.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +ResultVal> SaveDataArchive::OpenFile(const Path& path, + const Mode& mode) const { + LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); + + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + if (mode.hex == 0) { + LOG_ERROR(Service_FS, "Empty open mode"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + if (mode.create_flag && !mode.write_flag) { + LOG_ERROR(Service_FS, "Create flag set but write flag not set"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_FILE_NOT_FOUND; + case PathParser::PathNotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + case PathParser::DirectoryFound: + LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::NotFound: + if (!mode.create_flag) { + LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", + full_path.c_str()); + return ERROR_FILE_NOT_FOUND; + } else { + // Create the file + FileUtil::CreateEmptyFile(full_path); + } + break; + } + + FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); + if (!file.IsOpen()) { + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); + return ERROR_FILE_NOT_FOUND; + } + + auto disk_file = std::make_unique(std::move(file), mode); + return MakeResult>(std::move(disk_file)); +} + +ResultCode SaveDataArchive::DeleteFile(const Path& path) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_FILE_NOT_FOUND; + case PathParser::PathNotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + case PathParser::DirectoryFound: + case PathParser::NotFound: + LOG_ERROR(Service_FS, "File not found %s", full_path.c_str()); + return ERROR_FILE_NOT_FOUND; + } + + if (FileUtil::Delete(full_path)) { + return RESULT_SUCCESS; + } + + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); + return ERROR_FILE_NOT_FOUND; +} + +ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const { + if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { + return RESULT_SUCCESS; + } + + // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't + // exist or similar. Verify. + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::NothingHappened, ErrorLevel::Status); +} + +template +static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, + T deleter) { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + if (path_parser.IsRootDirectory()) + return ERROR_DIRECTORY_NOT_EMPTY; + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::NotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + } + + if (deleter(full_path)) { + return RESULT_SUCCESS; + } + + LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); + return ERROR_DIRECTORY_NOT_EMPTY; +} + +ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const { + return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); +} + +ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const { + return DeleteDirectoryHelper( + path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); +} + +ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_FILE_NOT_FOUND; + case PathParser::PathNotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::DirectoryFound: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); + return ERROR_FILE_ALREADY_EXISTS; + } + + if (size == 0) { + FileUtil::CreateEmptyFile(full_path); + return RESULT_SUCCESS; + } + + FileUtil::IOFile file(full_path, "wb"); + // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) + // We do this by seeking to the right size, then writing a single null byte. + if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { + return RESULT_SUCCESS; + } + + LOG_ERROR(Service_FS, "Too large file"); + return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, + ErrorLevel::Info); +} + +ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_FILE_NOT_FOUND; + case PathParser::PathNotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::DirectoryFound: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); + return ERROR_DIRECTORY_ALREADY_EXISTS; + } + + if (FileUtil::CreateDir(mount_point + path.AsString())) { + return RESULT_SUCCESS; + } + + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled, + ErrorLevel::Status); +} + +ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { + if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) + return RESULT_SUCCESS; + + // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't + // exist or similar. Verify. + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::NothingHappened, ErrorLevel::Status); +} + +ResultVal> SaveDataArchive::OpenDirectory( + const Path& path) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_FILE_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::NotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + } + + auto directory = std::make_unique(full_path); + return MakeResult>(std::move(directory)); +} + +u64 SaveDataArchive::GetFreeBytes() const { + // TODO: Stubbed to return 1GiB + return 1024 * 1024 * 1024; +} + +} // namespace FileSys diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h new file mode 100644 index 000000000..2fb6c452a --- /dev/null +++ b/src/core/file_sys/savedata_archive.h @@ -0,0 +1,43 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/file_sys/archive_backend.h" +#include "core/file_sys/directory_backend.h" +#include "core/file_sys/file_backend.h" +#include "core/hle/result.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +/// Archive backend for general save data archive type (SaveData and SystemSaveData) +class SaveDataArchive : public ArchiveBackend { +public: + SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {} + + std::string GetName() const override { + return "SaveDataArchive: " + mount_point; + } + + ResultVal> OpenFile(const Path& path, + const Mode& mode) const override; + ResultCode DeleteFile(const Path& path) const override; + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; + ResultCode DeleteDirectory(const Path& path) const override; + ResultCode DeleteDirectoryRecursively(const Path& path) const override; + ResultCode CreateFile(const Path& path, u64 size) const override; + ResultCode CreateDirectory(const Path& path) const override; + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; + ResultVal> OpenDirectory(const Path& path) const override; + u64 GetFreeBytes() const override; + +protected: + std::string mount_point; +}; + +} // namespace FileSys diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 7f8d8e00d..8330894f2 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -20,15 +20,22 @@ enum class ErrorDescription : u32 { OS_InvalidBufferDescriptor = 48, WrongAddress = 53, FS_ArchiveNotMounted = 101, + FS_FileNotFound = 112, + FS_PathNotFound = 113, FS_NotFound = 120, + FS_FileAlreadyExists = 180, + FS_DirectoryAlreadyExists = 185, FS_AlreadyExists = 190, FS_InvalidOpenFlags = 230, + FS_DirectoryNotEmpty = 240, FS_NotAFile = 250, FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage GPU_FirstInitialization = 519, FS_InvalidPath = 702, + FS_UnsupportedOpenFlags = 760, + FS_UnexpectedFileOrDirectory = 770, InvalidSection = 1000, TooLarge = 1001, NotAuthorized = 1002, From a879984c06baf6c4185e376dd47258bfc108dec5 Mon Sep 17 00:00:00 2001 From: wwylele Date: Mon, 17 Oct 2016 20:23:34 +0800 Subject: [PATCH 06/13] FileSys: add ExtSaveDataArchive ExtSaveData is more similar to SaveData, so let it be a subclass of SaveData --- src/core/file_sys/archive_extsavedata.cpp | 115 +++++++++++++++++++++- src/core/hle/result.h | 1 + 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index e1d29efd3..e1c4931ec 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -11,6 +11,9 @@ #include "common/string_util.h" #include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/disk_archive.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/path_parser.h" +#include "core/file_sys/savedata_archive.h" #include "core/hle/service/fs/archive.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -18,6 +21,116 @@ namespace FileSys { +/** + * A modified version of DiskFile for fixed-size file used by ExtSaveData + * The file size can't be changed by SetSize or Write. + */ +class FixSizeDiskFile : public DiskFile { +public: + FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) { + size = GetSize(); + } + + bool SetSize(u64 size) const override { + return false; + } + + ResultVal Write(u64 offset, size_t length, bool flush, + const u8* buffer) const override { + if (offset > size) { + return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + } else if (offset == size) { + return MakeResult(0); + } + + if (offset + length > size) { + length = size - offset; + } + + return DiskFile::Write(offset, length, flush, buffer); + } + +private: + u64 size{}; +}; + +/** + * Archive backend for general extsave data archive type. + * The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for + * - file size can't be changed once created (thus creating zero-size file and openning with create + * flag are prohibited); + * - always open a file with read+write permission. + */ +class ExtSaveDataArchive : public SaveDataArchive { +public: + ExtSaveDataArchive(const std::string& mount_point) : SaveDataArchive(mount_point) {} + + std::string GetName() const override { + return "ExtSaveDataArchive: " + mount_point; + } + + ResultVal> OpenFile(const Path& path, + const Mode& mode) const override { + LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); + + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + if (mode.hex == 0) { + LOG_ERROR(Service_FS, "Empty open mode"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + if (mode.create_flag) { + LOG_ERROR(Service_FS, "Create flag is not supported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_FILE_NOT_FOUND; + case PathParser::PathNotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_PATH_NOT_FOUND; + case PathParser::FileInPath: + case PathParser::DirectoryFound: + LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY; + case PathParser::NotFound: + LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); + return ERROR_FILE_NOT_FOUND; + } + + FileUtil::IOFile file(full_path, "r+b"); + if (!file.IsOpen()) { + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); + return ERROR_FILE_NOT_FOUND; + } + + Mode rwmode; + rwmode.write_flag.Assign(1); + rwmode.read_flag.Assign(1); + auto disk_file = std::make_unique(std::move(file), rwmode); + return MakeResult>(std::move(disk_file)); + } + + ResultCode CreateFile(const Path& path, u64 size) const override { + if (size == 0) { + LOG_ERROR(Service_FS, "Zero-size file is not supported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; + } + return SaveDataArchive::CreateFile(path, size); + } +}; + std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) { std::vector vec_data = path.AsBinary(); const u32* data = reinterpret_cast(vec_data.data()); @@ -84,7 +197,7 @@ ResultVal> ArchiveFactory_ExtSaveData::Open(cons ErrorSummary::InvalidState, ErrorLevel::Status); } } - auto archive = std::make_unique(fullpath); + auto archive = std::make_unique(fullpath); return MakeResult>(std::move(archive)); } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 8330894f2..a355f970a 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -34,6 +34,7 @@ enum class ErrorDescription : u32 { 513, // TODO(purpasmart): Check if this name fits its actual usage GPU_FirstInitialization = 519, FS_InvalidPath = 702, + FS_WriteBeyondEnd = 705, FS_UnsupportedOpenFlags = 760, FS_UnexpectedFileOrDirectory = 770, InvalidSection = 1000, From 93aa14e34572da0285de72b4da4f0fe8756d1c5f Mon Sep 17 00:00:00 2001 From: wwylele Date: Mon, 17 Oct 2016 23:00:50 +0800 Subject: [PATCH 07/13] FileSys: add SDMCArchive Now DiskArchive only serves for SDMC, then it should be just a "SDMCArchive" --- src/core/file_sys/archive_sdmc.cpp | 268 ++++++++++++++++++++++++++++- src/core/file_sys/archive_sdmc.h | 25 +++ src/core/file_sys/errors.h | 9 + 3 files changed, 301 insertions(+), 1 deletion(-) diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index bcb03ed36..c747ba82c 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -8,6 +8,8 @@ #include "common/logging/log.h" #include "core/file_sys/archive_sdmc.h" #include "core/file_sys/disk_archive.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/path_parser.h" #include "core/settings.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -15,6 +17,270 @@ namespace FileSys { +ResultVal> SDMCArchive::OpenFile(const Path& path, + const Mode& mode) const { + LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); + + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + if (mode.hex == 0) { + LOG_ERROR(Service_FS, "Empty open mode"); + return ERROR_INVALID_OPEN_FLAGS; + } + + if (mode.create_flag && !mode.write_flag) { + LOG_ERROR(Service_FS, "Create flag set but write flag not set"); + return ERROR_INVALID_OPEN_FLAGS; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::FileInPath: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_NOT_FOUND; + case PathParser::DirectoryFound: + LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::NotFound: + if (!mode.create_flag) { + LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", + full_path.c_str()); + return ERROR_NOT_FOUND; + } else { + // Create the file + FileUtil::CreateEmptyFile(full_path); + } + break; + } + + FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); + if (!file.IsOpen()) { + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str()); + return ERROR_NOT_FOUND; + } + + auto disk_file = std::make_unique(std::move(file), mode); + return MakeResult>(std::move(disk_file)); +} + +ResultCode SDMCArchive::DeleteFile(const Path& path) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::FileInPath: + case PathParser::NotFound: + LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); + return ERROR_NOT_FOUND; + case PathParser::DirectoryFound: + LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + } + + if (FileUtil::Delete(full_path)) { + return RESULT_SUCCESS; + } + + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str()); + return ERROR_NOT_FOUND; +} + +ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { + if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { + return RESULT_SUCCESS; + } + + // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't + // exist or similar. Verify. + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::NothingHappened, ErrorLevel::Status); +} + +template +static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point, + T deleter) { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + if (path_parser.IsRootDirectory()) + return ERROR_NOT_FOUND; + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::NotFound: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_NOT_FOUND; + case PathParser::FileInPath: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + } + + if (deleter(full_path)) { + return RESULT_SUCCESS; + } + + LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; +} + +ResultCode SDMCArchive::DeleteDirectory(const Path& path) const { + return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir); +} + +ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const { + return DeleteDirectoryHelper( + path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); }); +} + +ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::FileInPath: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_NOT_FOUND; + case PathParser::DirectoryFound: + LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + case PathParser::FileFound: + LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); + return ERROR_ALREADY_EXISTS; + } + + if (size == 0) { + FileUtil::CreateEmptyFile(full_path); + return RESULT_SUCCESS; + } + + FileUtil::IOFile file(full_path, "wb"); + // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) + // We do this by seeking to the right size, then writing a single null byte. + if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) { + return RESULT_SUCCESS; + } + + LOG_ERROR(Service_FS, "Too large file"); + return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, + ErrorLevel::Info); +} + +ResultCode SDMCArchive::CreateDirectory(const Path& path) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::FileInPath: + LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str()); + return ERROR_NOT_FOUND; + case PathParser::DirectoryFound: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "%s already exists", full_path.c_str()); + return ERROR_ALREADY_EXISTS; + } + + if (FileUtil::CreateDir(mount_point + path.AsString())) { + return RESULT_SUCCESS; + } + + LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str()); + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled, + ErrorLevel::Status); +} + +ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { + if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) + return RESULT_SUCCESS; + + // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't + // exist or similar. Verify. + return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description + ErrorSummary::NothingHappened, ErrorLevel::Status); +} + +ResultVal> SDMCArchive::OpenDirectory(const Path& path) const { + const PathParser path_parser(path); + + if (!path_parser.IsValid()) { + LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto full_path = path_parser.BuildHostPath(mount_point); + + switch (path_parser.GetHostStatus(mount_point)) { + case PathParser::InvalidMountPoint: + LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str()); + return ERROR_NOT_FOUND; + case PathParser::PathNotFound: + case PathParser::NotFound: + case PathParser::FileFound: + LOG_ERROR(Service_FS, "%s not found", full_path.c_str()); + return ERROR_NOT_FOUND; + case PathParser::FileInPath: + LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str()); + return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC; + } + + auto directory = std::make_unique(full_path); + return MakeResult>(std::move(directory)); +} + +u64 SDMCArchive::GetFreeBytes() const { + // TODO: Stubbed to return 1GiB + return 1024 * 1024 * 1024; +} + ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) : sdmc_directory(sdmc_directory) { LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); @@ -35,7 +301,7 @@ bool ArchiveFactory_SDMC::Initialize() { } ResultVal> ArchiveFactory_SDMC::Open(const Path& path) { - auto archive = std::make_unique(sdmc_directory); + auto archive = std::make_unique(sdmc_directory); return MakeResult>(std::move(archive)); } diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h index 88e855351..4fa47ff35 100644 --- a/src/core/file_sys/archive_sdmc.h +++ b/src/core/file_sys/archive_sdmc.h @@ -14,6 +14,31 @@ namespace FileSys { +/// Archive backend for SDMC archive +class SDMCArchive : public ArchiveBackend { +public: + SDMCArchive(const std::string& mount_point_) : mount_point(mount_point_) {} + + std::string GetName() const override { + return "SDMCArchive: " + mount_point; + } + + ResultVal> OpenFile(const Path& path, + const Mode& mode) const override; + ResultCode DeleteFile(const Path& path) const override; + ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; + ResultCode DeleteDirectory(const Path& path) const override; + ResultCode DeleteDirectoryRecursively(const Path& path) const override; + ResultCode CreateFile(const Path& path, u64 size) const override; + ResultCode CreateDirectory(const Path& path) const override; + ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; + ResultVal> OpenDirectory(const Path& path) const override; + u64 GetFreeBytes() const override; + +protected: + std::string mount_point; +}; + /// File system interface to the SDMC archive class ArchiveFactory_SDMC final : public ArchiveFactory { public: diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index da7e82642..f9299364c 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -11,18 +11,27 @@ const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModul const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags, ErrorModule::FS, ErrorSummary::NotSupported, ErrorLevel::Usage); +const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, + ErrorSummary::Canceled, ErrorLevel::Status); const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS, ErrorSummary::NotFound, ErrorLevel::Status); const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS, ErrorSummary::NotFound, ErrorLevel::Status); +const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status); const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory, ErrorModule::FS, ErrorSummary::NotSupported, ErrorLevel::Usage); +const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile, + ErrorModule::FS, ErrorSummary::Canceled, + ErrorLevel::Status); const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists, ErrorModule::FS, ErrorSummary::NothingHappened, ErrorLevel::Status); const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS, ErrorSummary::NothingHappened, ErrorLevel::Status); +const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS, + ErrorSummary::NothingHappened, ErrorLevel::Status); const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS, ErrorSummary::Canceled, ErrorLevel::Status); From 098778369962f8ec5eba6ccc91b2846c72cb0005 Mon Sep 17 00:00:00 2001 From: wwylele Date: Tue, 18 Oct 2016 17:27:51 +0800 Subject: [PATCH 08/13] FileSys: add SDMCWriteOnlyArchive --- src/core/CMakeLists.txt | 2 + src/core/file_sys/archive_sdmcwriteonly.cpp | 70 +++++++++++++++++++++ src/core/file_sys/archive_sdmcwriteonly.h | 57 +++++++++++++++++ src/core/file_sys/errors.h | 2 + src/core/hle/result.h | 1 + src/core/hle/service/fs/archive.cpp | 8 +++ 6 files changed, 140 insertions(+) create mode 100644 src/core/file_sys/archive_sdmcwriteonly.cpp create mode 100644 src/core/file_sys/archive_sdmcwriteonly.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 63a402ad0..1ecd1c431 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -21,6 +21,7 @@ set(SRCS file_sys/archive_savedata.cpp file_sys/archive_savedatacheck.cpp file_sys/archive_sdmc.cpp + file_sys/archive_sdmcwriteonly.cpp file_sys/archive_systemsavedata.cpp file_sys/disk_archive.cpp file_sys/ivfc_archive.cpp @@ -165,6 +166,7 @@ set(HEADERS file_sys/archive_savedata.h file_sys/archive_savedatacheck.h file_sys/archive_sdmc.h + file_sys/archive_sdmcwriteonly.h file_sys/archive_systemsavedata.h file_sys/directory_backend.h file_sys/disk_archive.h diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp new file mode 100644 index 000000000..64ae49b86 --- /dev/null +++ b/src/core/file_sys/archive_sdmcwriteonly.cpp @@ -0,0 +1,70 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/file_util.h" +#include "core/file_sys/archive_sdmcwriteonly.h" +#include "core/file_sys/directory_backend.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/file_backend.h" +#include "core/settings.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +ResultVal> SDMCWriteOnlyArchive::OpenFile(const Path& path, + const Mode& mode) const { + if (mode.read_flag) { + LOG_ERROR(Service_FS, "Read flag is not supported"); + return ERROR_INVALID_READ_FLAG; + } + return SDMCArchive::OpenFile(path, mode); +} + +ResultVal> SDMCWriteOnlyArchive::OpenDirectory( + const Path& path) const { + LOG_ERROR(Service_FS, "Not supported"); + return ERROR_UNSUPPORTED_OPEN_FLAGS; +} + +ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point) + : sdmc_directory(mount_point) { + LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str()); +} + +bool ArchiveFactory_SDMCWriteOnly::Initialize() { + if (!Settings::values.use_virtual_sd) { + LOG_WARNING(Service_FS, "SDMC disabled by config."); + return false; + } + + if (!FileUtil::CreateFullPath(sdmc_directory)) { + LOG_ERROR(Service_FS, "Unable to create SDMC path."); + return false; + } + + return true; +} + +ResultVal> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) { + auto archive = std::make_unique(sdmc_directory); + return MakeResult>(std::move(archive)); +} + +ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path, + const FileSys::ArchiveFormatInfo& format_info) { + // TODO(wwylele): hwtest this + LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive."); + return ResultCode(-1); +} + +ResultVal ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const { + // TODO(Subv): Implement + LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); + return ResultCode(-1); +} + +} // namespace FileSys diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h new file mode 100644 index 000000000..ed977485a --- /dev/null +++ b/src/core/file_sys/archive_sdmcwriteonly.h @@ -0,0 +1,57 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/archive_sdmc.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +/** + * Archive backend for SDMC write-only archive. + * The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for + * - OpenDirectory is unsupported; + * - OpenFile with read flag is unsupported. + */ +class SDMCWriteOnlyArchive : public SDMCArchive { +public: + SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {} + + std::string GetName() const override { + return "SDMCWriteOnlyArchive: " + mount_point; + } + + ResultVal> OpenFile(const Path& path, + const Mode& mode) const override; + + ResultVal> OpenDirectory(const Path& path) const override; +}; + +/// File system interface to the SDMC write-only archive +class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory { +public: + ArchiveFactory_SDMCWriteOnly(const std::string& mount_point); + + /** + * Initialize the archive. + * @return true if it initialized successfully + */ + bool Initialize(); + + std::string GetName() const override { + return "SDMCWriteOnly"; + } + + ResultVal> Open(const Path& path) override; + ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; + ResultVal GetFormatInfo(const Path& path) const override; + +private: + std::string sdmc_directory; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index f9299364c..fd1b07df0 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -13,6 +13,8 @@ const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOp ErrorLevel::Usage); const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, ErrorSummary::Canceled, ErrorLevel::Status); +const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS, ErrorSummary::NotFound, ErrorLevel::Status); const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS, diff --git a/src/core/hle/result.h b/src/core/hle/result.h index a355f970a..f7356f9d8 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -33,6 +33,7 @@ enum class ErrorDescription : u32 { OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage GPU_FirstInitialization = 519, + FS_InvalidReadFlag = 700, FS_InvalidPath = 702, FS_WriteBeyondEnd = 705, FS_UnsupportedOpenFlags = 760, diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 891d7bc84..62cf2c249 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -18,6 +18,7 @@ #include "core/file_sys/archive_savedata.h" #include "core/file_sys/archive_savedatacheck.h" #include "core/file_sys/archive_sdmc.h" +#include "core/file_sys/archive_sdmcwriteonly.h" #include "core/file_sys/archive_systemsavedata.h" #include "core/file_sys/directory_backend.h" #include "core/file_sys/file_backend.h" @@ -526,6 +527,13 @@ void RegisterArchiveTypes() { LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str()); + auto sdmcwo_factory = std::make_unique(sdmc_directory); + if (sdmcwo_factory->Initialize()) + RegisterArchiveType(std::move(sdmcwo_factory), ArchiveIdCode::SDMCWriteOnly); + else + LOG_ERROR(Service_FS, "Can't instantiate SDMCWriteOnly archive with path %s", + sdmc_directory.c_str()); + // Create the SaveData archive auto savedata_factory = std::make_unique(sdmc_directory); RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); From 0647f866498412f22999966cc97b122db2ef9318 Mon Sep 17 00:00:00 2001 From: wwylele Date: Thu, 20 Oct 2016 11:45:01 +0800 Subject: [PATCH 09/13] FileSys: w->rw permission lift only happens in SDMC archive --- src/core/file_sys/archive_sdmc.cpp | 11 +++++++++++ src/core/file_sys/archive_sdmc.h | 1 + src/core/file_sys/archive_sdmcwriteonly.cpp | 2 +- src/core/file_sys/disk_archive.cpp | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index c747ba82c..333dfb92e 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -19,6 +19,17 @@ namespace FileSys { ResultVal> SDMCArchive::OpenFile(const Path& path, const Mode& mode) const { + Mode modified_mode; + modified_mode.hex = mode.hex; + + // SDMC archive always opens a file with at least read permission + modified_mode.read_flag.Assign(1); + + return OpenFileBase(path, modified_mode); +} + +ResultVal> SDMCArchive::OpenFileBase(const Path& path, + const Mode& mode) const { LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); const PathParser path_parser(path); diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h index 4fa47ff35..9d99b110c 100644 --- a/src/core/file_sys/archive_sdmc.h +++ b/src/core/file_sys/archive_sdmc.h @@ -36,6 +36,7 @@ public: u64 GetFreeBytes() const override; protected: + ResultVal> OpenFileBase(const Path& path, const Mode& mode) const; std::string mount_point; }; diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp index 64ae49b86..2aafc9b1d 100644 --- a/src/core/file_sys/archive_sdmcwriteonly.cpp +++ b/src/core/file_sys/archive_sdmcwriteonly.cpp @@ -21,7 +21,7 @@ ResultVal> SDMCWriteOnlyArchive::OpenFile(const Pat LOG_ERROR(Service_FS, "Read flag is not supported"); return ERROR_INVALID_READ_FLAG; } - return SDMCArchive::OpenFile(path, mode); + return SDMCArchive::OpenFileBase(path, mode); } ResultVal> SDMCWriteOnlyArchive::OpenDirectory( diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index 43d199434..0e51fd1a6 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -163,7 +163,7 @@ u64 DiskArchive::GetFreeBytes() const { //////////////////////////////////////////////////////////////////////////////////////////////////// ResultVal DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { - if (!mode.read_flag && !mode.write_flag) + if (!mode.read_flag) return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, ErrorSummary::Canceled, ErrorLevel::Status); From 5c6e13a171cd5952732895bba645f375ae38d775 Mon Sep 17 00:00:00 2001 From: wwylele Date: Thu, 20 Oct 2016 09:43:43 +0800 Subject: [PATCH 10/13] PTM & CFG: use the correct path and error code according to the new FileSys policy --- src/core/hle/service/cfg/cfg.cpp | 9 +++++---- src/core/hle/service/ptm/ptm.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 24eee6903..849dab707 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -359,7 +359,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da } ResultCode DeleteConfigNANDSaveFile() { - FileSys::Path path("config"); + FileSys::Path path("/config"); return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); } @@ -368,7 +368,7 @@ ResultCode UpdateConfigNANDSavegame() { mode.write_flag.Assign(1); mode.create_flag.Assign(1); - FileSys::Path path("config"); + FileSys::Path path("/config"); auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); ASSERT_MSG(config_result.Succeeded(), "could not open file"); @@ -382,8 +382,9 @@ ResultCode UpdateConfigNANDSavegame() { ResultCode FormatConfig() { ResultCode res = DeleteConfigNANDSaveFile(); // The delete command fails if the file doesn't exist, so we have to check that too - if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound) + if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) { return res; + } // Delete the old data cfg_config_file_buffer.fill(0); // Create the header @@ -504,7 +505,7 @@ ResultCode LoadConfigNANDSaveFile() { cfg_system_save_data_archive = *archive_result; - FileSys::Path config_path("config"); + FileSys::Path config_path("/config"); FileSys::Mode open_mode = {}; open_mode.read_flag.Assign(1); diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 6e6b63329..cc859c14c 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -128,7 +128,7 @@ void Init() { Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); - FileSys::Path gamecoin_path("gamecoin.dat"); + FileSys::Path gamecoin_path("/gamecoin.dat"); FileSys::Mode open_mode = {}; open_mode.write_flag.Assign(1); open_mode.create_flag.Assign(1); From f775a3781bde276707b27ec220f4c7ca18062767 Mon Sep 17 00:00:00 2001 From: wwylele Date: Wed, 19 Oct 2016 21:42:47 +0800 Subject: [PATCH 11/13] FileSys: remove unused DiskArchive All "subclasses" of DiskArchive are splitted out. This class is useless --- src/core/file_sys/disk_archive.cpp | 147 ----------------------------- src/core/file_sys/disk_archive.h | 32 ------- 2 files changed, 179 deletions(-) diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp index 0e51fd1a6..a243d9a13 100644 --- a/src/core/file_sys/disk_archive.cpp +++ b/src/core/file_sys/disk_archive.cpp @@ -15,153 +15,6 @@ namespace FileSys { -ResultVal> DiskArchive::OpenFile(const Path& path, - const Mode& mode) const { - LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); - - auto full_path = mount_point + path.AsString(); - if (FileUtil::IsDirectory(full_path)) - return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, - ErrorLevel::Status); - - // Specifying only the Create flag is invalid - if (mode.create_flag && !mode.read_flag && !mode.write_flag) { - return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, - ErrorSummary::Canceled, ErrorLevel::Status); - } - - if (!FileUtil::Exists(full_path)) { - if (!mode.create_flag) { - LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.", - full_path.c_str()); - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, - ErrorSummary::NotFound, ErrorLevel::Status); - } else { - // Create the file - FileUtil::CreateEmptyFile(full_path); - } - } - - std::string mode_string = ""; - if (mode.write_flag) - mode_string += "r+"; // Files opened with Write access can be read from - else if (mode.read_flag) - mode_string += "r"; - - // Open the file in binary mode, to avoid problems with CR/LF on Windows systems - mode_string += "b"; - - FileUtil::IOFile file(full_path, mode_string.c_str()); - if (!file.IsOpen()) - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, - ErrorLevel::Status); - - auto disk_file = std::make_unique(std::move(file), mode); - return MakeResult>(std::move(disk_file)); -} - -ResultCode DiskArchive::DeleteFile(const Path& path) const { - std::string file_path = mount_point + path.AsString(); - - if (FileUtil::IsDirectory(file_path)) - return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, - ErrorLevel::Status); - - if (!FileUtil::Exists(file_path)) - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, - ErrorLevel::Status); - - if (FileUtil::Delete(file_path)) - return RESULT_SUCCESS; - - return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, - ErrorLevel::Status); -} - -ResultCode DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const { - if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) - return RESULT_SUCCESS; - - // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't - // exist or similar. Verify. - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::NothingHappened, ErrorLevel::Status); -} - -ResultCode DiskArchive::DeleteDirectory(const Path& path) const { - if (FileUtil::DeleteDir(mount_point + path.AsString())) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::Canceled, ErrorLevel::Status); -} - -ResultCode DiskArchive::DeleteDirectoryRecursively(const Path& path) const { - if (FileUtil::DeleteDirRecursively(mount_point + path.AsString())) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::Canceled, ErrorLevel::Status); -} - -ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const { - std::string full_path = mount_point + path.AsString(); - - if (FileUtil::IsDirectory(full_path)) - return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled, - ErrorLevel::Status); - - if (FileUtil::Exists(full_path)) - return ResultCode(ErrorDescription::FS_AlreadyExists, ErrorModule::FS, - ErrorSummary::NothingHappened, ErrorLevel::Status); - - if (size == 0) { - FileUtil::CreateEmptyFile(full_path); - return RESULT_SUCCESS; - } - - FileUtil::IOFile file(full_path, "wb"); - // Creates a sparse file (or a normal file on filesystems without the concept of sparse files) - // We do this by seeking to the right size, then writing a single null byte. - if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) - return RESULT_SUCCESS; - - return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource, - ErrorLevel::Info); -} - -ResultCode DiskArchive::CreateDirectory(const Path& path) const { - if (FileUtil::CreateDir(mount_point + path.AsString())) - return RESULT_SUCCESS; - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::Canceled, ErrorLevel::Status); -} - -ResultCode DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { - if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) - return RESULT_SUCCESS; - - // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't - // exist or similar. Verify. - return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description - ErrorSummary::NothingHappened, ErrorLevel::Status); -} - -ResultVal> DiskArchive::OpenDirectory(const Path& path) const { - LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); - auto full_path = mount_point + path.AsString(); - if (!FileUtil::IsDirectory(full_path)) - return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, - ErrorLevel::Permanent); - auto directory = std::make_unique(full_path); - return MakeResult>(std::move(directory)); -} - -u64 DiskArchive::GetFreeBytes() const { - // TODO: Stubbed to return 1GiB - return 1024 * 1024 * 1024; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - ResultVal DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { if (!mode.read_flag) return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index c2c3c3b23..eb9166df6 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h @@ -20,38 +20,6 @@ namespace FileSys { -/** - * Helper which implements a backend accessing the host machine's filesystem. - * This should be subclassed by concrete archive types, which will provide the - * base directory on the host filesystem and override any required functionality. - */ -class DiskArchive : public ArchiveBackend { -public: - DiskArchive(const std::string& mount_point_) : mount_point(mount_point_) {} - - virtual std::string GetName() const override { - return "DiskArchive: " + mount_point; - } - - ResultVal> OpenFile(const Path& path, - const Mode& mode) const override; - ResultCode DeleteFile(const Path& path) const override; - ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; - ResultCode DeleteDirectory(const Path& path) const override; - ResultCode DeleteDirectoryRecursively(const Path& path) const override; - ResultCode CreateFile(const Path& path, u64 size) const override; - ResultCode CreateDirectory(const Path& path) const override; - ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal> OpenDirectory(const Path& path) const override; - u64 GetFreeBytes() const override; - -protected: - friend class DiskFile; - friend class DiskDirectory; - - std::string mount_point; -}; - class DiskFile : public FileBackend { public: DiskFile(FileUtil::IOFile&& file_, const Mode& mode_) From d7d6975af0971f5a07d489bdef522ca121bb30ec Mon Sep 17 00:00:00 2001 From: wwylele Date: Fri, 21 Oct 2016 22:10:55 +0800 Subject: [PATCH 12/13] FileSys: rename SaveDataCheck archive to NCCH archive According to the observation from game and 3dbrew "Used for accessing general NCCH data" --- src/core/CMakeLists.txt | 4 ++-- ...ive_savedatacheck.cpp => archive_ncch.cpp} | 22 +++++++++---------- ...archive_savedatacheck.h => archive_ncch.h} | 8 +++---- src/core/hle/service/fs/archive.cpp | 9 ++++---- src/core/hle/service/fs/archive.h | 2 +- 5 files changed, 22 insertions(+), 23 deletions(-) rename src/core/file_sys/{archive_savedatacheck.cpp => archive_ncch.cpp} (63%) rename src/core/file_sys/{archive_savedatacheck.h => archive_ncch.h} (78%) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1ecd1c431..299f1f261 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -17,9 +17,9 @@ set(SRCS core_timing.cpp file_sys/archive_backend.cpp file_sys/archive_extsavedata.cpp + file_sys/archive_ncch.cpp file_sys/archive_romfs.cpp file_sys/archive_savedata.cpp - file_sys/archive_savedatacheck.cpp file_sys/archive_sdmc.cpp file_sys/archive_sdmcwriteonly.cpp file_sys/archive_systemsavedata.cpp @@ -162,9 +162,9 @@ set(HEADERS core_timing.h file_sys/archive_backend.h file_sys/archive_extsavedata.h + file_sys/archive_ncch.h file_sys/archive_romfs.h file_sys/archive_savedata.h - file_sys/archive_savedatacheck.h file_sys/archive_sdmc.h file_sys/archive_sdmcwriteonly.h file_sys/archive_systemsavedata.h diff --git a/src/core/file_sys/archive_savedatacheck.cpp b/src/core/file_sys/archive_ncch.cpp similarity index 63% rename from src/core/file_sys/archive_savedatacheck.cpp rename to src/core/file_sys/archive_ncch.cpp index 6c4542b7d..6f1aadfc3 100644 --- a/src/core/file_sys/archive_savedatacheck.cpp +++ b/src/core/file_sys/archive_ncch.cpp @@ -9,7 +9,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "core/file_sys/archive_savedatacheck.h" +#include "core/file_sys/archive_ncch.h" #include "core/file_sys/ivfc_archive.h" #include "core/hle/service/fs/archive.h" @@ -18,22 +18,22 @@ namespace FileSys { -static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) { +static std::string GetNCCHContainerPath(const std::string& nand_directory) { return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str()); } -static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) { +static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) { return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(), high, low); } -ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory) - : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {} +ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory) + : mount_point(GetNCCHContainerPath(nand_directory)) {} -ResultVal> ArchiveFactory_SaveDataCheck::Open(const Path& path) { +ResultVal> ArchiveFactory_NCCH::Open(const Path& path) { auto vec = path.AsBinary(); const u32* data = reinterpret_cast(vec.data()); - std::string file_path = GetSaveDataCheckPath(mount_point, data[1], data[0]); + std::string file_path = GetNCCHPath(mount_point, data[1], data[0]); auto file = std::make_shared(file_path, "rb"); if (!file->IsOpen()) { @@ -45,15 +45,15 @@ ResultVal> ArchiveFactory_SaveDataCheck::Open(co return MakeResult>(std::move(archive)); } -ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path, - const FileSys::ArchiveFormatInfo& format_info) { - LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive."); +ResultCode ArchiveFactory_NCCH::Format(const Path& path, + const FileSys::ArchiveFormatInfo& format_info) { + LOG_ERROR(Service_FS, "Attempted to format a NCCH archive."); // TODO: Verify error code return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, ErrorLevel::Permanent); } -ResultVal ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const { +ResultVal ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const { // TODO(Subv): Implement LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); return ResultCode(-1); diff --git a/src/core/file_sys/archive_savedatacheck.h b/src/core/file_sys/archive_ncch.h similarity index 78% rename from src/core/file_sys/archive_savedatacheck.h rename to src/core/file_sys/archive_ncch.h index e9cafbed9..66b8ce75d 100644 --- a/src/core/file_sys/archive_savedatacheck.h +++ b/src/core/file_sys/archive_ncch.h @@ -14,13 +14,13 @@ namespace FileSys { -/// File system interface to the SaveDataCheck archive -class ArchiveFactory_SaveDataCheck final : public ArchiveFactory { +/// File system interface to the NCCH archive +class ArchiveFactory_NCCH final : public ArchiveFactory { public: - ArchiveFactory_SaveDataCheck(const std::string& mount_point); + ArchiveFactory_NCCH(const std::string& mount_point); std::string GetName() const override { - return "SaveDataCheck"; + return "NCCH"; } ResultVal> Open(const Path& path) override; diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 62cf2c249..4c29784e8 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -15,8 +15,8 @@ #include "common/logging/log.h" #include "core/file_sys/archive_backend.h" #include "core/file_sys/archive_extsavedata.h" +#include "core/file_sys/archive_ncch.h" #include "core/file_sys/archive_savedata.h" -#include "core/file_sys/archive_savedatacheck.h" #include "core/file_sys/archive_sdmc.h" #include "core/file_sys/archive_sdmcwriteonly.h" #include "core/file_sys/archive_systemsavedata.h" @@ -554,10 +554,9 @@ void RegisterArchiveTypes() { LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", sharedextsavedata_factory->GetMountPoint().c_str()); - // Create the SaveDataCheck archive, basically a small variation of the RomFS archive - auto savedatacheck_factory = - std::make_unique(nand_directory); - RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck); + // Create the NCCH archive, basically a small variation of the RomFS archive + auto savedatacheck_factory = std::make_unique(nand_directory); + RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH); auto systemsavedata_factory = std::make_unique(nand_directory); diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 41a76285c..21ed9717b 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 { SystemSaveData = 0x00000008, SDMC = 0x00000009, SDMCWriteOnly = 0x0000000A, - SaveDataCheck = 0x2345678A, + NCCH = 0x2345678A, }; /// Media types for the archives From 282195b450721a5b8ad0cea8e66606b2661fe888 Mon Sep 17 00:00:00 2001 From: wwylele Date: Sat, 19 Nov 2016 17:16:34 +0200 Subject: [PATCH 13/13] tests: add a work-around for macOS linking error --- src/tests/CMakeLists.txt | 1 + src/tests/glad.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/tests/glad.cpp diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 47799e1c2..89237e477 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,4 +1,5 @@ set(SRCS + glad.cpp tests.cpp core/file_sys/path_parser.cpp ) diff --git a/src/tests/glad.cpp b/src/tests/glad.cpp new file mode 100644 index 000000000..b0b016440 --- /dev/null +++ b/src/tests/glad.cpp @@ -0,0 +1,14 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +// This is not an actual test, but a work-around for issue #2183. +// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS +// will error about undefined references from video_core to glad. So we explicitly use a glad +// function here to shut up the linker. +TEST_CASE("glad fake test", "[dummy]") { + REQUIRE(&gladLoadGL != nullptr); +}