From 1a3a15e4cf8b140bd14ee3c7f38db713a2715613 Mon Sep 17 00:00:00 2001 From: frangametv <50166684+frangametv@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:31:23 +0200 Subject: [PATCH] Fix mount root opens and empty APR prefixes (#439) * Fix opening mount root directories * Allow empty APR path prefixes --------- Co-authored-by: nmzik --- src/kernel/fileSystem.cpp | 71 +++------- src/libs/libAmpr.cpp | 223 +++++++------------------------- tests/KernelFileSystemTests.cpp | 115 ++++++++++++++++ 3 files changed, 179 insertions(+), 230 deletions(-) diff --git a/src/kernel/fileSystem.cpp b/src/kernel/fileSystem.cpp index 7a5c63630..72edb1e82 100644 --- a/src/kernel/fileSystem.cpp +++ b/src/kernel/fileSystem.cpp @@ -50,8 +50,7 @@ public: void Mount(const std::filesystem::path& folder, const std::string& point); void Umount(const std::string& folder_or_point); - [[nodiscard]] std::filesystem::path GetRealFilename(const std::string& mounted_file_name); - [[nodiscard]] std::filesystem::path GetRealDirectory(const std::string& mounted_directory); + [[nodiscard]] std::filesystem::path ResolvePath(const std::string& mounted_name); private: std::vector m_mount_pairs; @@ -284,26 +283,24 @@ static bool HasWindowsForbiddenFilenameCharacter(const std::string& relative_pat } #endif -std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_file_name) { +std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name) { Common::LockGuard lock(m_mutex); - auto mounted_path = - Common::DirectoryWithoutFilename(Common::FixFilenameSlash(mounted_file_name)); - + // Match the entire guest path so a mount root works with or without a trailing slash. + const auto mounted_path = Common::FixDirectorySlash(mounted_name); const auto it = std::find_if( m_mount_pairs.begin(), m_mount_pairs.end(), [&mounted_path](const MountPair& p) { return Common::StartsWith(mounted_path, p.point); }); if (it != m_mount_pairs.end()) { const auto& p = *it; - auto rel_path = - Common::RemoveFirst(Common::FixFilenameSlash(mounted_file_name), p.point.size()); + auto rel_path = Common::RemoveFirst(Common::FixFilenameSlash(mounted_name), p.point.size()); while (Common::StartsWith(rel_path, '/')) { rel_path = Common::RemoveFirst(rel_path, 1); } #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (HasWindowsForbiddenFilenameCharacter(rel_path)) { ::printf("FileSystem: Windows-incompatible guest filename: %s\n", - mounted_file_name.c_str()); + mounted_name.c_str()); } return p.dir / rel_path; #else @@ -311,32 +308,7 @@ std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_fi #endif } - return mounted_file_name; -} - -std::filesystem::path MountPoints::GetRealDirectory(const std::string& mounted_directory) { - Common::LockGuard lock(m_mutex); - - auto mounted_path = Common::FixDirectorySlash(mounted_directory); - - const auto it = std::find_if( - m_mount_pairs.begin(), m_mount_pairs.end(), - [&mounted_path](const MountPair& p) { return Common::StartsWith(mounted_path, p.point); }); - if (it != m_mount_pairs.end()) { - const auto& p = *it; - auto rel_path = - Common::RemoveFirst(Common::FixDirectorySlash(mounted_directory), p.point.size()); - while (Common::StartsWith(rel_path, '/')) { - rel_path = Common::RemoveFirst(rel_path, 1); - } -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - return p.dir / rel_path; -#else - return ResolvePathIgnoringCase(p.dir / rel_path); -#endif - } - - return mounted_directory; + return mounted_name; } void Initialize() { @@ -370,7 +342,7 @@ void Umount(const std::string& folder_or_point) { std::filesystem::path GetRealFilename(const std::string& mounted_file_name) { - return g_mount_points->GetRealFilename(mounted_file_name); + return g_mount_points->ResolvePath(mounted_file_name); } // NOLINTNEXTLINE(readability-function-cognitive-complexity) @@ -437,8 +409,7 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) { return descriptor; } - file->real_name = (directory ? g_mount_points->GetRealDirectory(file->name) - : g_mount_points->GetRealFilename(file->name)); + file->real_name = g_mount_points->ResolvePath(file->name); if (trunc && rw_mode == Common::File::Mode::Read) { return KERNEL_ERROR_EACCES; @@ -852,12 +823,9 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) { LOGF("\t KernelStat: %s\n", path); - std::string path_s = std::string(path); - auto real_file_name = g_mount_points->GetRealFilename(path_s); - auto real_directory = g_mount_points->GetRealDirectory(path_s); + auto real_file_name = g_mount_points->ResolvePath(path); - bool is_dir = Common::File::IsDirectoryExisting(real_file_name) || - Common::File::IsDirectoryExisting(real_directory); + bool is_dir = Common::File::IsDirectoryExisting(real_file_name); bool is_file = Common::File::IsFileExisting(real_file_name); if (!is_dir && !is_file) { @@ -1014,12 +982,9 @@ int KYTY_SYSV_ABI KernelUnlink(const char* path) { return KERNEL_ERROR_EINVAL; } - auto path_s = std::string(path); - auto real_file_name = g_mount_points->GetRealFilename(path_s); - auto real_directory = g_mount_points->GetRealDirectory(path_s); + auto real_file_name = g_mount_points->ResolvePath(path); - bool is_dir = Common::File::IsDirectoryExisting(real_file_name) || - Common::File::IsDirectoryExisting(real_directory); + bool is_dir = Common::File::IsDirectoryExisting(real_file_name); bool is_file = Common::File::IsFileExisting(real_file_name); if (is_dir) { @@ -1053,8 +1018,8 @@ int KYTY_SYSV_ABI KernelRename(const char* from, const char* to) { auto from_path = std::string(from); auto to_path = std::string(to); - auto real_from = g_mount_points->GetRealFilename(from_path); - auto real_to = g_mount_points->GetRealFilename(to_path); + auto real_from = g_mount_points->ResolvePath(from_path); + auto real_to = g_mount_points->ResolvePath(to_path); if (!Common::File::IsFileExisting(real_from)) { return KERNEL_ERROR_ENOENT; @@ -1220,7 +1185,7 @@ int KYTY_SYSV_ABI KernelMkdir(const char* path, uint16_t mode) { "\t mode = %04" PRIx16 "\n", path, mode); - auto real_name = g_mount_points->GetRealDirectory(std::string(path)); + auto real_name = g_mount_points->ResolvePath(std::string(path)); if (Common::File::IsDirectoryExisting(real_name)) { return KERNEL_ERROR_EEXIST; @@ -1246,7 +1211,7 @@ int KYTY_SYSV_ABI KernelRmdir(const char* path) { LOGF("\t path = %s\n", path); - auto real_name = g_mount_points->GetRealDirectory(std::string(path)); + auto real_name = g_mount_points->ResolvePath(std::string(path)); if (!Common::File::IsDirectoryExisting(real_name)) { return KERNEL_ERROR_ENOENT; @@ -1278,7 +1243,7 @@ int KYTY_SYSV_ABI KernelCheckReachability(const char* path) { return OK; } - auto real_name = g_mount_points->GetRealFilename(mounted_path); + auto real_name = g_mount_points->ResolvePath(mounted_path); if (Common::File::IsFileExisting(real_name) || Common::File::IsDirectoryExisting(real_name)) { return OK; diff --git a/src/libs/libAmpr.cpp b/src/libs/libAmpr.cpp index 30ecf8040..741ecc5f5 100644 --- a/src/libs/libAmpr.cpp +++ b/src/libs/libAmpr.cpp @@ -101,109 +101,23 @@ static uint32_t ComputeFileId(const char* guest_path) { return hash & static_cast(std::numeric_limits::max()); } -static bool CopyStringToOutput(const std::string& str, char* out, size_t out_size) { - if (str.empty() || str.size() + 1 > out_size) { - return false; +static int ReadGuestCString(uint64_t addr, char* out, size_t out_size) { + if (addr == 0) { + return LibKernel::KERNEL_ERROR_EFAULT; } - std::memcpy(out, str.c_str(), str.size() + 1); - return true; -} - -static bool ReadGuestCString(uint64_t addr, char* out, size_t out_size) { - if (out == nullptr || out_size == 0 || addr == 0) { - return false; - } - - const auto max_len = static_cast(out_size - 1); - const auto* src = reinterpret_cast(static_cast(addr)); - for (uint64_t pos = 0; pos < max_len; pos++) { + const auto* src = reinterpret_cast(addr); + for (size_t pos = 0; pos < out_size; pos++) { if (addr > std::numeric_limits::max() - pos) { - out[pos] = '\0'; - return false; + return LibKernel::KERNEL_ERROR_EFAULT; } - - const auto ch = src[pos]; - out[pos] = ch; - if (ch == '\0') { - return pos != 0; + out[pos] = src[pos]; + if (out[pos] == '\0') { + return OK; } } - out[max_len] = '\0'; - return false; -} - -static bool ReadGuestWideCString(uint64_t addr, char* out, size_t out_size) { - if (out == nullptr || out_size == 0 || addr == 0) { - return false; - } - - std::array tmp {}; - const auto max_len = std::min(tmp.size() - 1, out_size - 1); - const auto* src = reinterpret_cast(static_cast(addr)); - for (uint64_t pos = 0; pos < max_len; pos++) { - if (pos > (std::numeric_limits::max() - addr) / sizeof(char16_t)) { - return false; - } - - char16_t ch = 0; - std::memcpy(&ch, src + pos * sizeof(char16_t), sizeof(ch)); - tmp[static_cast(pos)] = ch; - if (ch == u'\0') { - return pos != 0 && CopyStringToOutput(Common::Utf16ToUtf8(tmp.data()), out, out_size); - } - } - - return false; -} - -static bool ReadGuestPathText(uint64_t addr, char* out, size_t out_size) { - std::array narrow {}; - if (ReadGuestCString(addr, narrow.data(), std::min(narrow.size(), out_size))) { - const auto narrow_len = std::strlen(narrow.data()); - if (narrow_len > 1 || !ReadGuestWideCString(addr, out, out_size)) { - std::memcpy(out, narrow.data(), std::strlen(narrow.data()) + 1); - } - return true; - } - - if (ReadGuestWideCString(addr, out, out_size)) { - return true; - } - return false; -} - -static bool ReadPathPointer(uint64_t pointer_addr, char* out, size_t out_size) { - uint64_t path_addr = 0; - if (!ReadGuest(pointer_addr, &path_addr)) { - return false; - } - if (path_addr == 0) { - return false; - } - if (ReadGuestPathText(path_addr, out, out_size)) { - return true; - } - return false; -} - -static bool ResolvePathFromList(uint64_t path_list, uint64_t index, char* out, size_t out_size) { - if (ReadPathPointer(path_list + index * sizeof(uint64_t), out, out_size)) { - return true; - } - if (index != 0) { - return false; - } - if (ReadGuestPathText(path_list, out, out_size)) { - return true; - } - for (uint64_t offset = 0; offset < 0x40; offset += sizeof(uint64_t)) { - if (ReadPathPointer(path_list + offset, out, out_size)) { - return true; - } - } - return false; + return LibKernel::KERNEL_ERROR_ENAMETOOLONG; } static void RegisterHostPathLocked(uint32_t file_id, const std::string& host_path, @@ -280,36 +194,6 @@ static int GetHostPathStat(const std::string& host_path, LibKernel::FileSystem:: return OK; } -static bool JoinPrefixPath(const char* prefix, const char* path, char* out, size_t out_size) { - if (path == nullptr || out == nullptr || out_size == 0) { - return false; - } - - if (prefix == nullptr || prefix[0] == '\0') { - std::strncpy(out, path, out_size - 1); - out[out_size - 1] = '\0'; - return true; - } - - const auto prefix_len = std::strlen(prefix); - const auto path_len = std::strlen(path); - const bool needs_sep = prefix_len != 0 && path_len != 0 && prefix[prefix_len - 1] != '/' && - prefix[prefix_len - 1] != '\\' && path[0] != '/' && path[0] != '\\'; - const auto total_len = prefix_len + (needs_sep ? 1u : 0u) + path_len; - if (total_len + 1u > out_size) { - return false; - } - - std::memcpy(out, prefix, prefix_len); - auto pos = prefix_len; - if (needs_sep) { - out[pos++] = '/'; - } - std::memcpy(out + pos, path, path_len); - out[total_len] = '\0'; - return true; -} - static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size) { if (guest_path == nullptr || guest_path[0] == '\0') { return LibKernel::KERNEL_ERROR_EINVAL; @@ -381,8 +265,8 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size) return OK; } -static int ResolvePathsCommon(const void* path_list, uint64_t count, uint32_t* ids, uint64_t* sizes, - uint32_t* error_index, const char* prefix, int* results) { +static int ResolvePathsCommon(const char* const* path_list, uint32_t count, uint32_t* ids, + uint64_t* sizes, uint32_t* error_index, const char* prefix, int* results) { if (path_list == nullptr || count == 0 || count > 1024 || (ids == nullptr && sizes == nullptr && results == nullptr)) { return LibKernel::KERNEL_ERROR_EINVAL; @@ -406,18 +290,27 @@ static int ResolvePathsCommon(const void* path_list, uint64_t count, uint32_t* i return LibKernel::KERNEL_ERROR_EFAULT; } - const auto path_list_addr = reinterpret_cast(path_list); - bool any_error = false; - int first_error = OK; - uint32_t success_count = 0; - for (uint64_t i = 0; i < count; i++) { - char guest_path[1024] {}; - char resolved_path[2048] {}; - int result = OK; - if (!AprShared::ResolvePathFromList(path_list_addr, i, guest_path, sizeof(guest_path)) || - !JoinPrefixPath(prefix, guest_path, resolved_path, sizeof(resolved_path))) { - result = LibKernel::KERNEL_ERROR_EFAULT; - } else { + if (!IsValidGuestRange(reinterpret_cast(path_list), count * sizeof(*path_list))) { + return LibKernel::KERNEL_ERROR_EFAULT; + } + + char prefix_buf[1024] {}; + if (prefix != nullptr) { + const int result = ReadGuestCString(reinterpret_cast(prefix), prefix_buf, + sizeof(prefix_buf)); + if (result != OK) { + return result; + } + } + const auto prefix_size = std::strlen(prefix_buf); + uint32_t success_count = 0; + for (uint32_t i = 0; i < count; i++) { + char resolved_path[1024] {}; + // Concatenate the prefix literally, including an empty prefix. + std::memcpy(resolved_path, prefix_buf, prefix_size); + int result = ReadGuestCString(reinterpret_cast(path_list[i]), + resolved_path + prefix_size, sizeof(resolved_path) - prefix_size); + if (result == OK) { result = ResolveOnePath(resolved_path, ids != nullptr ? &ids[i] : nullptr, sizes != nullptr ? &sizes[i] : nullptr); } @@ -437,17 +330,13 @@ static int ResolvePathsCommon(const void* path_list, uint64_t count, uint32_t* i if (error_index != nullptr) { *error_index = static_cast(i); } - if (!any_error) { - any_error = true; - first_error = result; - } if (results == nullptr) { return result; } } } - return results != nullptr ? static_cast(success_count) : (any_error ? first_error : OK); + return results != nullptr ? static_cast(success_count) : OK; } static uint32_t AllocateSubmissionId(uint64_t command_buffer, uint64_t result) { @@ -521,7 +410,7 @@ static int KernelSyscallResult(int result) { return -1; } -static int KYTY_SYSV_ABI ResolveFilepathsToIds(const void* path_list, uint32_t count, uint32_t* ids, +static int KYTY_SYSV_ABI ResolveFilepathsToIds(const char* const* path_list, uint32_t count, uint32_t* ids, uint32_t* error_index) { PRINT_NAME(); @@ -582,7 +471,7 @@ static int KYTY_SYSV_ABI GetFileSize(uint32_t file_id, uint64_t* size) { return OK; } -static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizes(const void* path_list, uint32_t count, +static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizes(const char* const* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes, uint32_t* error_index) { PRINT_NAME(); @@ -591,37 +480,27 @@ static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizes(const void* path_list AprShared::ResolvePathsCommon(path_list, count, ids, sizes, error_index, nullptr, nullptr)); } -static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIds(const char* prefix, const void* path_list, +static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIds(const char* prefix, const char* const* path_list, uint32_t count, uint32_t* ids, uint32_t* error_index) { PRINT_NAME(); - char prefix_buf[1024] {}; - if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast(prefix), - prefix_buf, sizeof(prefix_buf))) { - return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT); - } return KernelSyscallResult(AprShared::ResolvePathsCommon(path_list, count, ids, nullptr, - error_index, prefix_buf, nullptr)); + error_index, prefix, nullptr)); } static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsAndFileSizes(const char* prefix, - const void* path_list, + const char* const* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes, uint32_t* error_index) { PRINT_NAME(); - char prefix_buf[1024] {}; - if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast(prefix), - prefix_buf, sizeof(prefix_buf))) { - return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT); - } return KernelSyscallResult(AprShared::ResolvePathsCommon(path_list, count, ids, sizes, - error_index, prefix_buf, nullptr)); + error_index, prefix, nullptr)); } -static int KYTY_SYSV_ABI ResolveFilepathsToIdsForEach(const void* path_list, uint32_t count, +static int KYTY_SYSV_ABI ResolveFilepathsToIdsForEach(const char* const* path_list, uint32_t count, uint32_t* ids, int* results) { PRINT_NAME(); @@ -629,7 +508,7 @@ static int KYTY_SYSV_ABI ResolveFilepathsToIdsForEach(const void* path_list, uin AprShared::ResolvePathsCommon(path_list, count, ids, nullptr, nullptr, nullptr, results)); } -static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizesForEach(const void* path_list, +static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizesForEach(const char* const* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes, int* results) { PRINT_NAME(); @@ -639,32 +518,22 @@ static int KYTY_SYSV_ABI ResolveFilepathsToIdsAndFileSizesForEach(const void* pa } static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsForEach(const char* prefix, - const void* path_list, + const char* const* path_list, uint32_t count, uint32_t* ids, int* results) { PRINT_NAME(); - char prefix_buf[1024] {}; - if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast(prefix), - prefix_buf, sizeof(prefix_buf))) { - return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT); - } return KernelSyscallResult(AprShared::ResolvePathsCommon(path_list, count, ids, nullptr, - nullptr, prefix_buf, results)); + nullptr, prefix, results)); } static int KYTY_SYSV_ABI ResolveFilepathsWithPrefixToIdsAndFileSizesForEach( - const char* prefix, const void* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes, + const char* prefix, const char* const* path_list, uint32_t count, uint32_t* ids, uint64_t* sizes, int* results) { PRINT_NAME(); - char prefix_buf[1024] {}; - if (prefix != nullptr && !AprShared::ReadGuestPathText(reinterpret_cast(prefix), - prefix_buf, sizeof(prefix_buf))) { - return KernelSyscallResult(LibKernel::KERNEL_ERROR_EFAULT); - } return KernelSyscallResult( - AprShared::ResolvePathsCommon(path_list, count, ids, sizes, nullptr, prefix_buf, results)); + AprShared::ResolvePathsCommon(path_list, count, ids, sizes, nullptr, prefix, results)); } static int KYTY_SYSV_ABI SubmitCommandBufferAndGetResult(void* command_buffer, uint64_t, diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index e9ce956c2..400a64ec6 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -6,6 +6,7 @@ #include "kernel/fileSystem.h" #include "libs/errno.h" #include "libs/network.h" +#include "loader/symbolDatabase.h" #include @@ -17,6 +18,10 @@ #include #include +namespace Libs::LibKernelApr { +void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols); +} + namespace { namespace FileSystem = Libs::LibKernel::FileSystem; @@ -79,6 +84,114 @@ void CheckSaveRename(const std::filesystem::path &root, "renamed save contents"); } +void CheckMountRoot(const std::filesystem::path &root) { + Common::File cache; + Check(cache.Create(root / "rpf.cache"), "create directory listing fixture"); + cache.Close(); + FileSystem::Mount(root, "/app0"); + Check(FileSystem::GetRealFilename("/app0/rpf.cache") == root / "rpf.cache", + "resolve mount descendant"); + Check(FileSystem::GetRealFilename("/app01/rpf.cache") == "/app01/rpf.cache", + "mount prefix must end at a path component"); + + for (const char *path : {"/app0", "/app0/"}) { + for (const int flags : {0, 0x00020000}) { + const int fd = FileSystem::KernelOpen(path, flags, 0); + Check(fd >= 3, "open mounted root with O_RDONLY or O_DIRECTORY"); + std::array entries {}; + const int size = FileSystem::KernelGetdents(fd, entries.data(), entries.size()); + Check(size > 0 && size <= entries.size(), "enumerate mounted root"); + bool found = false; + for (int offset = 0; offset < size;) { + // Directory record: inode, record length, type, name length, name. + Check(size - offset >= 8, "directory record header fits"); + uint16_t length = 0; + std::memcpy(&length, entries.data() + offset + 4, sizeof(length)); + const auto name_length = static_cast(entries[offset + 7]); + Check(length >= 8 + name_length + 1 && length <= size - offset, + "directory record and name fit"); + if (std::string_view(entries.data() + offset + 8, name_length) == "rpf.cache") { + Check(entries[offset + 6] == 8, "cache directory entry is a regular file"); + found = true; + } + offset += length; + } + Check(found, "mounted root listing contains rpf.cache"); + Check(FileSystem::KernelClose(fd) == OK, "close mounted root"); + } + } + FileSystem::Umount("/app0"); +} + +void CheckAprPaths(const std::filesystem::path &root) { + Loader::SymbolDatabase symbols; + Libs::LibKernelApr::InitLibKernel_1_Apr(&symbols); + const auto *resolve_symbol = symbols.FindByNid("w5fcCG+t31g", Loader::SymbolType::Func); + const auto *each_symbol = symbols.FindByNid("C+Khtbbx2g8", Loader::SymbolType::Func); + Check(resolve_symbol && each_symbol, "APR path exports are registered"); + using Resolve = int (KYTY_SYSV_ABI *)(const char *, const char *const *, uint32_t, + uint32_t *, uint64_t *, uint32_t *); + using ResolveEach = int (KYTY_SYSV_ABI *)(const char *, const char *const *, uint32_t, + uint32_t *, uint64_t *, int *); + const auto resolve = reinterpret_cast(resolve_symbol->vaddr); + const auto resolve_each = reinterpret_cast(each_symbol->vaddr); + Common::File fixture; + Check(fixture.Create(root / "apr.dat"), "create APR fixture"); + fixture.Write("APR", 3); + fixture.Close(); + FileSystem::Mount(root, "/app0"); + + uint32_t expected_id = 0xffffffffu; + for (const auto &parts : {std::array{"", "/app0/apr.dat"}, + std::array{"/app0/", "apr.dat"}, + std::array{"/", "app0/apr.dat"}, + std::array{"/app", "0/apr.dat"}}) { + uint32_t id = 0xffffffffu, error_index = 0xffffffffu; + uint64_t size = 0; + Check(resolve(parts[0], &parts[1], 1, &id, &size, &error_index) == OK && + id != 0xffffffffu && size == 3, + "APR concatenates empty, one-character and partial-component prefixes"); + if (expected_id == 0xffffffffu) { + expected_id = id; + } + Check(id == expected_id, "equivalent APR paths return the same ID"); + } + + const char *paths[] = {"/app0/missing.dat", "/app0/apr.dat"}; + uint32_t ids[2] = {}, error_index = 0xffffffffu; + uint64_t sizes[2] = {1, 1}; + int results[2] = {}; + Check(resolve_each("", paths, 2, ids, sizes, results) == 1 && + results[0] == Libs::LibKernel::KERNEL_ERROR_ENOENT && results[1] == OK && + ids[0] == 0xffffffffu && ids[1] == expected_id && sizes[0] == 0 && sizes[1] == 3, + "APR foreach reports a missing path and continues to the valid file"); + + // PATH_MAX includes NUL; all components remain below NAME_MAX (255). + std::string longest = "/app0/"; + for (int i = 0; i < 3; ++i) { + longest += std::string(254, 'a') + '/'; + } + longest += std::string(1023 - longest.size(), 'b'); + paths[0] = longest.c_str(); + Check(resolve("", paths, 1, ids, sizes, &error_index) == -1 && + *Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENOENT && error_index == 0, + "APR accepts a pathname whose final NUL is at PATH_MAX minus one"); + Check(resolve("/", paths, 1, ids, sizes, &error_index) == -1 && + *Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENAMETOOLONG, + "APR rejects concatenated paths exceeding PATH_MAX"); + std::array unterminated; + unterminated.fill('/'); + paths[0] = unterminated.data(); + Check(resolve("", paths, 1, ids, sizes, &error_index) == -1 && + *Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENAMETOOLONG, + "APR rejects an unterminated pathname"); + paths[0] = "apr.dat"; + Check(resolve(unterminated.data(), paths, 1, ids, sizes, &error_index) == -1 && + *Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_ENAMETOOLONG, + "APR rejects an unterminated prefix"); + FileSystem::Umount("/app0"); +} + void CheckSocketWakeup() { namespace Net = Libs::Network::Net; // Guest sockaddr_in: length, family, network-order port/address, padding. @@ -156,6 +269,8 @@ int main() { TempDirectory temporary; FileSystem::Initialize(); + CheckMountRoot(temporary.Path()); + CheckAprPaths(temporary.Path()); FileSystem::Mount(temporary.Path(), "/savedata0"); CheckSaveRename(temporary.Path(), "first-save"); CheckSaveRename(temporary.Path(), "replacement-save");