summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Sebastian Valle2016-11-27 18:56:56 -0500
committerGravatar GitHub2016-11-27 18:56:56 -0500
commit4ba5acdaff19f5334b86e86c324763d4e9b969b0 (patch)
tree408343a46858bcde292744d89fc6b3dadd9a54b0
parentMerge pull request #2218 from Subv/stencil_lines (diff)
parenttests: add a work-around for macOS linking error (diff)
downloadyuzu-4ba5acdaff19f5334b86e86c324763d4e9b969b0.tar.gz
yuzu-4ba5acdaff19f5334b86e86c324763d4e9b969b0.tar.xz
yuzu-4ba5acdaff19f5334b86e86c324763d4e9b969b0.zip
Merge pull request #2132 from wwylele/fix-fs-err
Correct FS error codes & add path boundary checks
Diffstat (limited to '')
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/file_sys/archive_backend.h28
-rw-r--r--src/core/file_sys/archive_extsavedata.cpp115
-rw-r--r--src/core/file_sys/archive_ncch.cpp (renamed from src/core/file_sys/archive_savedatacheck.cpp)22
-rw-r--r--src/core/file_sys/archive_ncch.h (renamed from src/core/file_sys/archive_savedatacheck.h)8
-rw-r--r--src/core/file_sys/archive_savedata.cpp4
-rw-r--r--src/core/file_sys/archive_sdmc.cpp279
-rw-r--r--src/core/file_sys/archive_sdmc.h26
-rw-r--r--src/core/file_sys/archive_sdmcwriteonly.cpp70
-rw-r--r--src/core/file_sys/archive_sdmcwriteonly.h57
-rw-r--r--src/core/file_sys/archive_systemsavedata.cpp4
-rw-r--r--src/core/file_sys/directory_backend.h6
-rw-r--r--src/core/file_sys/disk_archive.cpp150
-rw-r--r--src/core/file_sys/disk_archive.h43
-rw-r--r--src/core/file_sys/errors.h40
-rw-r--r--src/core/file_sys/file_backend.h6
-rw-r--r--src/core/file_sys/ivfc_archive.cpp31
-rw-r--r--src/core/file_sys/ivfc_archive.h20
-rw-r--r--src/core/file_sys/path_parser.cpp98
-rw-r--r--src/core/file_sys/path_parser.h61
-rw-r--r--src/core/file_sys/savedata_archive.cpp283
-rw-r--r--src/core/file_sys/savedata_archive.h43
-rw-r--r--src/core/hle/result.h9
-rw-r--r--src/core/hle/service/cfg/cfg.cpp9
-rw-r--r--src/core/hle/service/fs/archive.cpp58
-rw-r--r--src/core/hle/service/fs/archive.h2
-rw-r--r--src/core/hle/service/ptm/ptm.cpp2
-rw-r--r--src/tests/CMakeLists.txt2
-rw-r--r--src/tests/core/file_sys/path_parser.cpp38
-rw-r--r--src/tests/glad.cpp14
30 files changed, 1234 insertions, 304 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4a9c6fd2f..299f1f261 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -17,13 +17,16 @@ set(SRCS
17 core_timing.cpp 17 core_timing.cpp
18 file_sys/archive_backend.cpp 18 file_sys/archive_backend.cpp
19 file_sys/archive_extsavedata.cpp 19 file_sys/archive_extsavedata.cpp
20 file_sys/archive_ncch.cpp
20 file_sys/archive_romfs.cpp 21 file_sys/archive_romfs.cpp
21 file_sys/archive_savedata.cpp 22 file_sys/archive_savedata.cpp
22 file_sys/archive_savedatacheck.cpp
23 file_sys/archive_sdmc.cpp 23 file_sys/archive_sdmc.cpp
24 file_sys/archive_sdmcwriteonly.cpp
24 file_sys/archive_systemsavedata.cpp 25 file_sys/archive_systemsavedata.cpp
25 file_sys/disk_archive.cpp 26 file_sys/disk_archive.cpp
26 file_sys/ivfc_archive.cpp 27 file_sys/ivfc_archive.cpp
28 file_sys/path_parser.cpp
29 file_sys/savedata_archive.cpp
27 gdbstub/gdbstub.cpp 30 gdbstub/gdbstub.cpp
28 hle/config_mem.cpp 31 hle/config_mem.cpp
29 hle/hle.cpp 32 hle/hle.cpp
@@ -159,15 +162,18 @@ set(HEADERS
159 core_timing.h 162 core_timing.h
160 file_sys/archive_backend.h 163 file_sys/archive_backend.h
161 file_sys/archive_extsavedata.h 164 file_sys/archive_extsavedata.h
165 file_sys/archive_ncch.h
162 file_sys/archive_romfs.h 166 file_sys/archive_romfs.h
163 file_sys/archive_savedata.h 167 file_sys/archive_savedata.h
164 file_sys/archive_savedatacheck.h
165 file_sys/archive_sdmc.h 168 file_sys/archive_sdmc.h
169 file_sys/archive_sdmcwriteonly.h
166 file_sys/archive_systemsavedata.h 170 file_sys/archive_systemsavedata.h
167 file_sys/directory_backend.h 171 file_sys/directory_backend.h
168 file_sys/disk_archive.h 172 file_sys/disk_archive.h
169 file_sys/file_backend.h 173 file_sys/file_backend.h
170 file_sys/ivfc_archive.h 174 file_sys/ivfc_archive.h
175 file_sys/path_parser.h
176 file_sys/savedata_archive.h
171 gdbstub/gdbstub.h 177 gdbstub/gdbstub.h
172 hle/config_mem.h 178 hle/config_mem.h
173 hle/function_wrappers.h 179 hle/function_wrappers.h
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h
index 06b8f2ed7..58f6c150c 100644
--- a/src/core/file_sys/archive_backend.h
+++ b/src/core/file_sys/archive_backend.h
@@ -87,7 +87,7 @@ public:
87 * @return Opened file, or error code 87 * @return Opened file, or error code
88 */ 88 */
89 virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, 89 virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
90 const Mode mode) const = 0; 90 const Mode& mode) const = 0;
91 91
92 /** 92 /**
93 * Delete a file specified by its path 93 * Delete a file specified by its path
@@ -100,53 +100,53 @@ public:
100 * Rename a File specified by its path 100 * Rename a File specified by its path
101 * @param src_path Source path relative to the archive 101 * @param src_path Source path relative to the archive
102 * @param dest_path Destination path relative to the archive 102 * @param dest_path Destination path relative to the archive
103 * @return Whether rename succeeded 103 * @return Result of the operation
104 */ 104 */
105 virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0; 105 virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
106 106
107 /** 107 /**
108 * Delete a directory specified by its path 108 * Delete a directory specified by its path
109 * @param path Path relative to the archive 109 * @param path Path relative to the archive
110 * @return Whether the directory could be deleted 110 * @return Result of the operation
111 */ 111 */
112 virtual bool DeleteDirectory(const Path& path) const = 0; 112 virtual ResultCode DeleteDirectory(const Path& path) const = 0;
113 113
114 /** 114 /**
115 * Delete a directory specified by its path and anything under it 115 * Delete a directory specified by its path and anything under it
116 * @param path Path relative to the archive 116 * @param path Path relative to the archive
117 * @return Whether the directory could be deleted 117 * @return Result of the operation
118 */ 118 */
119 virtual bool DeleteDirectoryRecursively(const Path& path) const = 0; 119 virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
120 120
121 /** 121 /**
122 * Create a file specified by its path 122 * Create a file specified by its path
123 * @param path Path relative to the Archive 123 * @param path Path relative to the Archive
124 * @param size The size of the new file, filled with zeroes 124 * @param size The size of the new file, filled with zeroes
125 * @return File creation result code 125 * @return Result of the operation
126 */ 126 */
127 virtual ResultCode CreateFile(const Path& path, u64 size) const = 0; 127 virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
128 128
129 /** 129 /**
130 * Create a directory specified by its path 130 * Create a directory specified by its path
131 * @param path Path relative to the archive 131 * @param path Path relative to the archive
132 * @return Whether the directory could be created 132 * @return Result of the operation
133 */ 133 */
134 virtual bool CreateDirectory(const Path& path) const = 0; 134 virtual ResultCode CreateDirectory(const Path& path) const = 0;
135 135
136 /** 136 /**
137 * Rename a Directory specified by its path 137 * Rename a Directory specified by its path
138 * @param src_path Source path relative to the archive 138 * @param src_path Source path relative to the archive
139 * @param dest_path Destination path relative to the archive 139 * @param dest_path Destination path relative to the archive
140 * @return Whether rename succeeded 140 * @return Result of the operation
141 */ 141 */
142 virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0; 142 virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
143 143
144 /** 144 /**
145 * Open a directory specified by its path 145 * Open a directory specified by its path
146 * @param path Path relative to the archive 146 * @param path Path relative to the archive
147 * @return Opened directory, or nullptr 147 * @return Opened directory, or error code
148 */ 148 */
149 virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0; 149 virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
150 150
151 /** 151 /**
152 * Get the free space 152 * Get the free space
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp
index e1d29efd3..e1c4931ec 100644
--- a/src/core/file_sys/archive_extsavedata.cpp
+++ b/src/core/file_sys/archive_extsavedata.cpp
@@ -11,6 +11,9 @@
11#include "common/string_util.h" 11#include "common/string_util.h"
12#include "core/file_sys/archive_extsavedata.h" 12#include "core/file_sys/archive_extsavedata.h"
13#include "core/file_sys/disk_archive.h" 13#include "core/file_sys/disk_archive.h"
14#include "core/file_sys/errors.h"
15#include "core/file_sys/path_parser.h"
16#include "core/file_sys/savedata_archive.h"
14#include "core/hle/service/fs/archive.h" 17#include "core/hle/service/fs/archive.h"
15 18
16//////////////////////////////////////////////////////////////////////////////////////////////////// 19////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -18,6 +21,116 @@
18 21
19namespace FileSys { 22namespace FileSys {
20 23
24/**
25 * A modified version of DiskFile for fixed-size file used by ExtSaveData
26 * The file size can't be changed by SetSize or Write.
27 */
28class FixSizeDiskFile : public DiskFile {
29public:
30 FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) {
31 size = GetSize();
32 }
33
34 bool SetSize(u64 size) const override {
35 return false;
36 }
37
38 ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
39 const u8* buffer) const override {
40 if (offset > size) {
41 return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS,
42 ErrorSummary::InvalidArgument, ErrorLevel::Usage);
43 } else if (offset == size) {
44 return MakeResult<size_t>(0);
45 }
46
47 if (offset + length > size) {
48 length = size - offset;
49 }
50
51 return DiskFile::Write(offset, length, flush, buffer);
52 }
53
54private:
55 u64 size{};
56};
57
58/**
59 * Archive backend for general extsave data archive type.
60 * The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for
61 * - file size can't be changed once created (thus creating zero-size file and openning with create
62 * flag are prohibited);
63 * - always open a file with read+write permission.
64 */
65class ExtSaveDataArchive : public SaveDataArchive {
66public:
67 ExtSaveDataArchive(const std::string& mount_point) : SaveDataArchive(mount_point) {}
68
69 std::string GetName() const override {
70 return "ExtSaveDataArchive: " + mount_point;
71 }
72
73 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
74 const Mode& mode) const override {
75 LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
76
77 const PathParser path_parser(path);
78
79 if (!path_parser.IsValid()) {
80 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
81 return ERROR_INVALID_PATH;
82 }
83
84 if (mode.hex == 0) {
85 LOG_ERROR(Service_FS, "Empty open mode");
86 return ERROR_UNSUPPORTED_OPEN_FLAGS;
87 }
88
89 if (mode.create_flag) {
90 LOG_ERROR(Service_FS, "Create flag is not supported");
91 return ERROR_UNSUPPORTED_OPEN_FLAGS;
92 }
93
94 const auto full_path = path_parser.BuildHostPath(mount_point);
95
96 switch (path_parser.GetHostStatus(mount_point)) {
97 case PathParser::InvalidMountPoint:
98 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
99 return ERROR_FILE_NOT_FOUND;
100 case PathParser::PathNotFound:
101 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
102 return ERROR_PATH_NOT_FOUND;
103 case PathParser::FileInPath:
104 case PathParser::DirectoryFound:
105 LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
106 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
107 case PathParser::NotFound:
108 LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
109 return ERROR_FILE_NOT_FOUND;
110 }
111
112 FileUtil::IOFile file(full_path, "r+b");
113 if (!file.IsOpen()) {
114 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
115 return ERROR_FILE_NOT_FOUND;
116 }
117
118 Mode rwmode;
119 rwmode.write_flag.Assign(1);
120 rwmode.read_flag.Assign(1);
121 auto disk_file = std::make_unique<FixSizeDiskFile>(std::move(file), rwmode);
122 return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
123 }
124
125 ResultCode CreateFile(const Path& path, u64 size) const override {
126 if (size == 0) {
127 LOG_ERROR(Service_FS, "Zero-size file is not supported");
128 return ERROR_UNSUPPORTED_OPEN_FLAGS;
129 }
130 return SaveDataArchive::CreateFile(path, size);
131 }
132};
133
21std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) { 134std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) {
22 std::vector<u8> vec_data = path.AsBinary(); 135 std::vector<u8> vec_data = path.AsBinary();
23 const u32* data = reinterpret_cast<const u32*>(vec_data.data()); 136 const u32* data = reinterpret_cast<const u32*>(vec_data.data());
@@ -84,7 +197,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons
84 ErrorSummary::InvalidState, ErrorLevel::Status); 197 ErrorSummary::InvalidState, ErrorLevel::Status);
85 } 198 }
86 } 199 }
87 auto archive = std::make_unique<DiskArchive>(fullpath); 200 auto archive = std::make_unique<ExtSaveDataArchive>(fullpath);
88 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); 201 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
89} 202}
90 203
diff --git a/src/core/file_sys/archive_savedatacheck.cpp b/src/core/file_sys/archive_ncch.cpp
index 6c4542b7d..6f1aadfc3 100644
--- a/src/core/file_sys/archive_savedatacheck.cpp
+++ b/src/core/file_sys/archive_ncch.cpp
@@ -9,7 +9,7 @@
9#include "common/file_util.h" 9#include "common/file_util.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/string_util.h" 11#include "common/string_util.h"
12#include "core/file_sys/archive_savedatacheck.h" 12#include "core/file_sys/archive_ncch.h"
13#include "core/file_sys/ivfc_archive.h" 13#include "core/file_sys/ivfc_archive.h"
14#include "core/hle/service/fs/archive.h" 14#include "core/hle/service/fs/archive.h"
15 15
@@ -18,22 +18,22 @@
18 18
19namespace FileSys { 19namespace FileSys {
20 20
21static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) { 21static std::string GetNCCHContainerPath(const std::string& nand_directory) {
22 return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str()); 22 return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str());
23} 23}
24 24
25static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) { 25static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) {
26 return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(), 26 return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(),
27 high, low); 27 high, low);
28} 28}
29 29
30ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory) 30ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)
31 : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {} 31 : mount_point(GetNCCHContainerPath(nand_directory)) {}
32 32
33ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(const Path& path) { 33ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) {
34 auto vec = path.AsBinary(); 34 auto vec = path.AsBinary();
35 const u32* data = reinterpret_cast<u32*>(vec.data()); 35 const u32* data = reinterpret_cast<u32*>(vec.data());
36 std::string file_path = GetSaveDataCheckPath(mount_point, data[1], data[0]); 36 std::string file_path = GetNCCHPath(mount_point, data[1], data[0]);
37 auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb"); 37 auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");
38 38
39 if (!file->IsOpen()) { 39 if (!file->IsOpen()) {
@@ -45,15 +45,15 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co
45 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); 45 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
46} 46}
47 47
48ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path, 48ResultCode ArchiveFactory_NCCH::Format(const Path& path,
49 const FileSys::ArchiveFormatInfo& format_info) { 49 const FileSys::ArchiveFormatInfo& format_info) {
50 LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive."); 50 LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
51 // TODO: Verify error code 51 // TODO: Verify error code
52 return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported, 52 return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
53 ErrorLevel::Permanent); 53 ErrorLevel::Permanent);
54} 54}
55 55
56ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const { 56ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const {
57 // TODO(Subv): Implement 57 // TODO(Subv): Implement
58 LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); 58 LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
59 return ResultCode(-1); 59 return ResultCode(-1);
diff --git a/src/core/file_sys/archive_savedatacheck.h b/src/core/file_sys/archive_ncch.h
index e9cafbed9..66b8ce75d 100644
--- a/src/core/file_sys/archive_savedatacheck.h
+++ b/src/core/file_sys/archive_ncch.h
@@ -14,13 +14,13 @@
14 14
15namespace FileSys { 15namespace FileSys {
16 16
17/// File system interface to the SaveDataCheck archive 17/// File system interface to the NCCH archive
18class ArchiveFactory_SaveDataCheck final : public ArchiveFactory { 18class ArchiveFactory_NCCH final : public ArchiveFactory {
19public: 19public:
20 ArchiveFactory_SaveDataCheck(const std::string& mount_point); 20 ArchiveFactory_NCCH(const std::string& mount_point);
21 21
22 std::string GetName() const override { 22 std::string GetName() const override {
23 return "SaveDataCheck"; 23 return "NCCH";
24 } 24 }
25 25
26 ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override; 26 ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp
index 6711035ec..ecb44a215 100644
--- a/src/core/file_sys/archive_savedata.cpp
+++ b/src/core/file_sys/archive_savedata.cpp
@@ -9,7 +9,7 @@
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "common/string_util.h" 10#include "common/string_util.h"
11#include "core/file_sys/archive_savedata.h" 11#include "core/file_sys/archive_savedata.h"
12#include "core/file_sys/disk_archive.h" 12#include "core/file_sys/savedata_archive.h"
13#include "core/hle/kernel/process.h" 13#include "core/hle/kernel/process.h"
14#include "core/hle/service/fs/archive.h" 14#include "core/hle/service/fs/archive.h"
15 15
@@ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P
54 ErrorSummary::InvalidState, ErrorLevel::Status); 54 ErrorSummary::InvalidState, ErrorLevel::Status);
55 } 55 }
56 56
57 auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point)); 57 auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
58 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); 58 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
59} 59}
60 60
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index bcb03ed36..333dfb92e 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -8,6 +8,8 @@
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/file_sys/archive_sdmc.h" 9#include "core/file_sys/archive_sdmc.h"
10#include "core/file_sys/disk_archive.h" 10#include "core/file_sys/disk_archive.h"
11#include "core/file_sys/errors.h"
12#include "core/file_sys/path_parser.h"
11#include "core/settings.h" 13#include "core/settings.h"
12 14
13//////////////////////////////////////////////////////////////////////////////////////////////////// 15////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -15,6 +17,281 @@
15 17
16namespace FileSys { 18namespace FileSys {
17 19
20ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path,
21 const Mode& mode) const {
22 Mode modified_mode;
23 modified_mode.hex = mode.hex;
24
25 // SDMC archive always opens a file with at least read permission
26 modified_mode.read_flag.Assign(1);
27
28 return OpenFileBase(path, modified_mode);
29}
30
31ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& path,
32 const Mode& mode) const {
33 LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
34
35 const PathParser path_parser(path);
36
37 if (!path_parser.IsValid()) {
38 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
39 return ERROR_INVALID_PATH;
40 }
41
42 if (mode.hex == 0) {
43 LOG_ERROR(Service_FS, "Empty open mode");
44 return ERROR_INVALID_OPEN_FLAGS;
45 }
46
47 if (mode.create_flag && !mode.write_flag) {
48 LOG_ERROR(Service_FS, "Create flag set but write flag not set");
49 return ERROR_INVALID_OPEN_FLAGS;
50 }
51
52 const auto full_path = path_parser.BuildHostPath(mount_point);
53
54 switch (path_parser.GetHostStatus(mount_point)) {
55 case PathParser::InvalidMountPoint:
56 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
57 return ERROR_NOT_FOUND;
58 case PathParser::PathNotFound:
59 case PathParser::FileInPath:
60 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
61 return ERROR_NOT_FOUND;
62 case PathParser::DirectoryFound:
63 LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
64 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
65 case PathParser::NotFound:
66 if (!mode.create_flag) {
67 LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
68 full_path.c_str());
69 return ERROR_NOT_FOUND;
70 } else {
71 // Create the file
72 FileUtil::CreateEmptyFile(full_path);
73 }
74 break;
75 }
76
77 FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
78 if (!file.IsOpen()) {
79 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
80 return ERROR_NOT_FOUND;
81 }
82
83 auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
84 return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
85}
86
87ResultCode SDMCArchive::DeleteFile(const Path& path) const {
88 const PathParser path_parser(path);
89
90 if (!path_parser.IsValid()) {
91 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
92 return ERROR_INVALID_PATH;
93 }
94
95 const auto full_path = path_parser.BuildHostPath(mount_point);
96
97 switch (path_parser.GetHostStatus(mount_point)) {
98 case PathParser::InvalidMountPoint:
99 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
100 return ERROR_NOT_FOUND;
101 case PathParser::PathNotFound:
102 case PathParser::FileInPath:
103 case PathParser::NotFound:
104 LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
105 return ERROR_NOT_FOUND;
106 case PathParser::DirectoryFound:
107 LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
108 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
109 }
110
111 if (FileUtil::Delete(full_path)) {
112 return RESULT_SUCCESS;
113 }
114
115 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
116 return ERROR_NOT_FOUND;
117}
118
119ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
120 if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
121 return RESULT_SUCCESS;
122 }
123
124 // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
125 // exist or similar. Verify.
126 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
127 ErrorSummary::NothingHappened, ErrorLevel::Status);
128}
129
130template <typename T>
131static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
132 T deleter) {
133 const PathParser path_parser(path);
134
135 if (!path_parser.IsValid()) {
136 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
137 return ERROR_INVALID_PATH;
138 }
139
140 if (path_parser.IsRootDirectory())
141 return ERROR_NOT_FOUND;
142
143 const auto full_path = path_parser.BuildHostPath(mount_point);
144
145 switch (path_parser.GetHostStatus(mount_point)) {
146 case PathParser::InvalidMountPoint:
147 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
148 return ERROR_NOT_FOUND;
149 case PathParser::PathNotFound:
150 case PathParser::NotFound:
151 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
152 return ERROR_NOT_FOUND;
153 case PathParser::FileInPath:
154 case PathParser::FileFound:
155 LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
156 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
157 }
158
159 if (deleter(full_path)) {
160 return RESULT_SUCCESS;
161 }
162
163 LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
164 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
165}
166
167ResultCode SDMCArchive::DeleteDirectory(const Path& path) const {
168 return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
169}
170
171ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
172 return DeleteDirectoryHelper(
173 path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
174}
175
176ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
177 const PathParser path_parser(path);
178
179 if (!path_parser.IsValid()) {
180 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
181 return ERROR_INVALID_PATH;
182 }
183
184 const auto full_path = path_parser.BuildHostPath(mount_point);
185
186 switch (path_parser.GetHostStatus(mount_point)) {
187 case PathParser::InvalidMountPoint:
188 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
189 return ERROR_NOT_FOUND;
190 case PathParser::PathNotFound:
191 case PathParser::FileInPath:
192 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
193 return ERROR_NOT_FOUND;
194 case PathParser::DirectoryFound:
195 LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
196 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
197 case PathParser::FileFound:
198 LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
199 return ERROR_ALREADY_EXISTS;
200 }
201
202 if (size == 0) {
203 FileUtil::CreateEmptyFile(full_path);
204 return RESULT_SUCCESS;
205 }
206
207 FileUtil::IOFile file(full_path, "wb");
208 // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
209 // We do this by seeking to the right size, then writing a single null byte.
210 if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
211 return RESULT_SUCCESS;
212 }
213
214 LOG_ERROR(Service_FS, "Too large file");
215 return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
216 ErrorLevel::Info);
217}
218
219ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
220 const PathParser path_parser(path);
221
222 if (!path_parser.IsValid()) {
223 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
224 return ERROR_INVALID_PATH;
225 }
226
227 const auto full_path = path_parser.BuildHostPath(mount_point);
228
229 switch (path_parser.GetHostStatus(mount_point)) {
230 case PathParser::InvalidMountPoint:
231 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
232 return ERROR_NOT_FOUND;
233 case PathParser::PathNotFound:
234 case PathParser::FileInPath:
235 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
236 return ERROR_NOT_FOUND;
237 case PathParser::DirectoryFound:
238 case PathParser::FileFound:
239 LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
240 return ERROR_ALREADY_EXISTS;
241 }
242
243 if (FileUtil::CreateDir(mount_point + path.AsString())) {
244 return RESULT_SUCCESS;
245 }
246
247 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
248 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
249 ErrorLevel::Status);
250}
251
252ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
253 if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
254 return RESULT_SUCCESS;
255
256 // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
257 // exist or similar. Verify.
258 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
259 ErrorSummary::NothingHappened, ErrorLevel::Status);
260}
261
262ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const {
263 const PathParser path_parser(path);
264
265 if (!path_parser.IsValid()) {
266 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
267 return ERROR_INVALID_PATH;
268 }
269
270 const auto full_path = path_parser.BuildHostPath(mount_point);
271
272 switch (path_parser.GetHostStatus(mount_point)) {
273 case PathParser::InvalidMountPoint:
274 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
275 return ERROR_NOT_FOUND;
276 case PathParser::PathNotFound:
277 case PathParser::NotFound:
278 case PathParser::FileFound:
279 LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
280 return ERROR_NOT_FOUND;
281 case PathParser::FileInPath:
282 LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
283 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
284 }
285
286 auto directory = std::make_unique<DiskDirectory>(full_path);
287 return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
288}
289
290u64 SDMCArchive::GetFreeBytes() const {
291 // TODO: Stubbed to return 1GiB
292 return 1024 * 1024 * 1024;
293}
294
18ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory) 295ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
19 : sdmc_directory(sdmc_directory) { 296 : sdmc_directory(sdmc_directory) {
20 LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); 297 LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
@@ -35,7 +312,7 @@ bool ArchiveFactory_SDMC::Initialize() {
35} 312}
36 313
37ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) { 314ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) {
38 auto archive = std::make_unique<DiskArchive>(sdmc_directory); 315 auto archive = std::make_unique<SDMCArchive>(sdmc_directory);
39 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); 316 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
40} 317}
41 318
diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h
index 88e855351..9d99b110c 100644
--- a/src/core/file_sys/archive_sdmc.h
+++ b/src/core/file_sys/archive_sdmc.h
@@ -14,6 +14,32 @@
14 14
15namespace FileSys { 15namespace FileSys {
16 16
17/// Archive backend for SDMC archive
18class SDMCArchive : public ArchiveBackend {
19public:
20 SDMCArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
21
22 std::string GetName() const override {
23 return "SDMCArchive: " + mount_point;
24 }
25
26 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
27 const Mode& mode) const override;
28 ResultCode DeleteFile(const Path& path) const override;
29 ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
30 ResultCode DeleteDirectory(const Path& path) const override;
31 ResultCode DeleteDirectoryRecursively(const Path& path) const override;
32 ResultCode CreateFile(const Path& path, u64 size) const override;
33 ResultCode CreateDirectory(const Path& path) const override;
34 ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
35 ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
36 u64 GetFreeBytes() const override;
37
38protected:
39 ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const;
40 std::string mount_point;
41};
42
17/// File system interface to the SDMC archive 43/// File system interface to the SDMC archive
18class ArchiveFactory_SDMC final : public ArchiveFactory { 44class ArchiveFactory_SDMC final : public ArchiveFactory {
19public: 45public:
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp
new file mode 100644
index 000000000..2aafc9b1d
--- /dev/null
+++ b/src/core/file_sys/archive_sdmcwriteonly.cpp
@@ -0,0 +1,70 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include "common/file_util.h"
7#include "core/file_sys/archive_sdmcwriteonly.h"
8#include "core/file_sys/directory_backend.h"
9#include "core/file_sys/errors.h"
10#include "core/file_sys/file_backend.h"
11#include "core/settings.h"
12
13////////////////////////////////////////////////////////////////////////////////////////////////////
14// FileSys namespace
15
16namespace FileSys {
17
18ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
19 const Mode& mode) const {
20 if (mode.read_flag) {
21 LOG_ERROR(Service_FS, "Read flag is not supported");
22 return ERROR_INVALID_READ_FLAG;
23 }
24 return SDMCArchive::OpenFileBase(path, mode);
25}
26
27ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(
28 const Path& path) const {
29 LOG_ERROR(Service_FS, "Not supported");
30 return ERROR_UNSUPPORTED_OPEN_FLAGS;
31}
32
33ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point)
34 : sdmc_directory(mount_point) {
35 LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str());
36}
37
38bool ArchiveFactory_SDMCWriteOnly::Initialize() {
39 if (!Settings::values.use_virtual_sd) {
40 LOG_WARNING(Service_FS, "SDMC disabled by config.");
41 return false;
42 }
43
44 if (!FileUtil::CreateFullPath(sdmc_directory)) {
45 LOG_ERROR(Service_FS, "Unable to create SDMC path.");
46 return false;
47 }
48
49 return true;
50}
51
52ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) {
53 auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory);
54 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
55}
56
57ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
58 const FileSys::ArchiveFormatInfo& format_info) {
59 // TODO(wwylele): hwtest this
60 LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
61 return ResultCode(-1);
62}
63
64ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const {
65 // TODO(Subv): Implement
66 LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
67 return ResultCode(-1);
68}
69
70} // namespace FileSys
diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h
new file mode 100644
index 000000000..ed977485a
--- /dev/null
+++ b/src/core/file_sys/archive_sdmcwriteonly.h
@@ -0,0 +1,57 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/file_sys/archive_sdmc.h"
8
9////////////////////////////////////////////////////////////////////////////////////////////////////
10// FileSys namespace
11
12namespace FileSys {
13
14/**
15 * Archive backend for SDMC write-only archive.
16 * The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for
17 * - OpenDirectory is unsupported;
18 * - OpenFile with read flag is unsupported.
19 */
20class SDMCWriteOnlyArchive : public SDMCArchive {
21public:
22 SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {}
23
24 std::string GetName() const override {
25 return "SDMCWriteOnlyArchive: " + mount_point;
26 }
27
28 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
29 const Mode& mode) const override;
30
31 ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
32};
33
34/// File system interface to the SDMC write-only archive
35class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory {
36public:
37 ArchiveFactory_SDMCWriteOnly(const std::string& mount_point);
38
39 /**
40 * Initialize the archive.
41 * @return true if it initialized successfully
42 */
43 bool Initialize();
44
45 std::string GetName() const override {
46 return "SDMCWriteOnly";
47 }
48
49 ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
50 ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override;
51 ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
52
53private:
54 std::string sdmc_directory;
55};
56
57} // namespace FileSys
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp
index 48ebc0ed4..54e7793e0 100644
--- a/src/core/file_sys/archive_systemsavedata.cpp
+++ b/src/core/file_sys/archive_systemsavedata.cpp
@@ -9,7 +9,7 @@
9#include "common/file_util.h" 9#include "common/file_util.h"
10#include "common/string_util.h" 10#include "common/string_util.h"
11#include "core/file_sys/archive_systemsavedata.h" 11#include "core/file_sys/archive_systemsavedata.h"
12#include "core/file_sys/disk_archive.h" 12#include "core/file_sys/savedata_archive.h"
13#include "core/hle/service/fs/archive.h" 13#include "core/hle/service/fs/archive.h"
14 14
15//////////////////////////////////////////////////////////////////////////////////////////////////// 15////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c
56 return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, 56 return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
57 ErrorSummary::InvalidState, ErrorLevel::Status); 57 ErrorSummary::InvalidState, ErrorLevel::Status);
58 } 58 }
59 auto archive = std::make_unique<DiskArchive>(fullpath); 59 auto archive = std::make_unique<SaveDataArchive>(fullpath);
60 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); 60 return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
61} 61}
62 62
diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h
index b55e382ef..0c93f2074 100644
--- a/src/core/file_sys/directory_backend.h
+++ b/src/core/file_sys/directory_backend.h
@@ -41,12 +41,6 @@ public:
41 virtual ~DirectoryBackend() {} 41 virtual ~DirectoryBackend() {}
42 42
43 /** 43 /**
44 * Open the directory
45 * @return true if the directory opened correctly
46 */
47 virtual bool Open() = 0;
48
49 /**
50 * List files contained in the directory 44 * List files contained in the directory
51 * @param count Number of entries to return at once in entries 45 * @param count Number of entries to return at once in entries
52 * @param entries Buffer to read data into 46 * @param entries Buffer to read data into
diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp
index 2f05af361..a243d9a13 100644
--- a/src/core/file_sys/disk_archive.cpp
+++ b/src/core/file_sys/disk_archive.cpp
@@ -15,144 +15,8 @@
15 15
16namespace FileSys { 16namespace FileSys {
17 17
18ResultVal<std::unique_ptr<FileBackend>> DiskArchive::OpenFile(const Path& path,
19 const Mode mode) const {
20 LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
21 auto file = std::make_unique<DiskFile>(*this, path, mode);
22 ResultCode result = file->Open();
23 if (result.IsError())
24 return result;
25 return MakeResult<std::unique_ptr<FileBackend>>(std::move(file));
26}
27
28ResultCode DiskArchive::DeleteFile(const Path& path) const {
29 std::string file_path = mount_point + path.AsString();
30
31 if (FileUtil::IsDirectory(file_path))
32 return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
33 ErrorLevel::Status);
34
35 if (!FileUtil::Exists(file_path))
36 return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
37 ErrorLevel::Status);
38
39 if (FileUtil::Delete(file_path))
40 return RESULT_SUCCESS;
41
42 return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
43 ErrorLevel::Status);
44}
45
46bool DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
47 return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
48}
49
50bool DiskArchive::DeleteDirectory(const Path& path) const {
51 return FileUtil::DeleteDir(mount_point + path.AsString());
52}
53
54bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const {
55 return FileUtil::DeleteDirRecursively(mount_point + path.AsString());
56}
57
58ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const {
59 std::string full_path = mount_point + path.AsString();
60
61 if (FileUtil::IsDirectory(full_path))
62 return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
63 ErrorLevel::Status);
64
65 if (FileUtil::Exists(full_path))
66 return ResultCode(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
67 ErrorSummary::NothingHappened, ErrorLevel::Status);
68
69 if (size == 0) {
70 FileUtil::CreateEmptyFile(full_path);
71 return RESULT_SUCCESS;
72 }
73
74 FileUtil::IOFile file(full_path, "wb");
75 // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
76 // We do this by seeking to the right size, then writing a single null byte.
77 if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1)
78 return RESULT_SUCCESS;
79
80 return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
81 ErrorLevel::Info);
82}
83
84bool DiskArchive::CreateDirectory(const Path& path) const {
85 return FileUtil::CreateDir(mount_point + path.AsString());
86}
87
88bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
89 return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
90}
91
92std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const {
93 LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str());
94 auto directory = std::make_unique<DiskDirectory>(*this, path);
95 if (!directory->Open())
96 return nullptr;
97 return std::move(directory);
98}
99
100u64 DiskArchive::GetFreeBytes() const {
101 // TODO: Stubbed to return 1GiB
102 return 1024 * 1024 * 1024;
103}
104
105////////////////////////////////////////////////////////////////////////////////////////////////////
106
107DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) {
108 // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
109 // the root directory we set while opening the archive.
110 // For example, opening /../../etc/passwd can give the emulated program your users list.
111 this->path = archive.mount_point + path.AsString();
112 this->mode.hex = mode.hex;
113}
114
115ResultCode DiskFile::Open() {
116 if (FileUtil::IsDirectory(path))
117 return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
118 ErrorLevel::Status);
119
120 // Specifying only the Create flag is invalid
121 if (mode.create_flag && !mode.read_flag && !mode.write_flag) {
122 return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
123 ErrorSummary::Canceled, ErrorLevel::Status);
124 }
125
126 if (!FileUtil::Exists(path)) {
127 if (!mode.create_flag) {
128 LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
129 path.c_str());
130 return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS,
131 ErrorSummary::NotFound, ErrorLevel::Status);
132 } else {
133 // Create the file
134 FileUtil::CreateEmptyFile(path);
135 }
136 }
137
138 std::string mode_string = "";
139 if (mode.write_flag)
140 mode_string += "r+"; // Files opened with Write access can be read from
141 else if (mode.read_flag)
142 mode_string += "r";
143
144 // Open the file in binary mode, to avoid problems with CR/LF on Windows systems
145 mode_string += "b";
146
147 file = std::make_unique<FileUtil::IOFile>(path, mode_string.c_str());
148 if (file->IsOpen())
149 return RESULT_SUCCESS;
150 return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
151 ErrorLevel::Status);
152}
153
154ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const { 18ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
155 if (!mode.read_flag && !mode.write_flag) 19 if (!mode.read_flag)
156 return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS, 20 return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
157 ErrorSummary::Canceled, ErrorLevel::Status); 21 ErrorSummary::Canceled, ErrorLevel::Status);
158 22
@@ -189,21 +53,11 @@ bool DiskFile::Close() const {
189 53
190//////////////////////////////////////////////////////////////////////////////////////////////////// 54////////////////////////////////////////////////////////////////////////////////////////////////////
191 55
192DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() { 56DiskDirectory::DiskDirectory(const std::string& path) : directory() {
193 // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
194 // the root directory we set while opening the archive.
195 // For example, opening /../../usr/bin can give the emulated program your installed programs.
196 this->path = archive.mount_point + path.AsString();
197}
198
199bool DiskDirectory::Open() {
200 if (!FileUtil::IsDirectory(path))
201 return false;
202 unsigned size = FileUtil::ScanDirectoryTree(path, directory); 57 unsigned size = FileUtil::ScanDirectoryTree(path, directory);
203 directory.size = size; 58 directory.size = size;
204 directory.isDirectory = true; 59 directory.isDirectory = true;
205 children_iterator = directory.children.begin(); 60 children_iterator = directory.children.begin();
206 return true;
207} 61}
208 62
209u32 DiskDirectory::Read(const u32 count, Entry* entries) { 63u32 DiskDirectory::Read(const u32 count, Entry* entries) {
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h
index 59ebb2002..eb9166df6 100644
--- a/src/core/file_sys/disk_archive.h
+++ b/src/core/file_sys/disk_archive.h
@@ -20,43 +20,13 @@
20 20
21namespace FileSys { 21namespace FileSys {
22 22
23/**
24 * Helper which implements a backend accessing the host machine's filesystem.
25 * This should be subclassed by concrete archive types, which will provide the
26 * base directory on the host filesystem and override any required functionality.
27 */
28class DiskArchive : public ArchiveBackend {
29public:
30 DiskArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
31
32 virtual std::string GetName() const override {
33 return "DiskArchive: " + mount_point;
34 }
35
36 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
37 const Mode mode) const override;
38 ResultCode DeleteFile(const Path& path) const override;
39 bool RenameFile(const Path& src_path, const Path& dest_path) const override;
40 bool DeleteDirectory(const Path& path) const override;
41 bool DeleteDirectoryRecursively(const Path& path) const override;
42 ResultCode CreateFile(const Path& path, u64 size) const override;
43 bool CreateDirectory(const Path& path) const override;
44 bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
45 std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
46 u64 GetFreeBytes() const override;
47
48protected:
49 friend class DiskFile;
50 friend class DiskDirectory;
51
52 std::string mount_point;
53};
54
55class DiskFile : public FileBackend { 23class DiskFile : public FileBackend {
56public: 24public:
57 DiskFile(const DiskArchive& archive, const Path& path, const Mode mode); 25 DiskFile(FileUtil::IOFile&& file_, const Mode& mode_)
26 : file(new FileUtil::IOFile(std::move(file_))) {
27 mode.hex = mode_.hex;
28 }
58 29
59 ResultCode Open() override;
60 ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; 30 ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
61 ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; 31 ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
62 u64 GetSize() const override; 32 u64 GetSize() const override;
@@ -68,20 +38,18 @@ public:
68 } 38 }
69 39
70protected: 40protected:
71 std::string path;
72 Mode mode; 41 Mode mode;
73 std::unique_ptr<FileUtil::IOFile> file; 42 std::unique_ptr<FileUtil::IOFile> file;
74}; 43};
75 44
76class DiskDirectory : public DirectoryBackend { 45class DiskDirectory : public DirectoryBackend {
77public: 46public:
78 DiskDirectory(const DiskArchive& archive, const Path& path); 47 DiskDirectory(const std::string& path);
79 48
80 ~DiskDirectory() override { 49 ~DiskDirectory() override {
81 Close(); 50 Close();
82 } 51 }
83 52
84 bool Open() override;
85 u32 Read(const u32 count, Entry* entries) override; 53 u32 Read(const u32 count, Entry* entries) override;
86 54
87 bool Close() const override { 55 bool Close() const override {
@@ -89,7 +57,6 @@ public:
89 } 57 }
90 58
91protected: 59protected:
92 std::string path;
93 u32 total_entries_in_directory; 60 u32 total_entries_in_directory;
94 FileUtil::FSTEntry directory; 61 FileUtil::FSTEntry directory;
95 62
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
new file mode 100644
index 000000000..fd1b07df0
--- /dev/null
+++ b/src/core/file_sys/errors.h
@@ -0,0 +1,40 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hle/result.h"
6
7namespace FileSys {
8
9const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS,
10 ErrorSummary::InvalidArgument, ErrorLevel::Usage);
11const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags,
12 ErrorModule::FS, ErrorSummary::NotSupported,
13 ErrorLevel::Usage);
14const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
15 ErrorSummary::Canceled, ErrorLevel::Status);
16const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS,
17 ErrorSummary::InvalidArgument, ErrorLevel::Usage);
18const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS,
19 ErrorSummary::NotFound, ErrorLevel::Status);
20const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS,
21 ErrorSummary::NotFound, ErrorLevel::Status);
22const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS,
23 ErrorSummary::NotFound, ErrorLevel::Status);
24const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory,
25 ErrorModule::FS, ErrorSummary::NotSupported,
26 ErrorLevel::Usage);
27const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile,
28 ErrorModule::FS, ErrorSummary::Canceled,
29 ErrorLevel::Status);
30const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists,
31 ErrorModule::FS, ErrorSummary::NothingHappened,
32 ErrorLevel::Status);
33const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS,
34 ErrorSummary::NothingHappened, ErrorLevel::Status);
35const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
36 ErrorSummary::NothingHappened, ErrorLevel::Status);
37const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS,
38 ErrorSummary::Canceled, ErrorLevel::Status);
39
40} // namespace FileSys
diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h
index ed997537f..5e7c2bab4 100644
--- a/src/core/file_sys/file_backend.h
+++ b/src/core/file_sys/file_backend.h
@@ -19,12 +19,6 @@ public:
19 virtual ~FileBackend() {} 19 virtual ~FileBackend() {}
20 20
21 /** 21 /**
22 * Open the file
23 * @return Result of the file operation
24 */
25 virtual ResultCode Open() = 0;
26
27 /**
28 * Read data from the file 22 * Read data from the file
29 * @param offset Offset in bytes to start reading data from 23 * @param offset Offset in bytes to start reading data from
30 * @param length Length in bytes of data to read from file 24 * @param length Length in bytes of data to read from file
diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp
index af59d296d..2735d2e3c 100644
--- a/src/core/file_sys/ivfc_archive.cpp
+++ b/src/core/file_sys/ivfc_archive.cpp
@@ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const {
18} 18}
19 19
20ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, 20ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
21 const Mode mode) const { 21 const Mode& mode) const {
22 return MakeResult<std::unique_ptr<FileBackend>>( 22 return MakeResult<std::unique_ptr<FileBackend>>(
23 std::make_unique<IVFCFile>(romfs_file, data_offset, data_size)); 23 std::make_unique<IVFCFile>(romfs_file, data_offset, data_size));
24} 24}
@@ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const {
31 ErrorLevel::Status); 31 ErrorLevel::Status);
32} 32}
33 33
34bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { 34ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
35 LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", 35 LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
36 GetName().c_str()); 36 GetName().c_str());
37 return false; 37 // TODO(wwylele): Use correct error code
38 return ResultCode(-1);
38} 39}
39 40
40bool IVFCArchive::DeleteDirectory(const Path& path) const { 41ResultCode IVFCArchive::DeleteDirectory(const Path& path) const {
41 LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", 42 LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
42 GetName().c_str()); 43 GetName().c_str());
43 return false; 44 // TODO(wwylele): Use correct error code
45 return ResultCode(-1);
44} 46}
45 47
46bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const { 48ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
47 LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).", 49 LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
48 GetName().c_str()); 50 GetName().c_str());
49 return false; 51 // TODO(wwylele): Use correct error code
52 return ResultCode(-1);
50} 53}
51 54
52ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const { 55ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
@@ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
57 ErrorLevel::Permanent); 60 ErrorLevel::Permanent);
58} 61}
59 62
60bool IVFCArchive::CreateDirectory(const Path& path) const { 63ResultCode IVFCArchive::CreateDirectory(const Path& path) const {
61 LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).", 64 LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).",
62 GetName().c_str()); 65 GetName().c_str());
63 return false; 66 // TODO(wwylele): Use correct error code
67 return ResultCode(-1);
64} 68}
65 69
66bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { 70ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
67 LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).", 71 LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
68 GetName().c_str()); 72 GetName().c_str());
69 return false; 73 // TODO(wwylele): Use correct error code
74 return ResultCode(-1);
70} 75}
71 76
72std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const { 77ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const {
73 return std::make_unique<IVFCDirectory>(); 78 return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>());
74} 79}
75 80
76u64 IVFCArchive::GetFreeBytes() const { 81u64 IVFCArchive::GetFreeBytes() const {
diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h
index 2fbb3a568..e6fbdfb1f 100644
--- a/src/core/file_sys/ivfc_archive.h
+++ b/src/core/file_sys/ivfc_archive.h
@@ -33,15 +33,15 @@ public:
33 std::string GetName() const override; 33 std::string GetName() const override;
34 34
35 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, 35 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
36 const Mode mode) const override; 36 const Mode& mode) const override;
37 ResultCode DeleteFile(const Path& path) const override; 37 ResultCode DeleteFile(const Path& path) const override;
38 bool RenameFile(const Path& src_path, const Path& dest_path) const override; 38 ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
39 bool DeleteDirectory(const Path& path) const override; 39 ResultCode DeleteDirectory(const Path& path) const override;
40 bool DeleteDirectoryRecursively(const Path& path) const override; 40 ResultCode DeleteDirectoryRecursively(const Path& path) const override;
41 ResultCode CreateFile(const Path& path, u64 size) const override; 41 ResultCode CreateFile(const Path& path, u64 size) const override;
42 bool CreateDirectory(const Path& path) const override; 42 ResultCode CreateDirectory(const Path& path) const override;
43 bool RenameDirectory(const Path& src_path, const Path& dest_path) const override; 43 ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
44 std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override; 44 ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
45 u64 GetFreeBytes() const override; 45 u64 GetFreeBytes() const override;
46 46
47protected: 47protected:
@@ -55,9 +55,6 @@ public:
55 IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) 55 IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
56 : romfs_file(file), data_offset(offset), data_size(size) {} 56 : romfs_file(file), data_offset(offset), data_size(size) {}
57 57
58 ResultCode Open() override {
59 return RESULT_SUCCESS;
60 }
61 ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; 58 ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
62 ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; 59 ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
63 u64 GetSize() const override; 60 u64 GetSize() const override;
@@ -75,9 +72,6 @@ private:
75 72
76class IVFCDirectory : public DirectoryBackend { 73class IVFCDirectory : public DirectoryBackend {
77public: 74public:
78 bool Open() override {
79 return false;
80 }
81 u32 Read(const u32 count, Entry* entries) override { 75 u32 Read(const u32 count, Entry* entries) override {
82 return 0; 76 return 0;
83 } 77 }
diff --git a/src/core/file_sys/path_parser.cpp b/src/core/file_sys/path_parser.cpp
new file mode 100644
index 000000000..5a89b02b8
--- /dev/null
+++ b/src/core/file_sys/path_parser.cpp
@@ -0,0 +1,98 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <set>
7#include "common/file_util.h"
8#include "common/string_util.h"
9#include "core/file_sys/path_parser.h"
10
11namespace FileSys {
12
13PathParser::PathParser(const Path& path) {
14 if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
15 is_valid = false;
16 return;
17 }
18
19 auto path_string = path.AsString();
20 if (path_string.size() == 0 || path_string[0] != '/') {
21 is_valid = false;
22 return;
23 }
24
25 // Filter out invalid characters for the host system.
26 // Although some of these characters are valid on 3DS, they are unlikely to be used by games.
27 if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
28 static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
29 return invalid_chars.find(c) != invalid_chars.end();
30 }) != path_string.end()) {
31 is_valid = false;
32 return;
33 }
34
35 Common::SplitString(path_string, '/', path_sequence);
36
37 auto begin = path_sequence.begin();
38 auto end = path_sequence.end();
39 end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
40 path_sequence = std::vector<std::string>(begin, end);
41
42 // checks if the path is out of bounds.
43 int level = 0;
44 for (auto& node : path_sequence) {
45 if (node == "..") {
46 --level;
47 if (level < 0) {
48 is_valid = false;
49 return;
50 }
51 } else {
52 ++level;
53 }
54 }
55
56 is_valid = true;
57 is_root = level == 0;
58}
59
60PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const {
61 auto path = mount_point;
62 if (!FileUtil::IsDirectory(path))
63 return InvalidMountPoint;
64 if (path_sequence.empty()) {
65 return DirectoryFound;
66 }
67
68 for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
69 if (path.back() != '/')
70 path += '/';
71 path += *iter;
72
73 if (!FileUtil::Exists(path))
74 return PathNotFound;
75 if (FileUtil::IsDirectory(path))
76 continue;
77 return FileInPath;
78 }
79
80 path += "/" + path_sequence.back();
81 if (!FileUtil::Exists(path))
82 return NotFound;
83 if (FileUtil::IsDirectory(path))
84 return DirectoryFound;
85 return FileFound;
86}
87
88std::string PathParser::BuildHostPath(const std::string& mount_point) const {
89 std::string path = mount_point;
90 for (auto& node : path_sequence) {
91 if (path.back() != '/')
92 path += '/';
93 path += node;
94 }
95 return path;
96}
97
98} // namespace FileSys
diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h
new file mode 100644
index 000000000..990802579
--- /dev/null
+++ b/src/core/file_sys/path_parser.h
@@ -0,0 +1,61 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8#include <vector>
9#include "core/file_sys/archive_backend.h"
10
11namespace FileSys {
12
13/**
14 * A helper class parsing and verifying a string-type Path.
15 * Every archives with a sub file system should use this class to parse the path argument and check
16 * the status of the file / directory in question on the host file system.
17 */
18class PathParser {
19public:
20 PathParser(const Path& path);
21
22 /**
23 * Checks if the Path is valid.
24 * This function should be called once a PathParser is constructed.
25 * A Path is valid if:
26 * - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
27 * - it starts with "/" (this seems a hard requirement in real 3DS),
28 * - it doesn't contain invalid characters, and
29 * - it doesn't go out of the root directory using "..".
30 */
31 bool IsValid() const {
32 return is_valid;
33 }
34
35 /// Checks if the Path represents the root directory.
36 bool IsRootDirectory() const {
37 return is_root;
38 }
39
40 enum HostStatus {
41 InvalidMountPoint,
42 PathNotFound, // "/a/b/c" when "a" doesn't exist
43 FileInPath, // "/a/b/c" when "a" is a file
44 FileFound, // "/a/b/c" when "c" is a file
45 DirectoryFound, // "/a/b/c" when "c" is a directory
46 NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist
47 };
48
49 /// Checks the status of the specified file / directory by the Path on the host file system.
50 HostStatus GetHostStatus(const std::string& mount_point) const;
51
52 /// Builds a full path on the host file system.
53 std::string BuildHostPath(const std::string& mount_point) const;
54
55private:
56 std::vector<std::string> path_sequence;
57 bool is_valid{};
58 bool is_root{};
59};
60
61} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
new file mode 100644
index 000000000..f2e6a06bc
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -0,0 +1,283 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/file_util.h"
6#include "core/file_sys/disk_archive.h"
7#include "core/file_sys/errors.h"
8#include "core/file_sys/path_parser.h"
9#include "core/file_sys/savedata_archive.h"
10
11////////////////////////////////////////////////////////////////////////////////////////////////////
12// FileSys namespace
13
14namespace FileSys {
15
16ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
17 const Mode& mode) const {
18 LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
19
20 const PathParser path_parser(path);
21
22 if (!path_parser.IsValid()) {
23 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
24 return ERROR_INVALID_PATH;
25 }
26
27 if (mode.hex == 0) {
28 LOG_ERROR(Service_FS, "Empty open mode");
29 return ERROR_UNSUPPORTED_OPEN_FLAGS;
30 }
31
32 if (mode.create_flag && !mode.write_flag) {
33 LOG_ERROR(Service_FS, "Create flag set but write flag not set");
34 return ERROR_UNSUPPORTED_OPEN_FLAGS;
35 }
36
37 const auto full_path = path_parser.BuildHostPath(mount_point);
38
39 switch (path_parser.GetHostStatus(mount_point)) {
40 case PathParser::InvalidMountPoint:
41 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
42 return ERROR_FILE_NOT_FOUND;
43 case PathParser::PathNotFound:
44 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
45 return ERROR_PATH_NOT_FOUND;
46 case PathParser::FileInPath:
47 case PathParser::DirectoryFound:
48 LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
49 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
50 case PathParser::NotFound:
51 if (!mode.create_flag) {
52 LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
53 full_path.c_str());
54 return ERROR_FILE_NOT_FOUND;
55 } else {
56 // Create the file
57 FileUtil::CreateEmptyFile(full_path);
58 }
59 break;
60 }
61
62 FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
63 if (!file.IsOpen()) {
64 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
65 return ERROR_FILE_NOT_FOUND;
66 }
67
68 auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
69 return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
70}
71
72ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
73 const PathParser path_parser(path);
74
75 if (!path_parser.IsValid()) {
76 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
77 return ERROR_INVALID_PATH;
78 }
79
80 const auto full_path = path_parser.BuildHostPath(mount_point);
81
82 switch (path_parser.GetHostStatus(mount_point)) {
83 case PathParser::InvalidMountPoint:
84 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
85 return ERROR_FILE_NOT_FOUND;
86 case PathParser::PathNotFound:
87 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
88 return ERROR_PATH_NOT_FOUND;
89 case PathParser::FileInPath:
90 case PathParser::DirectoryFound:
91 case PathParser::NotFound:
92 LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
93 return ERROR_FILE_NOT_FOUND;
94 }
95
96 if (FileUtil::Delete(full_path)) {
97 return RESULT_SUCCESS;
98 }
99
100 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
101 return ERROR_FILE_NOT_FOUND;
102}
103
104ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
105 if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
106 return RESULT_SUCCESS;
107 }
108
109 // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
110 // exist or similar. Verify.
111 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
112 ErrorSummary::NothingHappened, ErrorLevel::Status);
113}
114
115template <typename T>
116static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
117 T deleter) {
118 const PathParser path_parser(path);
119
120 if (!path_parser.IsValid()) {
121 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
122 return ERROR_INVALID_PATH;
123 }
124
125 if (path_parser.IsRootDirectory())
126 return ERROR_DIRECTORY_NOT_EMPTY;
127
128 const auto full_path = path_parser.BuildHostPath(mount_point);
129
130 switch (path_parser.GetHostStatus(mount_point)) {
131 case PathParser::InvalidMountPoint:
132 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
133 return ERROR_PATH_NOT_FOUND;
134 case PathParser::PathNotFound:
135 case PathParser::NotFound:
136 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
137 return ERROR_PATH_NOT_FOUND;
138 case PathParser::FileInPath:
139 case PathParser::FileFound:
140 LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
141 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
142 }
143
144 if (deleter(full_path)) {
145 return RESULT_SUCCESS;
146 }
147
148 LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
149 return ERROR_DIRECTORY_NOT_EMPTY;
150}
151
152ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
153 return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
154}
155
156ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
157 return DeleteDirectoryHelper(
158 path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
159}
160
161ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
162 const PathParser path_parser(path);
163
164 if (!path_parser.IsValid()) {
165 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
166 return ERROR_INVALID_PATH;
167 }
168
169 const auto full_path = path_parser.BuildHostPath(mount_point);
170
171 switch (path_parser.GetHostStatus(mount_point)) {
172 case PathParser::InvalidMountPoint:
173 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
174 return ERROR_FILE_NOT_FOUND;
175 case PathParser::PathNotFound:
176 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
177 return ERROR_PATH_NOT_FOUND;
178 case PathParser::FileInPath:
179 LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
180 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
181 case PathParser::DirectoryFound:
182 case PathParser::FileFound:
183 LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
184 return ERROR_FILE_ALREADY_EXISTS;
185 }
186
187 if (size == 0) {
188 FileUtil::CreateEmptyFile(full_path);
189 return RESULT_SUCCESS;
190 }
191
192 FileUtil::IOFile file(full_path, "wb");
193 // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
194 // We do this by seeking to the right size, then writing a single null byte.
195 if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
196 return RESULT_SUCCESS;
197 }
198
199 LOG_ERROR(Service_FS, "Too large file");
200 return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
201 ErrorLevel::Info);
202}
203
204ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
205 const PathParser path_parser(path);
206
207 if (!path_parser.IsValid()) {
208 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
209 return ERROR_INVALID_PATH;
210 }
211
212 const auto full_path = path_parser.BuildHostPath(mount_point);
213
214 switch (path_parser.GetHostStatus(mount_point)) {
215 case PathParser::InvalidMountPoint:
216 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
217 return ERROR_FILE_NOT_FOUND;
218 case PathParser::PathNotFound:
219 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
220 return ERROR_PATH_NOT_FOUND;
221 case PathParser::FileInPath:
222 LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
223 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
224 case PathParser::DirectoryFound:
225 case PathParser::FileFound:
226 LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
227 return ERROR_DIRECTORY_ALREADY_EXISTS;
228 }
229
230 if (FileUtil::CreateDir(mount_point + path.AsString())) {
231 return RESULT_SUCCESS;
232 }
233
234 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
235 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
236 ErrorLevel::Status);
237}
238
239ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
240 if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
241 return RESULT_SUCCESS;
242
243 // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
244 // exist or similar. Verify.
245 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
246 ErrorSummary::NothingHappened, ErrorLevel::Status);
247}
248
249ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
250 const Path& path) const {
251 const PathParser path_parser(path);
252
253 if (!path_parser.IsValid()) {
254 LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
255 return ERROR_INVALID_PATH;
256 }
257
258 const auto full_path = path_parser.BuildHostPath(mount_point);
259
260 switch (path_parser.GetHostStatus(mount_point)) {
261 case PathParser::InvalidMountPoint:
262 LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
263 return ERROR_FILE_NOT_FOUND;
264 case PathParser::PathNotFound:
265 case PathParser::NotFound:
266 LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
267 return ERROR_PATH_NOT_FOUND;
268 case PathParser::FileInPath:
269 case PathParser::FileFound:
270 LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
271 return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
272 }
273
274 auto directory = std::make_unique<DiskDirectory>(full_path);
275 return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
276}
277
278u64 SaveDataArchive::GetFreeBytes() const {
279 // TODO: Stubbed to return 1GiB
280 return 1024 * 1024 * 1024;
281}
282
283} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h
new file mode 100644
index 000000000..2fb6c452a
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.h
@@ -0,0 +1,43 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8#include "core/file_sys/archive_backend.h"
9#include "core/file_sys/directory_backend.h"
10#include "core/file_sys/file_backend.h"
11#include "core/hle/result.h"
12
13////////////////////////////////////////////////////////////////////////////////////////////////////
14// FileSys namespace
15
16namespace FileSys {
17
18/// Archive backend for general save data archive type (SaveData and SystemSaveData)
19class SaveDataArchive : public ArchiveBackend {
20public:
21 SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
22
23 std::string GetName() const override {
24 return "SaveDataArchive: " + mount_point;
25 }
26
27 ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
28 const Mode& mode) const override;
29 ResultCode DeleteFile(const Path& path) const override;
30 ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
31 ResultCode DeleteDirectory(const Path& path) const override;
32 ResultCode DeleteDirectoryRecursively(const Path& path) const override;
33 ResultCode CreateFile(const Path& path, u64 size) const override;
34 ResultCode CreateDirectory(const Path& path) const override;
35 ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
36 ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
37 u64 GetFreeBytes() const override;
38
39protected:
40 std::string mount_point;
41};
42
43} // namespace FileSys
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 7f8d8e00d..f7356f9d8 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -20,15 +20,24 @@ enum class ErrorDescription : u32 {
20 OS_InvalidBufferDescriptor = 48, 20 OS_InvalidBufferDescriptor = 48,
21 WrongAddress = 53, 21 WrongAddress = 53,
22 FS_ArchiveNotMounted = 101, 22 FS_ArchiveNotMounted = 101,
23 FS_FileNotFound = 112,
24 FS_PathNotFound = 113,
23 FS_NotFound = 120, 25 FS_NotFound = 120,
26 FS_FileAlreadyExists = 180,
27 FS_DirectoryAlreadyExists = 185,
24 FS_AlreadyExists = 190, 28 FS_AlreadyExists = 190,
25 FS_InvalidOpenFlags = 230, 29 FS_InvalidOpenFlags = 230,
30 FS_DirectoryNotEmpty = 240,
26 FS_NotAFile = 250, 31 FS_NotAFile = 250,
27 FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive 32 FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
28 OutofRangeOrMisalignedAddress = 33 OutofRangeOrMisalignedAddress =
29 513, // TODO(purpasmart): Check if this name fits its actual usage 34 513, // TODO(purpasmart): Check if this name fits its actual usage
30 GPU_FirstInitialization = 519, 35 GPU_FirstInitialization = 519,
36 FS_InvalidReadFlag = 700,
31 FS_InvalidPath = 702, 37 FS_InvalidPath = 702,
38 FS_WriteBeyondEnd = 705,
39 FS_UnsupportedOpenFlags = 760,
40 FS_UnexpectedFileOrDirectory = 770,
32 InvalidSection = 1000, 41 InvalidSection = 1000,
33 TooLarge = 1001, 42 TooLarge = 1001,
34 NotAuthorized = 1002, 43 NotAuthorized = 1002,
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index d3d0f3b55..d554c3f54 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -360,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da
360} 360}
361 361
362ResultCode DeleteConfigNANDSaveFile() { 362ResultCode DeleteConfigNANDSaveFile() {
363 FileSys::Path path("config"); 363 FileSys::Path path("/config");
364 return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); 364 return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path);
365} 365}
366 366
@@ -369,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() {
369 mode.write_flag.Assign(1); 369 mode.write_flag.Assign(1);
370 mode.create_flag.Assign(1); 370 mode.create_flag.Assign(1);
371 371
372 FileSys::Path path("config"); 372 FileSys::Path path("/config");
373 373
374 auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode); 374 auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
375 ASSERT_MSG(config_result.Succeeded(), "could not open file"); 375 ASSERT_MSG(config_result.Succeeded(), "could not open file");
@@ -383,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() {
383ResultCode FormatConfig() { 383ResultCode FormatConfig() {
384 ResultCode res = DeleteConfigNANDSaveFile(); 384 ResultCode res = DeleteConfigNANDSaveFile();
385 // The delete command fails if the file doesn't exist, so we have to check that too 385 // The delete command fails if the file doesn't exist, so we have to check that too
386 if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound) 386 if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) {
387 return res; 387 return res;
388 }
388 // Delete the old data 389 // Delete the old data
389 cfg_config_file_buffer.fill(0); 390 cfg_config_file_buffer.fill(0);
390 // Create the header 391 // Create the header
@@ -510,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() {
510 511
511 cfg_system_save_data_archive = *archive_result; 512 cfg_system_save_data_archive = *archive_result;
512 513
513 FileSys::Path config_path("config"); 514 FileSys::Path config_path("/config");
514 FileSys::Mode open_mode = {}; 515 FileSys::Mode open_mode = {};
515 open_mode.read_flag.Assign(1); 516 open_mode.read_flag.Assign(1);
516 517
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 7f9696bfb..4c29784e8 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -15,9 +15,10 @@
15#include "common/logging/log.h" 15#include "common/logging/log.h"
16#include "core/file_sys/archive_backend.h" 16#include "core/file_sys/archive_backend.h"
17#include "core/file_sys/archive_extsavedata.h" 17#include "core/file_sys/archive_extsavedata.h"
18#include "core/file_sys/archive_ncch.h"
18#include "core/file_sys/archive_savedata.h" 19#include "core/file_sys/archive_savedata.h"
19#include "core/file_sys/archive_savedatacheck.h"
20#include "core/file_sys/archive_sdmc.h" 20#include "core/file_sys/archive_sdmc.h"
21#include "core/file_sys/archive_sdmcwriteonly.h"
21#include "core/file_sys/archive_systemsavedata.h" 22#include "core/file_sys/archive_systemsavedata.h"
22#include "core/file_sys/directory_backend.h" 23#include "core/file_sys/directory_backend.h"
23#include "core/file_sys/file_backend.h" 24#include "core/file_sys/file_backend.h"
@@ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle,
338 return ERR_INVALID_ARCHIVE_HANDLE; 339 return ERR_INVALID_ARCHIVE_HANDLE;
339 340
340 if (src_archive == dest_archive) { 341 if (src_archive == dest_archive) {
341 if (src_archive->RenameFile(src_path, dest_path)) 342 return src_archive->RenameFile(src_path, dest_path);
342 return RESULT_SUCCESS;
343 } else { 343 } else {
344 // TODO: Implement renaming across archives 344 // TODO: Implement renaming across archives
345 return UnimplementedFunction(ErrorModule::FS); 345 return UnimplementedFunction(ErrorModule::FS);
346 } 346 }
347
348 // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
349 // exist or similar. Verify.
350 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
351 ErrorSummary::NothingHappened, ErrorLevel::Status);
352} 347}
353 348
354ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { 349ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
@@ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
356 if (archive == nullptr) 351 if (archive == nullptr)
357 return ERR_INVALID_ARCHIVE_HANDLE; 352 return ERR_INVALID_ARCHIVE_HANDLE;
358 353
359 if (archive->DeleteDirectory(path)) 354 return archive->DeleteDirectory(path);
360 return RESULT_SUCCESS;
361 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
362 ErrorSummary::Canceled, ErrorLevel::Status);
363} 355}
364 356
365ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, 357ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
@@ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
368 if (archive == nullptr) 360 if (archive == nullptr)
369 return ERR_INVALID_ARCHIVE_HANDLE; 361 return ERR_INVALID_ARCHIVE_HANDLE;
370 362
371 if (archive->DeleteDirectoryRecursively(path)) 363 return archive->DeleteDirectoryRecursively(path);
372 return RESULT_SUCCESS;
373 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
374 ErrorSummary::Canceled, ErrorLevel::Status);
375} 364}
376 365
377ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, 366ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
@@ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
388 if (archive == nullptr) 377 if (archive == nullptr)
389 return ERR_INVALID_ARCHIVE_HANDLE; 378 return ERR_INVALID_ARCHIVE_HANDLE;
390 379
391 if (archive->CreateDirectory(path)) 380 return archive->CreateDirectory(path);
392 return RESULT_SUCCESS;
393 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
394 ErrorSummary::Canceled, ErrorLevel::Status);
395} 381}
396 382
397ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, 383ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
@@ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
404 return ERR_INVALID_ARCHIVE_HANDLE; 390 return ERR_INVALID_ARCHIVE_HANDLE;
405 391
406 if (src_archive == dest_archive) { 392 if (src_archive == dest_archive) {
407 if (src_archive->RenameDirectory(src_path, dest_path)) 393 return src_archive->RenameDirectory(src_path, dest_path);
408 return RESULT_SUCCESS;
409 } else { 394 } else {
410 // TODO: Implement renaming across archives 395 // TODO: Implement renaming across archives
411 return UnimplementedFunction(ErrorModule::FS); 396 return UnimplementedFunction(ErrorModule::FS);
412 } 397 }
413
414 // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
415 // exist or similar. Verify.
416 return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
417 ErrorSummary::NothingHappened, ErrorLevel::Status);
418} 398}
419 399
420ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, 400ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle,
@@ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
423 if (archive == nullptr) 403 if (archive == nullptr)
424 return ERR_INVALID_ARCHIVE_HANDLE; 404 return ERR_INVALID_ARCHIVE_HANDLE;
425 405
426 std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path); 406 auto backend = archive->OpenDirectory(path);
427 if (backend == nullptr) { 407 if (backend.Failed())
428 return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound, 408 return backend.Code();
429 ErrorLevel::Permanent);
430 }
431 409
432 auto directory = Kernel::SharedPtr<Directory>(new Directory(std::move(backend), path)); 410 auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path));
433 return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory)); 411 return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory));
434} 412}
435 413
@@ -549,6 +527,13 @@ void RegisterArchiveTypes() {
549 LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", 527 LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s",
550 sdmc_directory.c_str()); 528 sdmc_directory.c_str());
551 529
530 auto sdmcwo_factory = std::make_unique<FileSys::ArchiveFactory_SDMCWriteOnly>(sdmc_directory);
531 if (sdmcwo_factory->Initialize())
532 RegisterArchiveType(std::move(sdmcwo_factory), ArchiveIdCode::SDMCWriteOnly);
533 else
534 LOG_ERROR(Service_FS, "Can't instantiate SDMCWriteOnly archive with path %s",
535 sdmc_directory.c_str());
536
552 // Create the SaveData archive 537 // Create the SaveData archive
553 auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory); 538 auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory);
554 RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); 539 RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
@@ -569,10 +554,9 @@ void RegisterArchiveTypes() {
569 LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", 554 LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s",
570 sharedextsavedata_factory->GetMountPoint().c_str()); 555 sharedextsavedata_factory->GetMountPoint().c_str());
571 556
572 // Create the SaveDataCheck archive, basically a small variation of the RomFS archive 557 // Create the NCCH archive, basically a small variation of the RomFS archive
573 auto savedatacheck_factory = 558 auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory);
574 std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory); 559 RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH);
575 RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck);
576 560
577 auto systemsavedata_factory = 561 auto systemsavedata_factory =
578 std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); 562 std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory);
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 41a76285c..21ed9717b 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 {
33 SystemSaveData = 0x00000008, 33 SystemSaveData = 0x00000008,
34 SDMC = 0x00000009, 34 SDMC = 0x00000009,
35 SDMCWriteOnly = 0x0000000A, 35 SDMCWriteOnly = 0x0000000A,
36 SaveDataCheck = 0x2345678A, 36 NCCH = 0x2345678A,
37}; 37};
38 38
39/// Media types for the archives 39/// Media types for the archives
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 6e6b63329..cc859c14c 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -128,7 +128,7 @@ void Init() {
128 Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path); 128 Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path);
129 ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!"); 129 ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!");
130 130
131 FileSys::Path gamecoin_path("gamecoin.dat"); 131 FileSys::Path gamecoin_path("/gamecoin.dat");
132 FileSys::Mode open_mode = {}; 132 FileSys::Mode open_mode = {};
133 open_mode.write_flag.Assign(1); 133 open_mode.write_flag.Assign(1);
134 open_mode.create_flag.Assign(1); 134 open_mode.create_flag.Assign(1);
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 457c55571..89237e477 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,5 +1,7 @@
1set(SRCS 1set(SRCS
2 glad.cpp
2 tests.cpp 3 tests.cpp
4 core/file_sys/path_parser.cpp
3 ) 5 )
4 6
5set(HEADERS 7set(HEADERS
diff --git a/src/tests/core/file_sys/path_parser.cpp b/src/tests/core/file_sys/path_parser.cpp
new file mode 100644
index 000000000..2b543e438
--- /dev/null
+++ b/src/tests/core/file_sys/path_parser.cpp
@@ -0,0 +1,38 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <catch.hpp>
6#include "common/file_util.h"
7#include "core/file_sys/path_parser.h"
8
9namespace FileSys {
10
11TEST_CASE("PathParser", "[core][file_sys]") {
12 REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid());
13 REQUIRE(!PathParser(Path("a")).IsValid());
14 REQUIRE(!PathParser(Path("/|")).IsValid());
15 REQUIRE(PathParser(Path("/a")).IsValid());
16 REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid());
17 REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid());
18 REQUIRE(PathParser(Path("/")).IsRootDirectory());
19 REQUIRE(!PathParser(Path("/a")).IsRootDirectory());
20 REQUIRE(PathParser(Path("/a/..")).IsRootDirectory());
21}
22
23TEST_CASE("PathParser - Host file system", "[core][file_sys]") {
24 std::string test_dir = "./test";
25 FileUtil::CreateDir(test_dir);
26 FileUtil::CreateDir(test_dir + "/z");
27 FileUtil::CreateEmptyFile(test_dir + "/a");
28
29 REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound);
30 REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound);
31 REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound);
32 REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath);
33 REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound);
34
35 FileUtil::DeleteDirRecursively(test_dir);
36}
37
38} // namespace FileSys
diff --git a/src/tests/glad.cpp b/src/tests/glad.cpp
new file mode 100644
index 000000000..b0b016440
--- /dev/null
+++ b/src/tests/glad.cpp
@@ -0,0 +1,14 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <catch.hpp>
6#include <glad/glad.h>
7
8// This is not an actual test, but a work-around for issue #2183.
9// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS
10// will error about undefined references from video_core to glad. So we explicitly use a glad
11// function here to shut up the linker.
12TEST_CASE("glad fake test", "[dummy]") {
13 REQUIRE(&gladLoadGL != nullptr);
14}