diff options
author | liamwhite <liamwhite@users.noreply.github.com> | 2024-01-26 15:55:25 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-26 15:55:25 +0100 |
commit | 55482ab5dce463d5014498b006c18a90d0d004e6 (patch) | |
tree | b343faa9cadc692265efb4b6b88e157c97ef76d2 /src/core/file_sys/fs_path.h | |
parent | Merge pull request #12796 from t895/controller-optimizations (diff) | |
parent | Address review comments and fix compilation problems (diff) | |
download | yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.gz yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.bz2 yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.lz yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.xz yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.zst yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.zip |
Diffstat (limited to 'src/core/file_sys/fs_path.h')
-rw-r--r-- | src/core/file_sys/fs_path.h | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/src/core/file_sys/fs_path.h b/src/core/file_sys/fs_path.h new file mode 100644 index 000000000..56ba08a6a --- /dev/null +++ b/src/core/file_sys/fs_path.h @@ -0,0 +1,566 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/fs_memory_management.h" +#include "core/file_sys/fs_path_utility.h" +#include "core/file_sys/fs_string_util.h" +#include "core/hle/result.h" + +namespace FileSys { +class DirectoryPathParser; + +class Path { + YUZU_NON_COPYABLE(Path); + YUZU_NON_MOVEABLE(Path); + +private: + static constexpr const char* EmptyPath = ""; + static constexpr size_t WriteBufferAlignmentLength = 8; + +private: + friend class DirectoryPathParser; + +public: + class WriteBuffer { + YUZU_NON_COPYABLE(WriteBuffer); + + private: + char* m_buffer; + size_t m_length_and_is_normalized; + + public: + constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) {} + + constexpr ~WriteBuffer() { + if (m_buffer != nullptr) { + Deallocate(m_buffer, this->GetLength()); + this->ResetBuffer(); + } + } + + constexpr WriteBuffer(WriteBuffer&& rhs) + : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) { + rhs.ResetBuffer(); + } + + constexpr WriteBuffer& operator=(WriteBuffer&& rhs) { + if (m_buffer != nullptr) { + Deallocate(m_buffer, this->GetLength()); + } + + m_buffer = rhs.m_buffer; + m_length_and_is_normalized = rhs.m_length_and_is_normalized; + + rhs.ResetBuffer(); + + return *this; + } + + constexpr void ResetBuffer() { + m_buffer = nullptr; + this->SetLength(0); + } + + constexpr char* Get() const { + return m_buffer; + } + + constexpr size_t GetLength() const { + return m_length_and_is_normalized >> 1; + } + + constexpr bool IsNormalized() const { + return static_cast<bool>(m_length_and_is_normalized & 1); + } + + constexpr void SetNormalized() { + m_length_and_is_normalized |= static_cast<size_t>(1); + } + + constexpr void SetNotNormalized() { + m_length_and_is_normalized &= ~static_cast<size_t>(1); + } + + private: + constexpr WriteBuffer(char* buffer, size_t length) + : m_buffer(buffer), m_length_and_is_normalized(0) { + this->SetLength(length); + } + + public: + static WriteBuffer Make(size_t length) { + if (void* alloc = Allocate(length); alloc != nullptr) { + return WriteBuffer(static_cast<char*>(alloc), length); + } else { + return WriteBuffer(); + } + } + + private: + constexpr void SetLength(size_t size) { + m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1); + } + }; + +private: + const char* m_str; + WriteBuffer m_write_buffer; + +public: + constexpr Path() : m_str(EmptyPath), m_write_buffer() {} + + constexpr Path(const char* s) : m_str(s), m_write_buffer() { + m_write_buffer.SetNormalized(); + } + + constexpr ~Path() = default; + + constexpr Result SetShallowBuffer(const char* buffer) { + // Check pre-conditions + ASSERT(m_write_buffer.GetLength() == 0); + + // Check the buffer is valid + R_UNLESS(buffer != nullptr, ResultNullptrArgument); + + // Set buffer + this->SetReadOnlyBuffer(buffer); + + // Note that we're normalized + this->SetNormalized(); + + R_SUCCEED(); + } + + constexpr const char* GetString() const { + // Check pre-conditions + ASSERT(this->IsNormalized()); + + return m_str; + } + + constexpr size_t GetLength() const { + if (std::is_constant_evaluated()) { + return Strlen(this->GetString()); + } else { + return std::strlen(this->GetString()); + } + } + + constexpr bool IsEmpty() const { + return *m_str == '\x00'; + } + + constexpr bool IsMatchHead(const char* p, size_t len) const { + return Strncmp(this->GetString(), p, len) == 0; + } + + Result Initialize(const Path& rhs) { + // Check the other path is normalized + const bool normalized = rhs.IsNormalized(); + R_UNLESS(normalized, ResultNotNormalized); + + // Allocate buffer for our path + const auto len = rhs.GetLength(); + R_TRY(this->Preallocate(len + 1)); + + // Copy the path + const size_t copied = Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1); + R_UNLESS(copied == len, ResultUnexpectedInPathA); + + // Set normalized + this->SetNormalized(); + R_SUCCEED(); + } + + Result Initialize(const char* path, size_t len) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, len)); + + // Set not normalized + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result Initialize(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + R_RETURN(this->Initialize(path, std::strlen(path))); + } + + Result InitializeWithReplaceBackslash(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + // Replace slashes as desired + if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) { + Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/'); + } + + // Set not normalized + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithReplaceForwardSlashes(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + // Replace slashes as desired + if (m_write_buffer.GetLength() > 1) { + if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') { + p[0] = '\\'; + p[1] = '\\'; + } + } + + // Set not normalized + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithNormalization(const char* path, size_t size) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + // Initialize + R_TRY(this->InitializeImpl(path, size)); + + // Set not normalized + this->SetNotNormalized(); + + // Perform normalization + PathFlags path_flags; + if (IsPathRelative(m_str)) { + path_flags.AllowRelativePath(); + } else if (IsWindowsPath(m_str, true)) { + path_flags.AllowWindowsPath(); + } else { + /* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then + * returns success. */ + /* This seems like a bug. */ + size_t dummy; + bool normalized; + R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), + m_str)); + + this->SetNormalized(); + R_SUCCEED(); + } + + // Normalize + R_TRY(this->Normalize(path_flags)); + + this->SetNormalized(); + R_SUCCEED(); + } + + Result InitializeWithNormalization(const char* path) { + // Check the path is valid + R_UNLESS(path != nullptr, ResultNullptrArgument); + + R_RETURN(this->InitializeWithNormalization(path, std::strlen(path))); + } + + Result InitializeAsEmpty() { + // Clear our buffer + this->ClearBuffer(); + + // Set normalized + this->SetNormalized(); + + R_SUCCEED(); + } + + Result AppendChild(const char* child) { + // Check the path is valid + R_UNLESS(child != nullptr, ResultNullptrArgument); + + // Basic checks. If we have a path and the child is empty, we have nothing to do + const char* c = child; + if (m_str[0]) { + // Skip an early separator + if (*c == '/') { + ++c; + } + + R_SUCCEED_IF(*c == '\x00'); + } + + // If we don't have a string, we can just initialize + auto cur_len = std::strlen(m_str); + if (cur_len == 0) { + R_RETURN(this->Initialize(child)); + } + + // Remove a trailing separator + if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') { + --cur_len; + } + + // Get the child path's length + auto child_len = std::strlen(c); + + // Reset our write buffer + WriteBuffer old_write_buffer; + if (m_write_buffer.Get() != nullptr) { + old_write_buffer = std::move(m_write_buffer); + this->ClearBuffer(); + } + + // Pre-allocate the new buffer + R_TRY(this->Preallocate(cur_len + 1 + child_len + 1)); + + // Get our write buffer + auto* dst = m_write_buffer.Get(); + if (old_write_buffer.Get() != nullptr && cur_len > 0) { + Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1); + } + + // Add separator + dst[cur_len] = '/'; + + // Copy the child path + const size_t copied = Strlcpy<char>(dst + cur_len + 1, c, child_len + 1); + R_UNLESS(copied == child_len, ResultUnexpectedInPathA); + + R_SUCCEED(); + } + + Result AppendChild(const Path& rhs) { + R_RETURN(this->AppendChild(rhs.GetString())); + } + + Result Combine(const Path& parent, const Path& child) { + // Get the lengths + const auto p_len = parent.GetLength(); + const auto c_len = child.GetLength(); + + // Allocate our buffer + R_TRY(this->Preallocate(p_len + c_len + 1)); + + // Initialize as parent + R_TRY(this->Initialize(parent)); + + // If we're empty, we can just initialize as child + if (this->IsEmpty()) { + R_TRY(this->Initialize(child)); + } else { + // Otherwise, we should append the child + R_TRY(this->AppendChild(child)); + } + + R_SUCCEED(); + } + + Result RemoveChild() { + // If we don't have a write-buffer, ensure that we have one + if (m_write_buffer.Get() == nullptr) { + if (const auto len = std::strlen(m_str); len > 0) { + R_TRY(this->Preallocate(len)); + Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1); + } + } + + // Check that it's possible for us to remove a child + auto* p = m_write_buffer.Get(); + s32 len = std::strlen(p); + R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented); + + // Handle a trailing separator + if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) { + --len; + } + + // Remove the child path segment + while ((--len) >= 0 && p[len]) { + if (p[len] == '/' || p[len] == '\\') { + if (len > 0) { + p[len] = 0; + } else { + p[1] = 0; + len = 1; + } + break; + } + } + + // Check that length remains > 0 + R_UNLESS(len > 0, ResultNotImplemented); + + R_SUCCEED(); + } + + Result Normalize(const PathFlags& flags) { + // If we're already normalized, nothing to do + R_SUCCEED_IF(this->IsNormalized()); + + // Check if we're normalized + bool normalized; + size_t dummy; + R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str, + flags)); + + // If we're not normalized, normalize + if (!normalized) { + // Determine necessary buffer length + auto len = m_write_buffer.GetLength(); + if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) { + len += 2; + } + if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) { + len += 1; + } + + // Allocate a new buffer + const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength); + auto buf = WriteBuffer::Make(size); + R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique); + + // Normalize into it + R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(), + m_write_buffer.GetLength(), flags)); + + // Set the normalized buffer as our buffer + this->SetModifiableBuffer(std::move(buf)); + } + + // Set normalized + this->SetNormalized(); + R_SUCCEED(); + } + +private: + void ClearBuffer() { + m_write_buffer.ResetBuffer(); + m_str = EmptyPath; + } + + void SetModifiableBuffer(WriteBuffer&& buffer) { + // Check pre-conditions + ASSERT(buffer.Get() != nullptr); + ASSERT(buffer.GetLength() > 0); + ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength)); + + // Get whether we're normalized + if (m_write_buffer.IsNormalized()) { + buffer.SetNormalized(); + } else { + buffer.SetNotNormalized(); + } + + // Set write buffer + m_write_buffer = std::move(buffer); + m_str = m_write_buffer.Get(); + } + + constexpr void SetReadOnlyBuffer(const char* buffer) { + m_str = buffer; + m_write_buffer.ResetBuffer(); + } + + Result Preallocate(size_t length) { + // Allocate additional space, if needed + if (length > m_write_buffer.GetLength()) { + // Allocate buffer + const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength); + auto buf = WriteBuffer::Make(size); + R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique); + + // Set write buffer + this->SetModifiableBuffer(std::move(buf)); + } + + R_SUCCEED(); + } + + Result InitializeImpl(const char* path, size_t size) { + if (size > 0 && path[0]) { + // Pre allocate a buffer for the path + R_TRY(this->Preallocate(size + 1)); + + // Copy the path + const size_t copied = Strlcpy<char>(m_write_buffer.Get(), path, size + 1); + R_UNLESS(copied >= size, ResultUnexpectedInPathA); + } else { + // We can just clear the buffer + this->ClearBuffer(); + } + + R_SUCCEED(); + } + + constexpr char* GetWriteBuffer() { + ASSERT(m_write_buffer.Get() != nullptr); + return m_write_buffer.Get(); + } + + constexpr size_t GetWriteBufferLength() const { + return m_write_buffer.GetLength(); + } + + constexpr bool IsNormalized() const { + return m_write_buffer.IsNormalized(); + } + + constexpr void SetNormalized() { + m_write_buffer.SetNormalized(); + } + + constexpr void SetNotNormalized() { + m_write_buffer.SetNotNormalized(); + } + +public: + bool operator==(const FileSys::Path& rhs) const { + return std::strcmp(this->GetString(), rhs.GetString()) == 0; + } + bool operator!=(const FileSys::Path& rhs) const { + return !(*this == rhs); + } + bool operator==(const char* p) const { + return std::strcmp(this->GetString(), p) == 0; + } + bool operator!=(const char* p) const { + return !(*this == p); + } +}; + +inline Result SetUpFixedPath(FileSys::Path* out, const char* s) { + // Verify the path is normalized + bool normalized; + size_t dummy; + R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s)); + + R_UNLESS(normalized, ResultInvalidPathFormat); + + // Set the fixed path + R_RETURN(out->SetShallowBuffer(s)); +} + +constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) { + const char* const str = path.GetString(); + return IsWindowsDrive(str) && + (str[2] == StringTraits::DirectorySeparator || + str[2] == StringTraits::AlternateDirectorySeparator) && + str[3] == StringTraits::NullTerminator; +} + +} // namespace FileSys |