mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +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 | ||||
|             file_sys/archive_backend.cpp | ||||
|             file_sys/archive_extsavedata.cpp | ||||
|             file_sys/archive_ncch.cpp | ||||
|             file_sys/archive_romfs.cpp | ||||
|             file_sys/archive_savedata.cpp | ||||
|             file_sys/archive_savedatacheck.cpp | ||||
|             file_sys/archive_sdmc.cpp | ||||
|             file_sys/archive_sdmcwriteonly.cpp | ||||
|             file_sys/archive_systemsavedata.cpp | ||||
|             file_sys/disk_archive.cpp | ||||
|             file_sys/ivfc_archive.cpp | ||||
|             file_sys/path_parser.cpp | ||||
|             file_sys/savedata_archive.cpp | ||||
|             gdbstub/gdbstub.cpp | ||||
|             hle/config_mem.cpp | ||||
|             hle/hle.cpp | ||||
|  | @ -159,15 +162,18 @@ set(HEADERS | |||
|             core_timing.h | ||||
|             file_sys/archive_backend.h | ||||
|             file_sys/archive_extsavedata.h | ||||
|             file_sys/archive_ncch.h | ||||
|             file_sys/archive_romfs.h | ||||
|             file_sys/archive_savedata.h | ||||
|             file_sys/archive_savedatacheck.h | ||||
|             file_sys/archive_sdmc.h | ||||
|             file_sys/archive_sdmcwriteonly.h | ||||
|             file_sys/archive_systemsavedata.h | ||||
|             file_sys/directory_backend.h | ||||
|             file_sys/disk_archive.h | ||||
|             file_sys/file_backend.h | ||||
|             file_sys/ivfc_archive.h | ||||
|             file_sys/path_parser.h | ||||
|             file_sys/savedata_archive.h | ||||
|             gdbstub/gdbstub.h | ||||
|             hle/config_mem.h | ||||
|             hle/function_wrappers.h | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ public: | |||
|      * @return Opened file, or error code | ||||
|      */ | ||||
|     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 | ||||
|  | @ -100,53 +100,53 @@ public: | |||
|      * Rename a File specified by its path | ||||
|      * @param src_path Source path relative to the archive | ||||
|      * @param dest_path Destination path relative to the archive | ||||
|      * @return Whether rename succeeded | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0; | ||||
|     virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Whether the directory could be deleted | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual bool DeleteDirectory(const Path& path) const = 0; | ||||
|     virtual ResultCode DeleteDirectory(const Path& path) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Delete a directory specified by its path and anything under it | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Whether the directory could be deleted | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual bool DeleteDirectoryRecursively(const Path& path) const = 0; | ||||
|     virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Create a file specified by its path | ||||
|      * @param path Path relative to the Archive | ||||
|      * @param size The size of the new file, filled with zeroes | ||||
|      * @return File creation result code | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Create a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Whether the directory could be created | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual bool CreateDirectory(const Path& path) const = 0; | ||||
|     virtual ResultCode CreateDirectory(const Path& path) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Rename a Directory specified by its path | ||||
|      * @param src_path Source path relative to the archive | ||||
|      * @param dest_path Destination path relative to the archive | ||||
|      * @return Whether rename succeeded | ||||
|      * @return Result of the operation | ||||
|      */ | ||||
|     virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; | ||||
|     virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Open a directory specified by its path | ||||
|      * @param path Path relative to the archive | ||||
|      * @return Opened directory, or nullptr | ||||
|      * @return Opened directory, or error code | ||||
|      */ | ||||
|     virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0; | ||||
|     virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the free space | ||||
|  |  | |||
|  | @ -11,6 +11,9 @@ | |||
| #include "common/string_util.h" | ||||
| #include "core/file_sys/archive_extsavedata.h" | ||||
| #include "core/file_sys/disk_archive.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/path_parser.h" | ||||
| #include "core/file_sys/savedata_archive.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -18,6 +21,116 @@ | |||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| /**
 | ||||
|  * A modified version of DiskFile for fixed-size file used by ExtSaveData | ||||
|  * The file size can't be changed by SetSize or Write. | ||||
|  */ | ||||
| class FixSizeDiskFile : public DiskFile { | ||||
| public: | ||||
|     FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) { | ||||
|         size = GetSize(); | ||||
|     } | ||||
| 
 | ||||
|     bool SetSize(u64 size) const override { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<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::vector<u8> vec_data = path.AsBinary(); | ||||
|     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); | ||||
|         } | ||||
|     } | ||||
|     auto archive = std::make_unique<DiskArchive>(fullpath); | ||||
|     auto archive = std::make_unique<ExtSaveDataArchive>(fullpath); | ||||
|     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/file_sys/archive_savedatacheck.h" | ||||
| #include "core/file_sys/archive_ncch.h" | ||||
| #include "core/file_sys/ivfc_archive.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| 
 | ||||
|  | @ -18,22 +18,22 @@ | |||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) { | ||||
| static std::string GetNCCHContainerPath(const std::string& nand_directory) { | ||||
|     return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str()); | ||||
| } | ||||
| 
 | ||||
| static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) { | ||||
| static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) { | ||||
|     return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(), | ||||
|                                     high, low); | ||||
| } | ||||
| 
 | ||||
| ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory) | ||||
|     : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {} | ||||
| ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory) | ||||
|     : mount_point(GetNCCHContainerPath(nand_directory)) {} | ||||
| 
 | ||||
| ResultVal<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(); | ||||
|     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"); | ||||
| 
 | ||||
|     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)); | ||||
| } | ||||
| 
 | ||||
| ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path, | ||||
|                                                 const FileSys::ArchiveFormatInfo& format_info) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive."); | ||||
| ResultCode ArchiveFactory_NCCH::Format(const Path& path, | ||||
|                                        const FileSys::ArchiveFormatInfo& format_info) { | ||||
|     LOG_ERROR(Service_FS, "Attempted to format a NCCH archive."); | ||||
|     // TODO: Verify error code
 | ||||
|     return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, | ||||
|                       ErrorLevel::Permanent); | ||||
| } | ||||
| 
 | ||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const { | ||||
| ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const { | ||||
|     // TODO(Subv): Implement
 | ||||
|     LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); | ||||
|     return ResultCode(-1); | ||||
|  | @ -14,13 +14,13 @@ | |||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| /// File system interface to the SaveDataCheck archive
 | ||||
| class ArchiveFactory_SaveDataCheck final : public ArchiveFactory { | ||||
| /// File system interface to the NCCH archive
 | ||||
| class ArchiveFactory_NCCH final : public ArchiveFactory { | ||||
| public: | ||||
|     ArchiveFactory_SaveDataCheck(const std::string& mount_point); | ||||
|     ArchiveFactory_NCCH(const std::string& mount_point); | ||||
| 
 | ||||
|     std::string GetName() const override { | ||||
|         return "SaveDataCheck"; | ||||
|         return "NCCH"; | ||||
|     } | ||||
| 
 | ||||
|     ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; | ||||
|  | @ -9,7 +9,7 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/file_sys/archive_savedata.h" | ||||
| #include "core/file_sys/disk_archive.h" | ||||
| #include "core/file_sys/savedata_archive.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| 
 | ||||
|  | @ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P | |||
|                           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)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "core/file_sys/archive_sdmc.h" | ||||
| #include "core/file_sys/disk_archive.h" | ||||
| #include "core/file_sys/errors.h" | ||||
| #include "core/file_sys/path_parser.h" | ||||
| #include "core/settings.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -15,6 +17,281 @@ | |||
| 
 | ||||
| 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) | ||||
|     : sdmc_directory(sdmc_directory) { | ||||
|     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) { | ||||
|     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)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,6 +14,32 @@ | |||
| 
 | ||||
| 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
 | ||||
| class ArchiveFactory_SDMC final : public ArchiveFactory { | ||||
| 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/string_util.h" | ||||
| #include "core/file_sys/archive_systemsavedata.h" | ||||
| #include "core/file_sys/disk_archive.h" | ||||
| #include "core/file_sys/savedata_archive.h" | ||||
| #include "core/hle/service/fs/archive.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c | |||
|         return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, | ||||
|                           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)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -40,12 +40,6 @@ public: | |||
|     DirectoryBackend() {} | ||||
|     virtual ~DirectoryBackend() {} | ||||
| 
 | ||||
|     /**
 | ||||
|     * Open the directory | ||||
|     * @return true if the directory opened correctly | ||||
|     */ | ||||
|     virtual bool Open() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * List files contained in the directory | ||||
|      * @param count Number of entries to return at once in entries | ||||
|  |  | |||
|  | @ -15,144 +15,8 @@ | |||
| 
 | ||||
| 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 { | ||||
|     if (!mode.read_flag && !mode.write_flag) | ||||
|     if (!mode.read_flag) | ||||
|         return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, | ||||
|                           ErrorSummary::Canceled, ErrorLevel::Status); | ||||
| 
 | ||||
|  | @ -189,21 +53,11 @@ bool DiskFile::Close() const { | |||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() { | ||||
|     // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
 | ||||
|     // the root directory we set while opening the archive.
 | ||||
|     // For example, opening /../../usr/bin can give the emulated program your installed programs.
 | ||||
|     this->path = archive.mount_point + path.AsString(); | ||||
| } | ||||
| 
 | ||||
| bool DiskDirectory::Open() { | ||||
|     if (!FileUtil::IsDirectory(path)) | ||||
|         return false; | ||||
| DiskDirectory::DiskDirectory(const std::string& path) : directory() { | ||||
|     unsigned size = FileUtil::ScanDirectoryTree(path, directory); | ||||
|     directory.size = size; | ||||
|     directory.isDirectory = true; | ||||
|     children_iterator = directory.children.begin(); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| u32 DiskDirectory::Read(const u32 count, Entry* entries) { | ||||
|  |  | |||
|  | @ -20,43 +20,13 @@ | |||
| 
 | ||||
| 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 { | ||||
| 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> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||
|     u64 GetSize() const override; | ||||
|  | @ -68,20 +38,18 @@ public: | |||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     std::string path; | ||||
|     Mode mode; | ||||
|     std::unique_ptr<FileUtil::IOFile> file; | ||||
| }; | ||||
| 
 | ||||
| class DiskDirectory : public DirectoryBackend { | ||||
| public: | ||||
|     DiskDirectory(const DiskArchive& archive, const Path& path); | ||||
|     DiskDirectory(const std::string& path); | ||||
| 
 | ||||
|     ~DiskDirectory() override { | ||||
|         Close(); | ||||
|     } | ||||
| 
 | ||||
|     bool Open() override; | ||||
|     u32 Read(const u32 count, Entry* entries) override; | ||||
| 
 | ||||
|     bool Close() const override { | ||||
|  | @ -89,7 +57,6 @@ public: | |||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     std::string path; | ||||
|     u32 total_entries_in_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() {} | ||||
|     virtual ~FileBackend() {} | ||||
| 
 | ||||
|     /**
 | ||||
|      * Open the file | ||||
|      * @return Result of the file operation | ||||
|      */ | ||||
|     virtual ResultCode Open() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Read data from the file | ||||
|      * @param offset Offset in bytes to start reading data from | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const { | |||
| } | ||||
| 
 | ||||
| ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, | ||||
|                                                               const Mode mode) const { | ||||
|                                                               const Mode& mode) const { | ||||
|     return MakeResult<std::unique_ptr<FileBackend>>( | ||||
|         std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); | ||||
| } | ||||
|  | @ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const { | |||
|                       ErrorLevel::Status); | ||||
| } | ||||
| 
 | ||||
| bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | ||||
| ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", | ||||
|                  GetName().c_str()); | ||||
|     return false; | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultCode(-1); | ||||
| } | ||||
| 
 | ||||
| bool IVFCArchive::DeleteDirectory(const Path& path) const { | ||||
| ResultCode IVFCArchive::DeleteDirectory(const Path& path) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", | ||||
|                  GetName().c_str()); | ||||
|     return false; | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultCode(-1); | ||||
| } | ||||
| 
 | ||||
| bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { | ||||
| ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", | ||||
|                  GetName().c_str()); | ||||
|     return false; | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultCode(-1); | ||||
| } | ||||
| 
 | ||||
| ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | ||||
|  | @ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { | |||
|                       ErrorLevel::Permanent); | ||||
| } | ||||
| 
 | ||||
| bool IVFCArchive::CreateDirectory(const Path& path) const { | ||||
| ResultCode IVFCArchive::CreateDirectory(const Path& path) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", | ||||
|                  GetName().c_str()); | ||||
|     return false; | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultCode(-1); | ||||
| } | ||||
| 
 | ||||
| bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | ||||
| ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { | ||||
|     LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", | ||||
|                  GetName().c_str()); | ||||
|     return false; | ||||
|     // TODO(wwylele): Use correct error code
 | ||||
|     return ResultCode(-1); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const { | ||||
|     return std::make_unique<IVFCDirectory>(); | ||||
| ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const { | ||||
|     return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>()); | ||||
| } | ||||
| 
 | ||||
| u64 IVFCArchive::GetFreeBytes() const { | ||||
|  |  | |||
|  | @ -33,15 +33,15 @@ public: | |||
|     std::string GetName() const override; | ||||
| 
 | ||||
|     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; | ||||
|     bool RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
|     bool DeleteDirectory(const Path& path) const override; | ||||
|     bool DeleteDirectoryRecursively(const Path& path) const override; | ||||
|     ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override; | ||||
|     ResultCode DeleteDirectory(const Path& path) const override; | ||||
|     ResultCode DeleteDirectoryRecursively(const Path& path) const override; | ||||
|     ResultCode CreateFile(const Path& path, u64 size) const override; | ||||
|     bool CreateDirectory(const Path& path) const override; | ||||
|     bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
|     std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; | ||||
|     ResultCode CreateDirectory(const Path& path) const override; | ||||
|     ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; | ||||
|     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override; | ||||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
| protected: | ||||
|  | @ -55,9 +55,6 @@ public: | |||
|     IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 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> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; | ||||
|     u64 GetSize() const override; | ||||
|  | @ -75,9 +72,6 @@ private: | |||
| 
 | ||||
| class IVFCDirectory : public DirectoryBackend { | ||||
| public: | ||||
|     bool Open() override { | ||||
|         return false; | ||||
|     } | ||||
|     u32 Read(const u32 count, Entry* entries) override { | ||||
|         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, | ||||
|     WrongAddress = 53, | ||||
|     FS_ArchiveNotMounted = 101, | ||||
|     FS_FileNotFound = 112, | ||||
|     FS_PathNotFound = 113, | ||||
|     FS_NotFound = 120, | ||||
|     FS_FileAlreadyExists = 180, | ||||
|     FS_DirectoryAlreadyExists = 185, | ||||
|     FS_AlreadyExists = 190, | ||||
|     FS_InvalidOpenFlags = 230, | ||||
|     FS_DirectoryNotEmpty = 240, | ||||
|     FS_NotAFile = 250, | ||||
|     FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
 | ||||
|     OutofRangeOrMisalignedAddress = | ||||
|         513, // TODO(purpasmart): Check if this name fits its actual usage
 | ||||
|     GPU_FirstInitialization = 519, | ||||
|     FS_InvalidReadFlag = 700, | ||||
|     FS_InvalidPath = 702, | ||||
|     FS_WriteBeyondEnd = 705, | ||||
|     FS_UnsupportedOpenFlags = 760, | ||||
|     FS_UnexpectedFileOrDirectory = 770, | ||||
|     InvalidSection = 1000, | ||||
|     TooLarge = 1001, | ||||
|     NotAuthorized = 1002, | ||||
|  |  | |||
|  | @ -360,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da | |||
| } | ||||
| 
 | ||||
| ResultCode DeleteConfigNANDSaveFile() { | ||||
|     FileSys::Path path("config"); | ||||
|     FileSys::Path path("/config"); | ||||
|     return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); | ||||
| } | ||||
| 
 | ||||
|  | @ -369,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() { | |||
|     mode.write_flag.Assign(1); | ||||
|     mode.create_flag.Assign(1); | ||||
| 
 | ||||
|     FileSys::Path path("config"); | ||||
|     FileSys::Path path("/config"); | ||||
| 
 | ||||
|     auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); | ||||
|     ASSERT_MSG(config_result.Succeeded(), "could not open file"); | ||||
|  | @ -383,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() { | |||
| ResultCode FormatConfig() { | ||||
|     ResultCode res = DeleteConfigNANDSaveFile(); | ||||
|     // The delete command fails if the file doesn't exist, so we have to check that too
 | ||||
|     if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound) | ||||
|     if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) { | ||||
|         return res; | ||||
|     } | ||||
|     // Delete the old data
 | ||||
|     cfg_config_file_buffer.fill(0); | ||||
|     // Create the header
 | ||||
|  | @ -510,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() { | |||
| 
 | ||||
|     cfg_system_save_data_archive = *archive_result; | ||||
| 
 | ||||
|     FileSys::Path config_path("config"); | ||||
|     FileSys::Path config_path("/config"); | ||||
|     FileSys::Mode open_mode = {}; | ||||
|     open_mode.read_flag.Assign(1); | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,9 +15,10 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/archive_extsavedata.h" | ||||
| #include "core/file_sys/archive_ncch.h" | ||||
| #include "core/file_sys/archive_savedata.h" | ||||
| #include "core/file_sys/archive_savedatacheck.h" | ||||
| #include "core/file_sys/archive_sdmc.h" | ||||
| #include "core/file_sys/archive_sdmcwriteonly.h" | ||||
| #include "core/file_sys/archive_systemsavedata.h" | ||||
| #include "core/file_sys/directory_backend.h" | ||||
| #include "core/file_sys/file_backend.h" | ||||
|  | @ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, | |||
|         return ERR_INVALID_ARCHIVE_HANDLE; | ||||
| 
 | ||||
|     if (src_archive == dest_archive) { | ||||
|         if (src_archive->RenameFile(src_path, dest_path)) | ||||
|             return RESULT_SUCCESS; | ||||
|         return src_archive->RenameFile(src_path, dest_path); | ||||
|     } else { | ||||
|         // TODO: Implement renaming across archives
 | ||||
|         return UnimplementedFunction(ErrorModule::FS); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
 | ||||
|     // exist or similar. Verify.
 | ||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 | ||||
|                       ErrorSummary::NothingHappened, ErrorLevel::Status); | ||||
| } | ||||
| 
 | ||||
| ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { | ||||
|  | @ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy | |||
|     if (archive == nullptr) | ||||
|         return ERR_INVALID_ARCHIVE_HANDLE; | ||||
| 
 | ||||
|     if (archive->DeleteDirectory(path)) | ||||
|         return RESULT_SUCCESS; | ||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 | ||||
|                       ErrorSummary::Canceled, ErrorLevel::Status); | ||||
|     return archive->DeleteDirectory(path); | ||||
| } | ||||
| 
 | ||||
| ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | ||||
|  | @ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, | |||
|     if (archive == nullptr) | ||||
|         return ERR_INVALID_ARCHIVE_HANDLE; | ||||
| 
 | ||||
|     if (archive->DeleteDirectoryRecursively(path)) | ||||
|         return RESULT_SUCCESS; | ||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 | ||||
|                       ErrorSummary::Canceled, ErrorLevel::Status); | ||||
|     return archive->DeleteDirectoryRecursively(path); | ||||
| } | ||||
| 
 | ||||
| ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, | ||||
|  | @ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy | |||
|     if (archive == nullptr) | ||||
|         return ERR_INVALID_ARCHIVE_HANDLE; | ||||
| 
 | ||||
|     if (archive->CreateDirectory(path)) | ||||
|         return RESULT_SUCCESS; | ||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 | ||||
|                       ErrorSummary::Canceled, ErrorLevel::Status); | ||||
|     return archive->CreateDirectory(path); | ||||
| } | ||||
| 
 | ||||
| ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | ||||
|  | @ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, | |||
|         return ERR_INVALID_ARCHIVE_HANDLE; | ||||
| 
 | ||||
|     if (src_archive == dest_archive) { | ||||
|         if (src_archive->RenameDirectory(src_path, dest_path)) | ||||
|             return RESULT_SUCCESS; | ||||
|         return src_archive->RenameDirectory(src_path, dest_path); | ||||
|     } else { | ||||
|         // TODO: Implement renaming across archives
 | ||||
|         return UnimplementedFunction(ErrorModule::FS); | ||||
|     } | ||||
| 
 | ||||
|     // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
 | ||||
|     // exist or similar. Verify.
 | ||||
|     return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
 | ||||
|                       ErrorSummary::NothingHappened, ErrorLevel::Status); | ||||
| } | ||||
| 
 | ||||
| ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, | ||||
|  | @ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a | |||
|     if (archive == nullptr) | ||||
|         return ERR_INVALID_ARCHIVE_HANDLE; | ||||
| 
 | ||||
|     std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path); | ||||
|     if (backend == nullptr) { | ||||
|         return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, | ||||
|                           ErrorLevel::Permanent); | ||||
|     } | ||||
|     auto backend = archive->OpenDirectory(path); | ||||
|     if (backend.Failed()) | ||||
|         return backend.Code(); | ||||
| 
 | ||||
|     auto directory = Kernel::SharedPtr<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)); | ||||
| } | ||||
| 
 | ||||
|  | @ -549,6 +527,13 @@ void RegisterArchiveTypes() { | |||
|         LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", | ||||
|                   sdmc_directory.c_str()); | ||||
| 
 | ||||
|     auto sdmcwo_factory = std::make_unique<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
 | ||||
|     auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); | ||||
|     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", | ||||
|                   sharedextsavedata_factory->GetMountPoint().c_str()); | ||||
| 
 | ||||
|     // Create the SaveDataCheck archive, basically a small variation of the RomFS archive
 | ||||
|     auto savedatacheck_factory = | ||||
|         std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); | ||||
|     RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck); | ||||
|     // Create the NCCH archive, basically a small variation of the RomFS archive
 | ||||
|     auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory); | ||||
|     RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH); | ||||
| 
 | ||||
|     auto systemsavedata_factory = | ||||
|         std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 { | |||
|     SystemSaveData = 0x00000008, | ||||
|     SDMC = 0x00000009, | ||||
|     SDMCWriteOnly = 0x0000000A, | ||||
|     SaveDataCheck = 0x2345678A, | ||||
|     NCCH = 0x2345678A, | ||||
| }; | ||||
| 
 | ||||
| /// Media types for the archives
 | ||||
|  |  | |||
|  | @ -128,7 +128,7 @@ void Init() { | |||
|             Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); | ||||
|         ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); | ||||
| 
 | ||||
|         FileSys::Path gamecoin_path("gamecoin.dat"); | ||||
|         FileSys::Path gamecoin_path("/gamecoin.dat"); | ||||
|         FileSys::Mode open_mode = {}; | ||||
|         open_mode.write_flag.Assign(1); | ||||
|         open_mode.create_flag.Assign(1); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| set(SRCS | ||||
|             glad.cpp | ||||
|             tests.cpp | ||||
|             core/file_sys/path_parser.cpp | ||||
|             ) | ||||
| 
 | ||||
| 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