Merge pull request #4569 from pipe1os/feat/libkernel-fdopendir

feat(libSceLibcInternal): implement fdopendir
This commit is contained in:
Adrià 2026-10-12 23:00:17 +02:00 committed by GitHub
commit 28c9dae73d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 96 additions and 1 deletions

View File

@ -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:$<TARGET_FILE_DIR:libSceLibcInternal>")
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:$<TARGET_FILE_DIR:libSceLibcInternal>")
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)

View File

@ -0,0 +1,16 @@
#include "prx/libc/include/GuestDirectory.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include <cerrno>
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);
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
@ -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));

View File

@ -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> directory(static_cast<Directory*>(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>();
directory->path = std::filesystem::path(reinterpret_cast<const char8_t*>(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; }
}
}

View File

@ -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 <chrono>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <map>
#include <string>
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<std::string, int> 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);
}

View File

@ -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