diff --git a/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp b/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp index a8ce99649..a740c9ac0 100644 --- a/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp +++ b/core/libs/prx/libkernel/File/include/DirectoryDescriptor.hpp @@ -13,6 +13,7 @@ namespace File { int OpenDirectoryDescriptor(const std::filesystem::path& path); std::optional DirectoryDescriptorPath(int fd); void ForgetDirectoryDescriptor(int fd); +void DuplicateDirectoryDescriptor(int from, int to); std::optional SeekDirectoryDescriptor(int fd, std::int64_t offset, int whence); int ReadDirectoryDescriptor(int fd, char* buf, int nbytes); diff --git a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp index de24d4eac..be0f571ff 100644 --- a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp +++ b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp @@ -87,6 +87,15 @@ void ForgetDirectoryDescriptor(int fd) { g_directoryCount = g_directories.size(); } +void DuplicateDirectoryDescriptor(int from, int to) { + if (g_directoryCount == 0) return; + std::lock_guard lock(g_mutex); + const auto found = g_directories.find(from); + if (found == g_directories.end()) return; + g_directories[to] = found->second; + g_directoryCount = g_directories.size(); +} + std::optional SeekDirectoryDescriptor(int fd, std::int64_t offset, int whence) { constexpr int SeekSet = 0; constexpr int SeekCurrent = 1; diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 7efe260d0..18027b03c 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -437,10 +437,6 @@ static int PathError(const char* path) { bool DescriptorIsOpen_nid_no_patch(int descriptor) { return descriptor >= 0 && WithoutParameterHandler([descriptor] { return ::_get_osfhandle(descriptor) == -1 ? -1 : 0; }) == 0; } -static void RejectDirectoryDuplicate(int descriptor, const char* function) { - if (File::DirectoryDescriptorPath(descriptor)) - throw std::runtime_error(std::string(function) + ": duplicating a directory descriptor is not supported on Windows"); -} #else bool DescriptorIsOpen_nid_no_patch(int descriptor) { return descriptor >= 0 && ::fcntl(descriptor, F_GETFD) >= 0; @@ -550,8 +546,8 @@ int APS5_VABI dup_nid_postfix(int d) { if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Duplicate(d); if (d < 0) return PosixFailure(GUEST_EBADF); #ifdef _WIN32 - RejectDirectoryDuplicate(d, __func__); const int duplicate = WithoutParameterHandler([d] { return ::_dup(d); }); + if (duplicate >= 0) File::DuplicateDirectoryDescriptor(d, duplicate); #else const int duplicate = ::dup(d); #endif @@ -579,10 +575,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { #ifdef _WIN32 if (WithoutParameterHandler([from] { return ::_get_osfhandle(from) == -1 ? -1 : 0; }) != 0) return PosixFailure(GUEST_EBADF); if (from == to) return to; - RejectDirectoryDuplicate(from, __func__); if (to == 2) PreserveHostStandardError(); if (WithoutParameterHandler([from, to] { return ::_dup2(from, to); }) != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); File::ForgetDirectoryDescriptor(to); + File::DuplicateDirectoryDescriptor(from, to); File::ForgetFileLock(to); File::ForgetRandomDevice(to); if (sourceIsRandom) File::RememberRandomDevice(to); diff --git a/core/libs/tests/GuestDirectoryRewind.cpp b/core/libs/tests/GuestDirectoryRewind.cpp index ee576b3c0..a7cb911be 100644 --- a/core/libs/tests/GuestDirectoryRewind.cpp +++ b/core/libs/tests/GuestDirectoryRewind.cpp @@ -17,6 +17,8 @@ int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); int APS5_VABI sceKernelClose(int); int APS5_VABI sceKernelGetdents(int, char*, int); std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int); +int APS5_VABI dup_nid_postfix(int); +int APS5_VABI dup2_nid_postfix(int, int); } static void Check(bool value, int line) { @@ -104,6 +106,23 @@ int main() { Require(sceKernelLseek(directory, 0, 0) == 0); Require(ReadAll(directory) == (std::set{".", "..", "a", "b", "c", "d"})); + Require(sceKernelLseek(directory, 0, 0) == 0); + Require(sceKernelGetdents(directory, two.data(), static_cast(two.size())) == 24); + const int duplicate = dup_nid_postfix(directory); + Require(duplicate >= 0 && duplicate != directory); + Require(sceKernelLseek(duplicate, 0, 1) == 2); + Require(ReadAll(duplicate) == (std::set{"a", "b", "c", "d"})); + Require(sceKernelLseek(directory, 0, 1) == sceKernelLseek(duplicate, 0, 1)); + Require(sceKernelLseek(directory, 0, 0) == 0); + Require(sceKernelLseek(duplicate, 0, 1) == 0); + const int target = sceKernelOpen((root / "a").string().c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(target >= 0); + Require(dup2_nid_postfix(duplicate, target) == target); + Require(sceKernelClose(duplicate) == 0); + Require(ReadAll(target) == (std::set{".", "..", "a", "b", "c", "d"})); + Require(sceKernelLseek(directory, 0, 1) == sceKernelLseek(target, 0, 1)); + Require(sceKernelClose(target) == 0); + Require(sceKernelClose(directory) == 0); const int reused = sceKernelOpen((root / "a").string().c_str(), SCE_KERNEL_O_RDWR, 0); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 670d2bc86..973e23e4c 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -124,7 +124,7 @@ - [Thread cancellation](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) follows FreeBSD libthr (states and types 0/1 and 0/2, `PTHREAD_CANCELED` 1, the `_pthread_cleanup_info` node layout), not measured on the console. A request is acted on only in `scePthreadTestcancel`, the condition variable, `sem` and `scePthreadSem` waits, `scePthreadJoin` and the sleeps, and their POSIX names; file and socket calls are not cancellation points. A sleep is not interrupted: the thread exits when it ends. The asynchronous type acts like the deferred one, except at the two set calls. - [cpuset_setaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. Bits outside `sceKernelGetAvailableCpumask` are dropped, and a mask with none of those bits fails with `EDEADLK`, as FreeBSD does when the new set has no CPU in common with the parent set - [cpuset_getaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. For the process the mask is the union of the affinities of the live guest threads, as [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1150-L1156) ORs the mask of every thread in the process; threads the guest did not create through libkernel are not included, and the console result is unverified -- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` between a guest socket and a host descriptor in either direction, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates and the descriptor numbers come from the guest socket counter, not the lowest free number as in FreeBSD `kern_dup`. The errno values for descriptor exhaustion are not verified against the console. +- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` between a guest socket and a host descriptor in either direction. Guest sockets share one socket object between the duplicates and the descriptor numbers come from the guest socket counter, not the lowest free number as in FreeBSD `kern_dup`. The errno values for descriptor exhaustion are not verified against the console. - [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`. - [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it. - [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified.