mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #2132 from wwylele/fix-fs-err
Correct FS error codes & add path boundary checks
This commit is contained in:
		
						commit
						4ba5acdaff
					
				
					 30 changed files with 1234 additions and 304 deletions
				
			
		|  | @ -17,13 +17,16 @@ set(SRCS | ||||||
|             core_timing.cpp |             core_timing.cpp | ||||||
|             file_sys/archive_backend.cpp |             file_sys/archive_backend.cpp | ||||||
|             file_sys/archive_extsavedata.cpp |             file_sys/archive_extsavedata.cpp | ||||||
|  |             file_sys/archive_ncch.cpp | ||||||
|             file_sys/archive_romfs.cpp |             file_sys/archive_romfs.cpp | ||||||
|             file_sys/archive_savedata.cpp |             file_sys/archive_savedata.cpp | ||||||
|             file_sys/archive_savedatacheck.cpp |  | ||||||
|             file_sys/archive_sdmc.cpp |             file_sys/archive_sdmc.cpp | ||||||
|  |             file_sys/archive_sdmcwriteonly.cpp | ||||||
|             file_sys/archive_systemsavedata.cpp |             file_sys/archive_systemsavedata.cpp | ||||||
|             file_sys/disk_archive.cpp |             file_sys/disk_archive.cpp | ||||||
|             file_sys/ivfc_archive.cpp |             file_sys/ivfc_archive.cpp | ||||||
|  |             file_sys/path_parser.cpp | ||||||
|  |             file_sys/savedata_archive.cpp | ||||||
|             gdbstub/gdbstub.cpp |             gdbstub/gdbstub.cpp | ||||||
|             hle/config_mem.cpp |             hle/config_mem.cpp | ||||||
|             hle/hle.cpp |             hle/hle.cpp | ||||||
|  | @ -159,15 +162,18 @@ set(HEADERS | ||||||
|             core_timing.h |             core_timing.h | ||||||
|             file_sys/archive_backend.h |             file_sys/archive_backend.h | ||||||
|             file_sys/archive_extsavedata.h |             file_sys/archive_extsavedata.h | ||||||
|  |             file_sys/archive_ncch.h | ||||||
|             file_sys/archive_romfs.h |             file_sys/archive_romfs.h | ||||||
|             file_sys/archive_savedata.h |             file_sys/archive_savedata.h | ||||||
|             file_sys/archive_savedatacheck.h |  | ||||||
|             file_sys/archive_sdmc.h |             file_sys/archive_sdmc.h | ||||||
|  |             file_sys/archive_sdmcwriteonly.h | ||||||
|             file_sys/archive_systemsavedata.h |             file_sys/archive_systemsavedata.h | ||||||
|             file_sys/directory_backend.h |             file_sys/directory_backend.h | ||||||
|             file_sys/disk_archive.h |             file_sys/disk_archive.h | ||||||
|             file_sys/file_backend.h |             file_sys/file_backend.h | ||||||
|             file_sys/ivfc_archive.h |             file_sys/ivfc_archive.h | ||||||
|  |             file_sys/path_parser.h | ||||||
|  |             file_sys/savedata_archive.h | ||||||
|             gdbstub/gdbstub.h |             gdbstub/gdbstub.h | ||||||
|             hle/config_mem.h |             hle/config_mem.h | ||||||
|             hle/function_wrappers.h |             hle/function_wrappers.h | ||||||
|  |  | ||||||
|  | @ -87,7 +87,7 @@ public: | ||||||
|      * @return Opened file, or error code |      * @return Opened file, or error code | ||||||
|      */ |      */ | ||||||
|     virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |     virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||||
|                                                              const Mode mode) const = 0; |                                                              const Mode& mode) const = 0; | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Delete a file specified by its path |      * Delete a file specified by its path | ||||||
|  | @ -100,53 +100,53 @@ public: | ||||||
|      * Rename a File specified by its path |      * Rename a File specified by its path | ||||||
|      * @param src_path Source path relative to the archive |      * @param src_path Source path relative to the archive | ||||||
|      * @param dest_path Destination 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 |      * Delete a directory specified by its path | ||||||
|      * @param path Path relative to the archive |      * @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 |      * Delete a directory specified by its path and anything under it | ||||||
|      * @param path Path relative to the archive |      * @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 |      * Create a file specified by its path | ||||||
|      * @param path Path relative to the Archive |      * @param path Path relative to the Archive | ||||||
|      * @param size The size of the new file, filled with zeroes |      * @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; |     virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Create a directory specified by its path |      * Create a directory specified by its path | ||||||
|      * @param path Path relative to the archive |      * @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 |      * Rename a Directory specified by its path | ||||||
|      * @param src_path Source path relative to the archive |      * @param src_path Source path relative to the archive | ||||||
|      * @param dest_path Destination 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 |      * Open a directory specified by its path | ||||||
|      * @param path Path relative to the archive |      * @param path Path relative to the archive | ||||||
|      * @return Opened directory, or nullptr |      * @return Opened directory, or error code | ||||||
|      */ |      */ | ||||||
|     virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0; |     virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Get the free space |      * Get the free space | ||||||
|  |  | ||||||
|  | @ -11,6 +11,9 @@ | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "core/file_sys/archive_extsavedata.h" | #include "core/file_sys/archive_extsavedata.h" | ||||||
| #include "core/file_sys/disk_archive.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" | #include "core/hle/service/fs/archive.h" | ||||||
| 
 | 
 | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
|  | @ -18,6 +21,116 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | 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<size_t> 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<size_t>(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<std::unique_ptr<FileBackend>> 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<FixSizeDiskFile>(std::move(file), rwmode); | ||||||
|  |         return MakeResult<std::unique_ptr<FileBackend>>(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::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) { | ||||||
|     std::vector<u8> vec_data = path.AsBinary(); |     std::vector<u8> vec_data = path.AsBinary(); | ||||||
|     const u32* data = reinterpret_cast<const u32*>(vec_data.data()); |     const u32* data = reinterpret_cast<const u32*>(vec_data.data()); | ||||||
|  | @ -84,7 +197,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons | ||||||
|                               ErrorSummary::InvalidState, ErrorLevel::Status); |                               ErrorSummary::InvalidState, ErrorLevel::Status); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     auto archive = std::make_unique<DiskArchive>(fullpath); |     auto archive = std::make_unique<ExtSaveDataArchive>(fullpath); | ||||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.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/file_sys/ivfc_archive.h" | ||||||
| #include "core/hle/service/fs/archive.h" | #include "core/hle/service/fs/archive.h" | ||||||
| 
 | 
 | ||||||
|  | @ -18,22 +18,22 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | 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()); |     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(), |     return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(), | ||||||
|                                     high, low); |                                     high, low); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory) | ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory) | ||||||
|     : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {} |     : mount_point(GetNCCHContainerPath(nand_directory)) {} | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(const Path& path) { | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) { | ||||||
|     auto vec = path.AsBinary(); |     auto vec = path.AsBinary(); | ||||||
|     const u32* data = reinterpret_cast<u32*>(vec.data()); |     const u32* data = reinterpret_cast<u32*>(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<FileUtil::IOFile>(file_path, "rb"); |     auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb"); | ||||||
| 
 | 
 | ||||||
|     if (!file->IsOpen()) { |     if (!file->IsOpen()) { | ||||||
|  | @ -45,15 +45,15 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co | ||||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path, | ResultCode ArchiveFactory_NCCH::Format(const Path& path, | ||||||
|                                                 const FileSys::ArchiveFormatInfo& format_info) { |                                        const FileSys::ArchiveFormatInfo& format_info) { | ||||||
|     LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive."); |     LOG_ERROR(Service_FS, "Attempted to format a NCCH archive."); | ||||||
|     // TODO: Verify error code
 |     // TODO: Verify error code
 | ||||||
|     return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, |     return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, | ||||||
|                       ErrorLevel::Permanent); |                       ErrorLevel::Permanent); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const { | ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const { | ||||||
|     // TODO(Subv): Implement
 |     // TODO(Subv): Implement
 | ||||||
|     LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |     LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); | ||||||
|     return ResultCode(-1); |     return ResultCode(-1); | ||||||
|  | @ -14,13 +14,13 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| /// File system interface to the SaveDataCheck archive
 | /// File system interface to the NCCH archive
 | ||||||
| class ArchiveFactory_SaveDataCheck final : public ArchiveFactory { | class ArchiveFactory_NCCH final : public ArchiveFactory { | ||||||
| public: | public: | ||||||
|     ArchiveFactory_SaveDataCheck(const std::string& mount_point); |     ArchiveFactory_NCCH(const std::string& mount_point); | ||||||
| 
 | 
 | ||||||
|     std::string GetName() const override { |     std::string GetName() const override { | ||||||
|         return "SaveDataCheck"; |         return "NCCH"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; |     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "core/file_sys/archive_savedata.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/kernel/process.h" | ||||||
| #include "core/hle/service/fs/archive.h" | #include "core/hle/service/fs/archive.h" | ||||||
| 
 | 
 | ||||||
|  | @ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P | ||||||
|                           ErrorSummary::InvalidState, ErrorLevel::Status); |                           ErrorSummary::InvalidState, ErrorLevel::Status); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point)); |     auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point)); | ||||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/file_sys/archive_sdmc.h" | #include "core/file_sys/archive_sdmc.h" | ||||||
| #include "core/file_sys/disk_archive.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" | #include "core/settings.h" | ||||||
| 
 | 
 | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
|  | @ -15,6 +17,281 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
|  | ResultVal<std::unique_ptr<FileBackend>> 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<std::unique_ptr<FileBackend>> 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); | ||||||
|  | 
 | ||||||
|  |     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<DiskFile>(std::move(file), mode); | ||||||
|  |     return MakeResult<std::unique_ptr<FileBackend>>(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 <typename T> | ||||||
|  | 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<std::unique_ptr<DirectoryBackend>> 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<DiskDirectory>(full_path); | ||||||
|  |     return MakeResult<std::unique_ptr<DirectoryBackend>>(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) | ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) | ||||||
|     : sdmc_directory(sdmc_directory) { |     : sdmc_directory(sdmc_directory) { | ||||||
|     LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); |     LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); | ||||||
|  | @ -35,7 +312,7 @@ bool ArchiveFactory_SDMC::Initialize() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) { | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) { | ||||||
|     auto archive = std::make_unique<DiskArchive>(sdmc_directory); |     auto archive = std::make_unique<SDMCArchive>(sdmc_directory); | ||||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,6 +14,32 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | 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<std::unique_ptr<FileBackend>> 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<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||||
|  |     u64 GetFreeBytes() const override; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const; | ||||||
|  |     std::string mount_point; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /// File system interface to the SDMC archive
 | /// File system interface to the SDMC archive
 | ||||||
| class ArchiveFactory_SDMC final : public ArchiveFactory { | class ArchiveFactory_SDMC final : public ArchiveFactory { | ||||||
| public: | public: | ||||||
|  |  | ||||||
							
								
								
									
										70
									
								
								src/core/file_sys/archive_sdmcwriteonly.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/core/file_sys/archive_sdmcwriteonly.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | ||||||
|  | // Copyright 2016 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #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<std::unique_ptr<FileBackend>> 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::OpenFileBase(path, mode); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultVal<std::unique_ptr<DirectoryBackend>> 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<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) { | ||||||
|  |     auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory); | ||||||
|  |     return MakeResult<std::unique_ptr<ArchiveBackend>>(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<ArchiveFormatInfo> 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
 | ||||||
							
								
								
									
										57
									
								
								src/core/file_sys/archive_sdmcwriteonly.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/core/file_sys/archive_sdmcwriteonly.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||||
|  |                                                      const Mode& mode) const override; | ||||||
|  | 
 | ||||||
|  |     ResultVal<std::unique_ptr<DirectoryBackend>> 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<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; | ||||||
|  |     ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override; | ||||||
|  |     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::string sdmc_directory; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "core/file_sys/archive_systemsavedata.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" | #include "core/hle/service/fs/archive.h" | ||||||
| 
 | 
 | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
|  | @ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c | ||||||
|         return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, |         return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, | ||||||
|                           ErrorSummary::InvalidState, ErrorLevel::Status); |                           ErrorSummary::InvalidState, ErrorLevel::Status); | ||||||
|     } |     } | ||||||
|     auto archive = std::make_unique<DiskArchive>(fullpath); |     auto archive = std::make_unique<SaveDataArchive>(fullpath); | ||||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,12 +40,6 @@ public: | ||||||
|     DirectoryBackend() {} |     DirectoryBackend() {} | ||||||
|     virtual ~DirectoryBackend() {} |     virtual ~DirectoryBackend() {} | ||||||
| 
 | 
 | ||||||
|     /**
 |  | ||||||
|     * Open the directory |  | ||||||
|     * @return true if the directory opened correctly |  | ||||||
|     */ |  | ||||||
|     virtual bool Open() = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |     /**
 | ||||||
|      * List files contained in the directory |      * List files contained in the directory | ||||||
|      * @param count Number of entries to return at once in entries |      * @param count Number of entries to return at once in entries | ||||||
|  |  | ||||||
|  | @ -15,144 +15,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileBackend>> 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<DiskFile>(*this, path, mode); |  | ||||||
|     ResultCode result = file->Open(); |  | ||||||
|     if (result.IsError()) |  | ||||||
|         return result; |  | ||||||
|     return MakeResult<std::unique_ptr<FileBackend>>(std::move(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); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool DiskArchive::DeleteDirectory(const Path& path) const { |  | ||||||
|     return FileUtil::DeleteDir(mount_point + path.AsString()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const { |  | ||||||
|     return FileUtil::DeleteDirRecursively(mount_point + path.AsString()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool DiskArchive::CreateDirectory(const Path& path) const { |  | ||||||
|     return FileUtil::CreateDir(mount_point + path.AsString()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const { |  | ||||||
|     LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); |  | ||||||
|     auto directory = std::make_unique<DiskDirectory>(*this, path); |  | ||||||
|     if (!directory->Open()) |  | ||||||
|         return nullptr; |  | ||||||
|     return std::move(directory); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| u64 DiskArchive::GetFreeBytes() const { |  | ||||||
|     // TODO: Stubbed to return 1GiB
 |  | ||||||
|     return 1024 * 1024 * 1024; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 |  | ||||||
| 
 |  | ||||||
| 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<FileUtil::IOFile>(path, mode_string.c_str()); |  | ||||||
|     if (file->IsOpen()) |  | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, |  | ||||||
|                       ErrorLevel::Status); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { | ResultVal<size_t> 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, |         return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, | ||||||
|                           ErrorSummary::Canceled, ErrorLevel::Status); |                           ErrorSummary::Canceled, ErrorLevel::Status); | ||||||
| 
 | 
 | ||||||
|  | @ -189,21 +53,11 @@ bool DiskFile::Close() const { | ||||||
| 
 | 
 | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
| 
 | 
 | ||||||
| DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() { | DiskDirectory::DiskDirectory(const std::string& 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; |  | ||||||
|     unsigned size = FileUtil::ScanDirectoryTree(path, directory); |     unsigned size = FileUtil::ScanDirectoryTree(path, directory); | ||||||
|     directory.size = size; |     directory.size = size; | ||||||
|     directory.isDirectory = true; |     directory.isDirectory = true; | ||||||
|     children_iterator = directory.children.begin(); |     children_iterator = directory.children.begin(); | ||||||
|     return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u32 DiskDirectory::Read(const u32 count, Entry* entries) { | u32 DiskDirectory::Read(const u32 count, Entry* entries) { | ||||||
|  |  | ||||||
|  | @ -20,43 +20,13 @@ | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | 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<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |  | ||||||
|                                                      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 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<DirectoryBackend> 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 { | class DiskFile : public FileBackend { | ||||||
| public: | 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<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | ||||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||||
|     u64 GetSize() const override; |     u64 GetSize() const override; | ||||||
|  | @ -68,20 +38,18 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|     std::string path; |  | ||||||
|     Mode mode; |     Mode mode; | ||||||
|     std::unique_ptr<FileUtil::IOFile> file; |     std::unique_ptr<FileUtil::IOFile> file; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class DiskDirectory : public DirectoryBackend { | class DiskDirectory : public DirectoryBackend { | ||||||
| public: | public: | ||||||
|     DiskDirectory(const DiskArchive& archive, const Path& path); |     DiskDirectory(const std::string& path); | ||||||
| 
 | 
 | ||||||
|     ~DiskDirectory() override { |     ~DiskDirectory() override { | ||||||
|         Close(); |         Close(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     bool Open() override; |  | ||||||
|     u32 Read(const u32 count, Entry* entries) override; |     u32 Read(const u32 count, Entry* entries) override; | ||||||
| 
 | 
 | ||||||
|     bool Close() const override { |     bool Close() const override { | ||||||
|  | @ -89,7 +57,6 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|     std::string path; |  | ||||||
|     u32 total_entries_in_directory; |     u32 total_entries_in_directory; | ||||||
|     FileUtil::FSTEntry directory; |     FileUtil::FSTEntry directory; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								src/core/file_sys/errors.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/core/file_sys/errors.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | // 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_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, | ||||||
|  |                                       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); | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
|  | @ -18,12 +18,6 @@ public: | ||||||
|     FileBackend() {} |     FileBackend() {} | ||||||
|     virtual ~FileBackend() {} |     virtual ~FileBackend() {} | ||||||
| 
 | 
 | ||||||
|     /**
 |  | ||||||
|      * Open the file |  | ||||||
|      * @return Result of the file operation |  | ||||||
|      */ |  | ||||||
|     virtual ResultCode Open() = 0; |  | ||||||
| 
 |  | ||||||
|     /**
 |     /**
 | ||||||
|      * Read data from the file |      * Read data from the file | ||||||
|      * @param offset Offset in bytes to start reading data from |      * @param offset Offset in bytes to start reading data from | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, | ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, | ||||||
|                                                               const Mode mode) const { |                                                               const Mode& mode) const { | ||||||
|     return MakeResult<std::unique_ptr<FileBackend>>( |     return MakeResult<std::unique_ptr<FileBackend>>( | ||||||
|         std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); |         std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); | ||||||
| } | } | ||||||
|  | @ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const { | ||||||
|                       ErrorLevel::Status); |                       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).", |     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", | ||||||
|                  GetName().c_str()); |                  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).", |     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", | ||||||
|                  GetName().c_str()); |                  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).", |     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", | ||||||
|                  GetName().c_str()); |                  GetName().c_str()); | ||||||
|     return false; |     // TODO(wwylele): Use correct error code
 | ||||||
|  |     return ResultCode(-1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | ||||||
|  | @ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | ||||||
|                       ErrorLevel::Permanent); |                       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).", |     LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", | ||||||
|                  GetName().c_str()); |                  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).", |     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", | ||||||
|                  GetName().c_str()); |                  GetName().c_str()); | ||||||
|     return false; |     // TODO(wwylele): Use correct error code
 | ||||||
|  |     return ResultCode(-1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const { | ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const { | ||||||
|     return std::make_unique<IVFCDirectory>(); |     return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| u64 IVFCArchive::GetFreeBytes() const { | u64 IVFCArchive::GetFreeBytes() const { | ||||||
|  |  | ||||||
|  | @ -33,15 +33,15 @@ public: | ||||||
|     std::string GetName() const override; |     std::string GetName() const override; | ||||||
| 
 | 
 | ||||||
|     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, |     ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, | ||||||
|                                                      const Mode mode) const override; |                                                      const Mode& mode) const override; | ||||||
|     ResultCode DeleteFile(const Path& path) const override; |     ResultCode DeleteFile(const Path& path) const override; | ||||||
|     bool RenameFile(const Path& src_path, const Path& dest_path) const override; |     ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||||
|     bool DeleteDirectory(const Path& path) const override; |     ResultCode DeleteDirectory(const Path& path) const override; | ||||||
|     bool DeleteDirectoryRecursively(const Path& path) const override; |     ResultCode DeleteDirectoryRecursively(const Path& path) const override; | ||||||
|     ResultCode CreateFile(const Path& path, u64 size) const override; |     ResultCode CreateFile(const Path& path, u64 size) const override; | ||||||
|     bool CreateDirectory(const Path& path) const override; |     ResultCode CreateDirectory(const Path& path) const override; | ||||||
|     bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; |     ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||||
|     std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; |     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||||
|     u64 GetFreeBytes() const override; |     u64 GetFreeBytes() const override; | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|  | @ -55,9 +55,6 @@ public: | ||||||
|     IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) |     IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) | ||||||
|         : romfs_file(file), data_offset(offset), data_size(size) {} |         : romfs_file(file), data_offset(offset), data_size(size) {} | ||||||
| 
 | 
 | ||||||
|     ResultCode Open() override { |  | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     } |  | ||||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; |     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | ||||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; |     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||||
|     u64 GetSize() const override; |     u64 GetSize() const override; | ||||||
|  | @ -75,9 +72,6 @@ private: | ||||||
| 
 | 
 | ||||||
| class IVFCDirectory : public DirectoryBackend { | class IVFCDirectory : public DirectoryBackend { | ||||||
| public: | public: | ||||||
|     bool Open() override { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|     u32 Read(const u32 count, Entry* entries) override { |     u32 Read(const u32 count, Entry* entries) override { | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										98
									
								
								src/core/file_sys/path_parser.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/core/file_sys/path_parser.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | ||||||
|  | // Copyright 2016 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <algorithm> | ||||||
|  | #include <set> | ||||||
|  | #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<char> 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<std::string>(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
 | ||||||
							
								
								
									
										61
									
								
								src/core/file_sys/path_parser.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/core/file_sys/path_parser.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <string> | ||||||
|  | #include <vector> | ||||||
|  | #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<std::string> path_sequence; | ||||||
|  |     bool is_valid{}; | ||||||
|  |     bool is_root{}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										283
									
								
								src/core/file_sys/savedata_archive.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								src/core/file_sys/savedata_archive.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<std::unique_ptr<FileBackend>> 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<DiskFile>(std::move(file), mode); | ||||||
|  |     return MakeResult<std::unique_ptr<FileBackend>>(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 <typename T> | ||||||
|  | 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<std::unique_ptr<DirectoryBackend>> 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<DiskDirectory>(full_path); | ||||||
|  |     return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u64 SaveDataArchive::GetFreeBytes() const { | ||||||
|  |     // TODO: Stubbed to return 1GiB
 | ||||||
|  |     return 1024 * 1024 * 1024; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										43
									
								
								src/core/file_sys/savedata_archive.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/core/file_sys/savedata_archive.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <string> | ||||||
|  | #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<std::unique_ptr<FileBackend>> 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<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||||
|  |     u64 GetFreeBytes() const override; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     std::string mount_point; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace FileSys
 | ||||||
|  | @ -20,15 +20,24 @@ enum class ErrorDescription : u32 { | ||||||
|     OS_InvalidBufferDescriptor = 48, |     OS_InvalidBufferDescriptor = 48, | ||||||
|     WrongAddress = 53, |     WrongAddress = 53, | ||||||
|     FS_ArchiveNotMounted = 101, |     FS_ArchiveNotMounted = 101, | ||||||
|  |     FS_FileNotFound = 112, | ||||||
|  |     FS_PathNotFound = 113, | ||||||
|     FS_NotFound = 120, |     FS_NotFound = 120, | ||||||
|  |     FS_FileAlreadyExists = 180, | ||||||
|  |     FS_DirectoryAlreadyExists = 185, | ||||||
|     FS_AlreadyExists = 190, |     FS_AlreadyExists = 190, | ||||||
|     FS_InvalidOpenFlags = 230, |     FS_InvalidOpenFlags = 230, | ||||||
|  |     FS_DirectoryNotEmpty = 240, | ||||||
|     FS_NotAFile = 250, |     FS_NotAFile = 250, | ||||||
|     FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
 |     FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
 | ||||||
|     OutofRangeOrMisalignedAddress = |     OutofRangeOrMisalignedAddress = | ||||||
|         513, // TODO(purpasmart): Check if this name fits its actual usage
 |         513, // TODO(purpasmart): Check if this name fits its actual usage
 | ||||||
|     GPU_FirstInitialization = 519, |     GPU_FirstInitialization = 519, | ||||||
|  |     FS_InvalidReadFlag = 700, | ||||||
|     FS_InvalidPath = 702, |     FS_InvalidPath = 702, | ||||||
|  |     FS_WriteBeyondEnd = 705, | ||||||
|  |     FS_UnsupportedOpenFlags = 760, | ||||||
|  |     FS_UnexpectedFileOrDirectory = 770, | ||||||
|     InvalidSection = 1000, |     InvalidSection = 1000, | ||||||
|     TooLarge = 1001, |     TooLarge = 1001, | ||||||
|     NotAuthorized = 1002, |     NotAuthorized = 1002, | ||||||
|  |  | ||||||
|  | @ -360,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultCode DeleteConfigNANDSaveFile() { | ResultCode DeleteConfigNANDSaveFile() { | ||||||
|     FileSys::Path path("config"); |     FileSys::Path path("/config"); | ||||||
|     return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); |     return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -369,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() { | ||||||
|     mode.write_flag.Assign(1); |     mode.write_flag.Assign(1); | ||||||
|     mode.create_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); |     auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); | ||||||
|     ASSERT_MSG(config_result.Succeeded(), "could not open file"); |     ASSERT_MSG(config_result.Succeeded(), "could not open file"); | ||||||
|  | @ -383,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() { | ||||||
| ResultCode FormatConfig() { | ResultCode FormatConfig() { | ||||||
|     ResultCode res = DeleteConfigNANDSaveFile(); |     ResultCode res = DeleteConfigNANDSaveFile(); | ||||||
|     // The delete command fails if the file doesn't exist, so we have to check that too
 |     // 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; |         return res; | ||||||
|  |     } | ||||||
|     // Delete the old data
 |     // Delete the old data
 | ||||||
|     cfg_config_file_buffer.fill(0); |     cfg_config_file_buffer.fill(0); | ||||||
|     // Create the header
 |     // Create the header
 | ||||||
|  | @ -510,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() { | ||||||
| 
 | 
 | ||||||
|     cfg_system_save_data_archive = *archive_result; |     cfg_system_save_data_archive = *archive_result; | ||||||
| 
 | 
 | ||||||
|     FileSys::Path config_path("config"); |     FileSys::Path config_path("/config"); | ||||||
|     FileSys::Mode open_mode = {}; |     FileSys::Mode open_mode = {}; | ||||||
|     open_mode.read_flag.Assign(1); |     open_mode.read_flag.Assign(1); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,9 +15,10 @@ | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "core/file_sys/archive_backend.h" | #include "core/file_sys/archive_backend.h" | ||||||
| #include "core/file_sys/archive_extsavedata.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_savedata.h" | ||||||
| #include "core/file_sys/archive_savedatacheck.h" |  | ||||||
| #include "core/file_sys/archive_sdmc.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/archive_systemsavedata.h" | ||||||
| #include "core/file_sys/directory_backend.h" | #include "core/file_sys/directory_backend.h" | ||||||
| #include "core/file_sys/file_backend.h" | #include "core/file_sys/file_backend.h" | ||||||
|  | @ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, | ||||||
|         return ERR_INVALID_ARCHIVE_HANDLE; |         return ERR_INVALID_ARCHIVE_HANDLE; | ||||||
| 
 | 
 | ||||||
|     if (src_archive == dest_archive) { |     if (src_archive == dest_archive) { | ||||||
|         if (src_archive->RenameFile(src_path, dest_path)) |         return src_archive->RenameFile(src_path, dest_path); | ||||||
|             return RESULT_SUCCESS; |  | ||||||
|     } else { |     } else { | ||||||
|         // TODO: Implement renaming across archives
 |         // TODO: Implement renaming across archives
 | ||||||
|         return UnimplementedFunction(ErrorModule::FS); |         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) { | ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { | ||||||
|  | @ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy | ||||||
|     if (archive == nullptr) |     if (archive == nullptr) | ||||||
|         return ERR_INVALID_ARCHIVE_HANDLE; |         return ERR_INVALID_ARCHIVE_HANDLE; | ||||||
| 
 | 
 | ||||||
|     if (archive->DeleteDirectory(path)) |     return archive->DeleteDirectory(path); | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 |  | ||||||
|                       ErrorSummary::Canceled, ErrorLevel::Status); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | ||||||
|  | @ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | ||||||
|     if (archive == nullptr) |     if (archive == nullptr) | ||||||
|         return ERR_INVALID_ARCHIVE_HANDLE; |         return ERR_INVALID_ARCHIVE_HANDLE; | ||||||
| 
 | 
 | ||||||
|     if (archive->DeleteDirectoryRecursively(path)) |     return archive->DeleteDirectoryRecursively(path); | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 |  | ||||||
|                       ErrorSummary::Canceled, ErrorLevel::Status); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ||||||
|  | @ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy | ||||||
|     if (archive == nullptr) |     if (archive == nullptr) | ||||||
|         return ERR_INVALID_ARCHIVE_HANDLE; |         return ERR_INVALID_ARCHIVE_HANDLE; | ||||||
| 
 | 
 | ||||||
|     if (archive->CreateDirectory(path)) |     return archive->CreateDirectory(path); | ||||||
|         return RESULT_SUCCESS; |  | ||||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 |  | ||||||
|                       ErrorSummary::Canceled, ErrorLevel::Status); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | ||||||
|  | @ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | ||||||
|         return ERR_INVALID_ARCHIVE_HANDLE; |         return ERR_INVALID_ARCHIVE_HANDLE; | ||||||
| 
 | 
 | ||||||
|     if (src_archive == dest_archive) { |     if (src_archive == dest_archive) { | ||||||
|         if (src_archive->RenameDirectory(src_path, dest_path)) |         return src_archive->RenameDirectory(src_path, dest_path); | ||||||
|             return RESULT_SUCCESS; |  | ||||||
|     } else { |     } else { | ||||||
|         // TODO: Implement renaming across archives
 |         // TODO: Implement renaming across archives
 | ||||||
|         return UnimplementedFunction(ErrorModule::FS); |         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<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, | ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, | ||||||
|  | @ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a | ||||||
|     if (archive == nullptr) |     if (archive == nullptr) | ||||||
|         return ERR_INVALID_ARCHIVE_HANDLE; |         return ERR_INVALID_ARCHIVE_HANDLE; | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path); |     auto backend = archive->OpenDirectory(path); | ||||||
|     if (backend == nullptr) { |     if (backend.Failed()) | ||||||
|         return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, |         return backend.Code(); | ||||||
|                           ErrorLevel::Permanent); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     auto directory = Kernel::SharedPtr<Directory>(new Directory(std::move(backend), path)); |     auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path)); | ||||||
|     return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); |     return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -549,6 +527,13 @@ void RegisterArchiveTypes() { | ||||||
|         LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", |         LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", | ||||||
|                   sdmc_directory.c_str()); |                   sdmc_directory.c_str()); | ||||||
| 
 | 
 | ||||||
|  |     auto sdmcwo_factory = std::make_unique<FileSys::ArchiveFactory_SDMCWriteOnly>(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
 |     // Create the SaveData archive
 | ||||||
|     auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); |     auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); | ||||||
|     RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); |     RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); | ||||||
|  | @ -569,10 +554,9 @@ void RegisterArchiveTypes() { | ||||||
|         LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", |         LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", | ||||||
|                   sharedextsavedata_factory->GetMountPoint().c_str()); |                   sharedextsavedata_factory->GetMountPoint().c_str()); | ||||||
| 
 | 
 | ||||||
|     // Create the SaveDataCheck archive, basically a small variation of the RomFS archive
 |     // Create the NCCH archive, basically a small variation of the RomFS archive
 | ||||||
|     auto savedatacheck_factory = |     auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory); | ||||||
|         std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); |     RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH); | ||||||
|     RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck); |  | ||||||
| 
 | 
 | ||||||
|     auto systemsavedata_factory = |     auto systemsavedata_factory = | ||||||
|         std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); |         std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 { | ||||||
|     SystemSaveData = 0x00000008, |     SystemSaveData = 0x00000008, | ||||||
|     SDMC = 0x00000009, |     SDMC = 0x00000009, | ||||||
|     SDMCWriteOnly = 0x0000000A, |     SDMCWriteOnly = 0x0000000A, | ||||||
|     SaveDataCheck = 0x2345678A, |     NCCH = 0x2345678A, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// Media types for the archives
 | /// Media types for the archives
 | ||||||
|  |  | ||||||
|  | @ -128,7 +128,7 @@ void Init() { | ||||||
|             Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); |             Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); | ||||||
|         ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); |         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 = {}; |         FileSys::Mode open_mode = {}; | ||||||
|         open_mode.write_flag.Assign(1); |         open_mode.write_flag.Assign(1); | ||||||
|         open_mode.create_flag.Assign(1); |         open_mode.create_flag.Assign(1); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| set(SRCS | set(SRCS | ||||||
|  |             glad.cpp | ||||||
|             tests.cpp |             tests.cpp | ||||||
|  |             core/file_sys/path_parser.cpp | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
| set(HEADERS | set(HEADERS | ||||||
|  |  | ||||||
							
								
								
									
										38
									
								
								src/tests/core/file_sys/path_parser.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/tests/core/file_sys/path_parser.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | // Copyright 2016 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <catch.hpp> | ||||||
|  | #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<u8>{})).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
 | ||||||
							
								
								
									
										14
									
								
								src/tests/glad.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/tests/glad.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | // Copyright 2016 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <catch.hpp> | ||||||
|  | #include <glad/glad.h> | ||||||
|  | 
 | ||||||
|  | // 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); | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue