fix(libkernel): share the directory state between duplicated descriptors on Windows
dup and dup2 of a Windows directory descriptor threw. FreeBSD's kern_dup makes both descriptors refer to one open file, so they share the read position; the duplicate now points at the same DirectoryState, which getdents and lseek go through, and keeps it after the original is closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a60f52e318
commit
d3011ae113
|
|
@ -13,6 +13,7 @@ namespace File {
|
|||
int OpenDirectoryDescriptor(const std::filesystem::path& path);
|
||||
std::optional<std::filesystem::path> DirectoryDescriptorPath(int fd);
|
||||
void ForgetDirectoryDescriptor(int fd);
|
||||
void DuplicateDirectoryDescriptor(int from, int to);
|
||||
std::optional<std::int64_t> SeekDirectoryDescriptor(int fd, std::int64_t offset, int whence);
|
||||
int ReadDirectoryDescriptor(int fd, char* buf, int nbytes);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<std::int64_t> SeekDirectoryDescriptor(int fd, std::int64_t offset, int whence) {
|
||||
constexpr int SeekSet = 0;
|
||||
constexpr int SeekCurrent = 1;
|
||||
|
|
|
|||
|
|
@ -436,10 +436,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;
|
||||
|
|
@ -548,8 +544,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
|
||||
|
|
@ -576,10 +572,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);
|
||||
|
|
|
|||
|
|
@ -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<std::string>{".", "..", "a", "b", "c", "d"}));
|
||||
|
||||
Require(sceKernelLseek(directory, 0, 0) == 0);
|
||||
Require(sceKernelGetdents(directory, two.data(), static_cast<int>(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<std::string>{"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<std::string>{".", "..", "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);
|
||||
|
|
|
|||
|
|
@ -122,7 +122,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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue