diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 87885f922..fdc1f7302 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp FileTime.cpp Getopt.cpp UserContext.cpp) target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) +target_sources(libSceLibcInternal PRIVATE Directory.cpp) configure_windows_unwind(libSceLibcInternal) target_sources(libSceLibcInternal PRIVATE FilesystemStatistics.cpp) @@ -20,6 +21,15 @@ if(BUILD_TESTING) set_tests_properties(guest_fstatvfs PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_fdopendir_tests ${LIBS_INCLUDE_DIR}/tests/GuestFdopendir.cpp) + target_include_directories(guest_fdopendir_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_fdopendir_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_fdopendir_tests) + add_test(NAME guest_fdopendir COMMAND guest_fdopendir_tests) + if(WIN32) + set_tests_properties(guest_fdopendir PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_utime_forwarding_tests ${LIBS_INCLUDE_DIR}/tests/GuestUtimeForwarding.cpp FileTime.cpp) target_include_directories(guest_utime_forwarding_tests PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(guest_utime_forwarding_tests) diff --git a/core/libs/prx/libSceLibcInternal/Directory.cpp b/core/libs/prx/libSceLibcInternal/Directory.cpp new file mode 100644 index 000000000..937f52fd9 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/Directory.cpp @@ -0,0 +1,16 @@ +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libkernel/File/include/DirectoryDescriptor.hpp" +#include + +extern "C" int APS5_VABI close_nid_postfix(int d); +extern "C" int* APS5_VABI __error_nid_postfix(); + +extern "C" void* APS5_VABI fdopendir_nid_postfix(int fd) { + int error = 0; + const char* path = GetDirectoryDescriptorPath_nid_no_patch(fd, &error); + if (path == nullptr) { + *__error_nid_postfix() = error == EBADF ? 9 : 20; + return nullptr; + } + return OpenDescriptorDirectory_nid_no_patch(path, fd, close_nid_postfix); +} diff --git a/core/libs/prx/libc/include/GuestDirectory.hpp b/core/libs/prx/libc/include/GuestDirectory.hpp index 532db73fb..092289d03 100644 --- a/core/libs/prx/libc/include/GuestDirectory.hpp +++ b/core/libs/prx/libc/include/GuestDirectory.hpp @@ -1,4 +1,5 @@ #pragma once +#include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -12,3 +13,5 @@ struct GuestDirectoryEntry { }; static_assert(offsetof(GuestDirectoryEntry, name) == 8); static_assert(sizeof(GuestDirectoryEntry) == 264); + +extern "C" void* APS5_VABI OpenDescriptorDirectory_nid_no_patch(const char* hostPath, int descriptor, int (APS5_VABI* closeDescriptor)(int)); diff --git a/core/libs/prx/libc/src/Directory.cpp b/core/libs/prx/libc/src/Directory.cpp index 27c7332b1..e94af9a6d 100644 --- a/core/libs/prx/libc/src/Directory.cpp +++ b/core/libs/prx/libc/src/Directory.cpp @@ -57,6 +57,8 @@ struct Directory { std::filesystem::path path; GuestDirectoryEntry entry{}; std::mutex mutex; + int descriptor = -1; + int (APS5_VABI* closeDescriptor)(int) = nullptr; ~Directory() { if (native) CloseNative(native); } }; std::uint8_t DirectoryType(const std::filesystem::path& path) { @@ -120,9 +122,10 @@ GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void* handle) { int APS5_VABI closedir_nid_postfix(void* handle) { if (!handle) { errno = 9; return -1; } std::unique_ptr directory(static_cast(handle)); - const int result = CloseNative(directory->native); + int result = CloseNative(directory->native); directory->native = nullptr; if (result) errno = DirectoryError(errno); + if (directory->closeDescriptor && directory->closeDescriptor(directory->descriptor) != 0) result = -1; return result; } @@ -132,4 +135,17 @@ void APS5_VABI rewinddir_nid_postfix(void* handle) { std::lock_guard lock(directory.mutex); RewindNative(directory.native); } + +void* APS5_VABI OpenDescriptorDirectory_nid_no_patch(const char* hostPath, int descriptor, int (APS5_VABI* closeDescriptor)(int)) { + try { + auto directory = std::make_unique(); + directory->path = std::filesystem::path(reinterpret_cast(hostPath)); + directory->native = OpenNative(directory->path); + if (!directory->native) { errno = DirectoryError(errno); return nullptr; } + directory->descriptor = descriptor; + directory->closeDescriptor = closeDescriptor; + return directory.release(); + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } + catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; } +} } diff --git a/core/libs/tests/GuestFdopendir.cpp b/core/libs/tests/GuestFdopendir.cpp new file mode 100644 index 000000000..4e6cfffa8 --- /dev/null +++ b/core/libs/tests/GuestFdopendir.cpp @@ -0,0 +1,49 @@ +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include +#include +#include +#include +#include +#include +#include +extern "C" { +void* APS5_VABI fdopendir_nid_postfix(int); +GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void*); +int APS5_VABI closedir_nid_postfix(void*); +int APS5_VABI open_nid_postfix(const char*, int, int); +int APS5_VABI close_nid_postfix(int); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const auto root = std::filesystem::path("anyps5-fdopendir-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + Require(std::filesystem::create_directory(root / "subdirectory")); + { std::ofstream file(root / "sample.txt"); file << "test"; } + const int fd = open_nid_postfix(root.string().c_str(), 0x20000, 0); + Require(fd >= 0); + void* directory = fdopendir_nid_postfix(fd); + Require(directory != nullptr); + std::map entries; + while (auto* entry = readdir_nid_postfix(directory)) { + entries[entry->name] = entry->type; + } + Require(entries.at("sample.txt") == 8); + Require(entries.at("subdirectory") == 4); + Require(DescriptorIsOpen_nid_no_patch(fd)); + Require(closedir_nid_postfix(directory) == 0); + Require(!DescriptorIsOpen_nid_no_patch(fd)); + Require(fdopendir_nid_postfix(fd) == nullptr && *__error_nid_postfix() == 9); + Require(fdopendir_nid_postfix(-1) == nullptr && *__error_nid_postfix() == 9); + const int file = open_nid_postfix((root / "sample.txt").string().c_str(), 0, 0); + Require(file >= 0); + Require(fdopendir_nid_postfix(file) == nullptr && *__error_nid_postfix() == 20); + Require(DescriptorIsOpen_nid_no_patch(file)); + Require(close_nid_postfix(file) == 0); + std::filesystem::remove(root / "sample.txt"); + std::filesystem::remove(root / "subdirectory"); + std::filesystem::remove(root); +} diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 8dfa8330c..3e06950f7 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -10,5 +10,6 @@ - [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) follows FreeBSD 11's `lib/libc/amd64/gen/makecontext.c`: at most six arguments, a stack of at least `MINSIGSTKSZ` (2048) bytes, and `exit(0)` when the function returns with no `uc_link`. The console's version is not verified - [makecontext](../../../core/libs/prx/libSceLibcInternal/UserContext.cpp) (libSceLibcInternal) on Windows pins its stack writable until a later `makecontext` stack overlaps it, since nothing tells the library when the guest frees the stack - [Thread exit destructors](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped +- [fdopendir](../../../core/libs/prx/libSceLibcInternal/Directory.cpp) (libSceLibcInternal) - the directory stream starts at the directory's first entry, while FreeBSD's [`fdopendir`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/gen/opendir.c#L67-L74) reads from the descriptor's current offset, and `FD_CLOEXEC` is not set on the descriptor. As in FreeBSD, a descriptor that is not open fails with `EBADF`, one that is not a directory with `ENOTDIR` (both leave it open), and `closedir` closes it - [fstatvfs](../../../core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp) (libSceLibcInternal) converts `_fstatfs` to the 88-byte `struct statvfs` as FreeBSD 9.2's `sfs2svfs` does (`f_bsize` from the transfer size, `f_frsize` from the block size, `f_favail` equal to `f_ffree`, `f_fsid` 0, `ST_RDONLY` and `ST_NOSUID` only). Differences: `f_namemax` is the `statfs` value, not `fpathconf(_PC_NAME_MAX)`; negative `f_bavail` and `f_ffree` wrap to unsigned without `EOVERFLOW`, as the same-width copy in FreeBSD does; inode counts are 0 on Windows, inherited from `_fstatfs`; a null output returns `EFAULT` after the descriptor is validated, where FreeBSD would fault. Layout and conversion are unverified against the PS5 library (PS5MC imports `fstatvfs` but was not run) - [getopt, getopt_long](../../../core/libs/prx/libSceLibcInternal/Getopt.cpp) (libSceLibcInternal) follow FreeBSD 11 [getopt.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt.c) and [getopt_long.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt_long.c) with `GNU_COMPATIBLE`: `getopt` does not permute, `getopt_long` permutes unless `POSIXLY_CORRECT` is set in the guest environment or the option string starts with `+`, and the exported `opterr`, `optind`, `optopt`, `optreset` and `optarg` start at 1, 1, 0, 0 and null. Warnings go to the guest's current `stderr` prefixed with `__progname`, where FreeBSD's `warnx` keeps the `stderr` of its first call. A null `getopt` option string, a null argument vector or argument, a negative `optind` and an `optind` moved inside a pending permutation throw where FreeBSD faults. `getopt_long_only` is not exported. That the console's `libSceLibcInternal` uses this FreeBSD code is unverified