| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- // 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 = static_cast<s32>(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
|