From 7166fdc49072d987d04e681de4d9e1558ba75c63 Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Mon, 17 Oct 2016 14:54:48 +0800
Subject: [PATCH] FileSys: add SaveDataArchive

The error checking of SaveDataArchive is completely different from DiskArchive, so it has to be a new class instead of a subclass of DiskArchive.
---
 src/core/CMakeLists.txt                      |   2 +
 src/core/file_sys/archive_savedata.cpp       |   4 +-
 src/core/file_sys/archive_systemsavedata.cpp |   4 +-
 src/core/file_sys/errors.h                   |  29 ++
 src/core/file_sys/savedata_archive.cpp       | 283 +++++++++++++++++++
 src/core/file_sys/savedata_archive.h         |  43 +++
 src/core/hle/result.h                        |   7 +
 7 files changed, 368 insertions(+), 4 deletions(-)
 create mode 100644 src/core/file_sys/errors.h
 create mode 100644 src/core/file_sys/savedata_archive.cpp
 create mode 100644 src/core/file_sys/savedata_archive.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ae9e8dcea..63a402ad0 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -25,6 +25,7 @@ set(SRCS
             file_sys/disk_archive.cpp
             file_sys/ivfc_archive.cpp
             file_sys/path_parser.cpp
+            file_sys/savedata_archive.cpp
             gdbstub/gdbstub.cpp
             hle/config_mem.cpp
             hle/hle.cpp
@@ -170,6 +171,7 @@ set(HEADERS
             file_sys/file_backend.h
             file_sys/ivfc_archive.h
             file_sys/path_parser.h
+            file_sys/savedata_archive.h
             gdbstub/gdbstub.h
             hle/config_mem.h
             hle/function_wrappers.h
diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp
index 6711035ec..ecb44a215 100644
--- a/src/core/file_sys/archive_savedata.cpp
+++ b/src/core/file_sys/archive_savedata.cpp
@@ -9,7 +9,7 @@
 #include "common/logging/log.h"
 #include "common/string_util.h"
 #include "core/file_sys/archive_savedata.h"
-#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/savedata_archive.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/service/fs/archive.h"
 
@@ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P
                           ErrorSummary::InvalidState, ErrorLevel::Status);
     }
 
-    auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point));
+    auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp
index 48ebc0ed4..54e7793e0 100644
--- a/src/core/file_sys/archive_systemsavedata.cpp
+++ b/src/core/file_sys/archive_systemsavedata.cpp
@@ -9,7 +9,7 @@
 #include "common/file_util.h"
 #include "common/string_util.h"
 #include "core/file_sys/archive_systemsavedata.h"
-#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/savedata_archive.h"
 #include "core/hle/service/fs/archive.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c
         return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
                           ErrorSummary::InvalidState, ErrorLevel::Status);
     }
-    auto archive = std::make_unique<DiskArchive>(fullpath);
+    auto archive = std::make_unique<SaveDataArchive>(fullpath);
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
new file mode 100644
index 000000000..da7e82642
--- /dev/null
+++ b/src/core/file_sys/errors.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS,
+                                    ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags,
+                                              ErrorModule::FS, ErrorSummary::NotSupported,
+                                              ErrorLevel::Usage);
+const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS,
+                                      ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS,
+                                      ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory,
+                                                    ErrorModule::FS, ErrorSummary::NotSupported,
+                                                    ErrorLevel::Usage);
+const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists,
+                                                ErrorModule::FS, ErrorSummary::NothingHappened,
+                                                ErrorLevel::Status);
+const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS,
+                                           ErrorSummary::NothingHappened, ErrorLevel::Status);
+const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS,
+                                           ErrorSummary::Canceled, ErrorLevel::Status);
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
new file mode 100644
index 000000000..f2e6a06bc
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -0,0 +1,283 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/file_util.h"
+#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
+#include "core/file_sys/savedata_archive.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
+                                                                  const Mode& mode) const {
+    LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    if (mode.hex == 0) {
+        LOG_ERROR(Service_FS, "Empty open mode");
+        return ERROR_UNSUPPORTED_OPEN_FLAGS;
+    }
+
+    if (mode.create_flag && !mode.write_flag) {
+        LOG_ERROR(Service_FS, "Create flag set but write flag not set");
+        return ERROR_UNSUPPORTED_OPEN_FLAGS;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::DirectoryFound:
+        LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    case PathParser::NotFound:
+        if (!mode.create_flag) {
+            LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
+                      full_path.c_str());
+            return ERROR_FILE_NOT_FOUND;
+        } else {
+            // Create the file
+            FileUtil::CreateEmptyFile(full_path);
+        }
+        break;
+    }
+
+    FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
+    if (!file.IsOpen()) {
+        LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    }
+
+    auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
+    return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+}
+
+ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::DirectoryFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    }
+
+    if (FileUtil::Delete(full_path)) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
+    return ERROR_FILE_NOT_FOUND;
+}
+
+ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+        return RESULT_SUCCESS;
+    }
+
+    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+    // exist or similar. Verify.
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+template <typename T>
+static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
+                                        T deleter) {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    if (path_parser.IsRootDirectory())
+        return ERROR_DIRECTORY_NOT_EMPTY;
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    }
+
+    if (deleter(full_path)) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
+    return ERROR_DIRECTORY_NOT_EMPTY;
+}
+
+ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
+    return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
+}
+
+ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
+    return DeleteDirectoryHelper(
+        path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
+}
+
+ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    case PathParser::DirectoryFound:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_FILE_ALREADY_EXISTS;
+    }
+
+    if (size == 0) {
+        FileUtil::CreateEmptyFile(full_path);
+        return RESULT_SUCCESS;
+    }
+
+    FileUtil::IOFile file(full_path, "wb");
+    // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+    // We do this by seeking to the right size, then writing a single null byte.
+    if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_FS, "Too large file");
+    return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
+                      ErrorLevel::Info);
+}
+
+ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    case PathParser::DirectoryFound:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+        return ERROR_DIRECTORY_ALREADY_EXISTS;
+    }
+
+    if (FileUtil::CreateDir(mount_point + path.AsString())) {
+        return RESULT_SUCCESS;
+    }
+
+    LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
+                      ErrorLevel::Status);
+}
+
+ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+        return RESULT_SUCCESS;
+
+    // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+    // exist or similar. Verify.
+    return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+                      ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
+    const Path& path) const {
+    const PathParser path_parser(path);
+
+    if (!path_parser.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto full_path = path_parser.BuildHostPath(mount_point);
+
+    switch (path_parser.GetHostStatus(mount_point)) {
+    case PathParser::InvalidMountPoint:
+        LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+        return ERROR_FILE_NOT_FOUND;
+    case PathParser::PathNotFound:
+    case PathParser::NotFound:
+        LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+        return ERROR_PATH_NOT_FOUND;
+    case PathParser::FileInPath:
+    case PathParser::FileFound:
+        LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+        return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+    }
+
+    auto directory = std::make_unique<DiskDirectory>(full_path);
+    return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 SaveDataArchive::GetFreeBytes() const {
+    // TODO: Stubbed to return 1GiB
+    return 1024 * 1024 * 1024;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h
new file mode 100644
index 000000000..2fb6c452a
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.h
@@ -0,0 +1,43 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "core/file_sys/archive_backend.h"
+#include "core/file_sys/directory_backend.h"
+#include "core/file_sys/file_backend.h"
+#include "core/hle/result.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+/// Archive backend for general save data archive type (SaveData and SystemSaveData)
+class SaveDataArchive : public ArchiveBackend {
+public:
+    SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
+
+    std::string GetName() const override {
+        return "SaveDataArchive: " + mount_point;
+    }
+
+    ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+                                                     const Mode& mode) const override;
+    ResultCode DeleteFile(const Path& path) const override;
+    ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+    ResultCode DeleteDirectory(const Path& path) const override;
+    ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+    ResultCode CreateFile(const Path& path, u64 size) const override;
+    ResultCode CreateDirectory(const Path& path) const override;
+    ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+    ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+    u64 GetFreeBytes() const override;
+
+protected:
+    std::string mount_point;
+};
+
+} // namespace FileSys
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 7f8d8e00d..8330894f2 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -20,15 +20,22 @@ enum class ErrorDescription : u32 {
     OS_InvalidBufferDescriptor = 48,
     WrongAddress = 53,
     FS_ArchiveNotMounted = 101,
+    FS_FileNotFound = 112,
+    FS_PathNotFound = 113,
     FS_NotFound = 120,
+    FS_FileAlreadyExists = 180,
+    FS_DirectoryAlreadyExists = 185,
     FS_AlreadyExists = 190,
     FS_InvalidOpenFlags = 230,
+    FS_DirectoryNotEmpty = 240,
     FS_NotAFile = 250,
     FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
     OutofRangeOrMisalignedAddress =
         513, // TODO(purpasmart): Check if this name fits its actual usage
     GPU_FirstInitialization = 519,
     FS_InvalidPath = 702,
+    FS_UnsupportedOpenFlags = 760,
+    FS_UnexpectedFileOrDirectory = 770,
     InvalidSection = 1000,
     TooLarge = 1001,
     NotAuthorized = 1002,