From 065867e2c24e9856c360fc2d6b9a86c92aedc43e Mon Sep 17 00:00:00 2001
From: Morph <39850852+Morph1984@users.noreply.github.com>
Date: Tue, 25 May 2021 19:32:56 -0400
Subject: common: fs: Rework the Common Filesystem interface to make use of
 std::filesystem (#6270)

* common: fs: fs_types: Create filesystem types

Contains various filesystem types used by the Common::FS library

* common: fs: fs_util: Add std::string to std::u8string conversion utility

* common: fs: path_util: Add utlity functions for paths

Contains various utility functions for getting or manipulating filesystem paths used by the Common::FS library

* common: fs: file: Rewrite the IOFile implementation

* common: fs: Reimplement Common::FS library using std::filesystem

* common: fs: fs_paths: Add fs_paths to replace common_paths

* common: fs: path_util: Add the rest of the path functions

* common: Remove the previous Common::FS implementation

* general: Remove unused fs includes

* string_util: Remove unused function and include

* nvidia_flags: Migrate to the new Common::FS library

* settings: Migrate to the new Common::FS library

* logging: backend: Migrate to the new Common::FS library

* core: Migrate to the new Common::FS library

* perf_stats: Migrate to the new Common::FS library

* reporter: Migrate to the new Common::FS library

* telemetry_session: Migrate to the new Common::FS library

* key_manager: Migrate to the new Common::FS library

* bis_factory: Migrate to the new Common::FS library

* registered_cache: Migrate to the new Common::FS library

* xts_archive: Migrate to the new Common::FS library

* service: acc: Migrate to the new Common::FS library

* applets/profile: Migrate to the new Common::FS library

* applets/web: Migrate to the new Common::FS library

* service: filesystem: Migrate to the new Common::FS library

* loader: Migrate to the new Common::FS library

* gl_shader_disk_cache: Migrate to the new Common::FS library

* nsight_aftermath_tracker: Migrate to the new Common::FS library

* vulkan_library: Migrate to the new Common::FS library

* configure_debug: Migrate to the new Common::FS library

* game_list_worker: Migrate to the new Common::FS library

* config: Migrate to the new Common::FS library

* configure_filesystem: Migrate to the new Common::FS library

* configure_per_game_addons: Migrate to the new Common::FS library

* configure_profile_manager: Migrate to the new Common::FS library

* configure_ui: Migrate to the new Common::FS library

* input_profiles: Migrate to the new Common::FS library

* yuzu_cmd: config: Migrate to the new Common::FS library

* yuzu_cmd: Migrate to the new Common::FS library

* vfs_real: Migrate to the new Common::FS library

* vfs: Migrate to the new Common::FS library

* vfs_libzip: Migrate to the new Common::FS library

* service: bcat: Migrate to the new Common::FS library

* yuzu: main: Migrate to the new Common::FS library

* vfs_real: Delete the contents of an existing file in CreateFile

Current usages of CreateFile expect to delete the contents of an existing file, retain this behavior for now.

* input_profiles: Don't iterate the input profile dir if it does not exist

Silences an error produced in the log if the directory does not exist.

* game_list_worker: Skip parsing file if the returned VfsFile is nullptr

Prevents crashes in GetLoader when the virtual file is nullptr

* common: fs: Validate paths for path length

* service: filesystem: Open the mod load directory as read only
---
 src/common/fs/file.cpp | 392 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 392 insertions(+)
 create mode 100644 src/common/fs/file.cpp

(limited to 'src/common/fs/file.cpp')

diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
new file mode 100644
index 0000000000..9f3de1cb03
--- /dev/null
+++ b/src/common/fs/file.cpp
@@ -0,0 +1,392 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+
+#ifdef _WIN32
+#include <io.h>
+#include <share.h>
+#else
+#include <unistd.h>
+#endif
+
+#ifdef _MSC_VER
+#define fileno _fileno
+#define fseeko _fseeki64
+#define ftello _ftelli64
+#endif
+
+namespace Common::FS {
+
+namespace fs = std::filesystem;
+
+namespace {
+
+#ifdef _WIN32
+
+/**
+ * Converts the file access mode and file type enums to a file access mode wide string.
+ *
+ * @param mode File access mode
+ * @param type File type
+ *
+ * @returns A pointer to a wide string representing the file access mode.
+ */
+[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
+    switch (type) {
+    case FileType::BinaryFile:
+        switch (mode) {
+        case FileAccessMode::Read:
+            return L"rb";
+        case FileAccessMode::Write:
+            return L"wb";
+        case FileAccessMode::Append:
+            return L"ab";
+        case FileAccessMode::ReadWrite:
+            return L"r+b";
+        case FileAccessMode::ReadAppend:
+            return L"a+b";
+        }
+        break;
+    case FileType::TextFile:
+        switch (mode) {
+        case FileAccessMode::Read:
+            return L"r";
+        case FileAccessMode::Write:
+            return L"w";
+        case FileAccessMode::Append:
+            return L"a";
+        case FileAccessMode::ReadWrite:
+            return L"r+";
+        case FileAccessMode::ReadAppend:
+            return L"a+";
+        }
+        break;
+    }
+
+    return L"";
+}
+
+/**
+ * Converts the file-share access flag enum to a Windows defined file-share access flag.
+ *
+ * @param flag File-share access flag
+ *
+ * @returns Windows defined file-share access flag.
+ */
+[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
+    switch (flag) {
+    case FileShareFlag::ShareNone:
+    default:
+        return _SH_DENYRW;
+    case FileShareFlag::ShareReadOnly:
+        return _SH_DENYWR;
+    case FileShareFlag::ShareWriteOnly:
+        return _SH_DENYRD;
+    case FileShareFlag::ShareReadWrite:
+        return _SH_DENYNO;
+    }
+}
+
+#else
+
+/**
+ * Converts the file access mode and file type enums to a file access mode string.
+ *
+ * @param mode File access mode
+ * @param type File type
+ *
+ * @returns A pointer to a string representing the file access mode.
+ */
+[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
+    switch (type) {
+    case FileType::BinaryFile:
+        switch (mode) {
+        case FileAccessMode::Read:
+            return "rb";
+        case FileAccessMode::Write:
+            return "wb";
+        case FileAccessMode::Append:
+            return "ab";
+        case FileAccessMode::ReadWrite:
+            return "r+b";
+        case FileAccessMode::ReadAppend:
+            return "a+b";
+        }
+        break;
+    case FileType::TextFile:
+        switch (mode) {
+        case FileAccessMode::Read:
+            return "r";
+        case FileAccessMode::Write:
+            return "w";
+        case FileAccessMode::Append:
+            return "a";
+        case FileAccessMode::ReadWrite:
+            return "r+";
+        case FileAccessMode::ReadAppend:
+            return "a+";
+        }
+        break;
+    }
+
+    return "";
+}
+
+#endif
+
+/**
+ * Converts the seek origin enum to a seek origin integer.
+ *
+ * @param origin Seek origin
+ *
+ * @returns Seek origin integer.
+ */
+[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
+    switch (origin) {
+    case SeekOrigin::SetOrigin:
+    default:
+        return SEEK_SET;
+    case SeekOrigin::CurrentPosition:
+        return SEEK_CUR;
+    case SeekOrigin::End:
+        return SEEK_END;
+    }
+}
+
+} // Anonymous namespace
+
+std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) {
+    if (!IsFile(path)) {
+        return "";
+    }
+
+    IOFile io_file{path, FileAccessMode::Read, type};
+
+    return io_file.ReadString(io_file.GetSize());
+}
+
+size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
+                         std::string_view string) {
+    if (!IsFile(path)) {
+        return 0;
+    }
+
+    IOFile io_file{path, FileAccessMode::Write, type};
+
+    return io_file.WriteString(string);
+}
+
+size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
+                          std::string_view string) {
+    if (!Exists(path)) {
+        return WriteStringToFile(path, type, string);
+    }
+
+    if (!IsFile(path)) {
+        return 0;
+    }
+
+    IOFile io_file{path, FileAccessMode::Append, type};
+
+    return io_file.WriteString(string);
+}
+
+IOFile::IOFile() = default;
+
+IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
+    Open(path, mode, type, flag);
+}
+
+IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
+    Open(path, mode, type, flag);
+}
+
+IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
+    Open(path, mode, type, flag);
+}
+
+IOFile::~IOFile() {
+    Close();
+}
+
+IOFile::IOFile(IOFile&& other) noexcept {
+    std::swap(file_path, other.file_path);
+    std::swap(file_access_mode, other.file_access_mode);
+    std::swap(file_type, other.file_type);
+    std::swap(file, other.file);
+}
+
+IOFile& IOFile::operator=(IOFile&& other) noexcept {
+    std::swap(file_path, other.file_path);
+    std::swap(file_access_mode, other.file_access_mode);
+    std::swap(file_type, other.file_type);
+    std::swap(file, other.file);
+    return *this;
+}
+
+fs::path IOFile::GetPath() const {
+    return file_path;
+}
+
+FileAccessMode IOFile::GetAccessMode() const {
+    return file_access_mode;
+}
+
+FileType IOFile::GetType() const {
+    return file_type;
+}
+
+void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
+    Close();
+
+    file_path = path;
+    file_access_mode = mode;
+    file_type = type;
+
+    errno = 0;
+
+#ifdef _WIN32
+    if (flag != FileShareFlag::ShareNone) {
+        file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
+    } else {
+        _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
+    }
+#else
+    file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
+#endif
+
+    if (!IsOpen()) {
+        const auto ec = std::error_code{errno, std::generic_category()};
+        LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}",
+                  PathToUTF8String(file_path), ec.message());
+    }
+}
+
+void IOFile::Close() {
+    if (!IsOpen()) {
+        return;
+    }
+
+    errno = 0;
+
+    const auto close_result = std::fclose(file) == 0;
+
+    if (!close_result) {
+        const auto ec = std::error_code{errno, std::generic_category()};
+        LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
+                  PathToUTF8String(file_path), ec.message());
+    }
+
+    file = nullptr;
+}
+
+bool IOFile::IsOpen() const {
+    return file != nullptr;
+}
+
+std::string IOFile::ReadString(size_t length) const {
+    std::vector<char> string_buffer(length);
+
+    const auto chars_read = ReadSpan<char>(string_buffer);
+    const auto string_size = chars_read != length ? chars_read : length;
+
+    return std::string{string_buffer.data(), string_size};
+}
+
+size_t IOFile::WriteString(std::span<const char> string) const {
+    return WriteSpan(string);
+}
+
+bool IOFile::Flush() const {
+    if (!IsOpen()) {
+        return false;
+    }
+
+    errno = 0;
+
+    const auto flush_result = std::fflush(file) == 0;
+
+    if (!flush_result) {
+        const auto ec = std::error_code{errno, std::generic_category()};
+        LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
+                  PathToUTF8String(file_path), ec.message());
+    }
+
+    return flush_result;
+}
+
+bool IOFile::SetSize(u64 size) const {
+    if (!IsOpen()) {
+        return false;
+    }
+
+    errno = 0;
+
+#ifdef _WIN32
+    const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
+#else
+    const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
+#endif
+
+    if (!set_size_result) {
+        const auto ec = std::error_code{errno, std::generic_category()};
+        LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
+                  PathToUTF8String(file_path), size, ec.message());
+    }
+
+    return set_size_result;
+}
+
+u64 IOFile::GetSize() const {
+    if (!IsOpen()) {
+        return 0;
+    }
+
+    std::error_code ec;
+
+    const auto file_size = fs::file_size(file_path, ec);
+
+    if (ec) {
+        LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
+                  PathToUTF8String(file_path), ec.message());
+        return 0;
+    }
+
+    return file_size;
+}
+
+bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
+    if (!IsOpen()) {
+        return false;
+    }
+
+    errno = 0;
+
+    const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
+
+    if (!seek_result) {
+        const auto ec = std::error_code{errno, std::generic_category()};
+        LOG_ERROR(Common_Filesystem,
+                  "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
+                  PathToUTF8String(file_path), offset, origin, ec.message());
+    }
+
+    return seek_result;
+}
+
+s64 IOFile::Tell() const {
+    if (!IsOpen()) {
+        return 0;
+    }
+
+    errno = 0;
+
+    return ftello(file);
+}
+
+} // namespace Common::FS
-- 
cgit v1.2.3-70-g09d2