Merge pull request #4640 from MarcZX100/codex/win-host-standard-handles-4631

fix(libc): preserve host standard handles on Windows redirects
This commit is contained in:
Dean 2026-10-13 17:49:02 +13:00 committed by GitHub
commit 4fe3123627
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 63 additions and 2 deletions

View File

@ -13,6 +13,8 @@
#ifdef _WIN32
#include <io.h>
#include "prx/libc/include/FileLock.hpp"
extern "C" void* HostStandardHandle_nid_no_patch(int descriptor);
extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle);
#endif
struct GuestFilePrefix {
@ -81,8 +83,11 @@ public:
#ifdef _WIN32
std::wstring wideMode;
for (const char character : std::string_view(mode)) wideMode.push_back(static_cast<unsigned char>(character));
GuestFileLockForget_nid_no_patch(::_fileno(previous));
const int replaced = previous ? _fileno(previous) : -1;
auto* const standard = HostStandardHandle_nid_no_patch(replaced);
GuestFileLockForget_nid_no_patch(replaced);
_handle = ::_wfreopen(filename.c_str(), wideMode.c_str(), previous);
KeepStandardHandle_nid_no_patch(replaced, standard);
#else
if (::fileno(previous) == 2) KeepHostStandardError_nid_no_patch();
_handle = std::freopen(filename.c_str(), mode, previous);

View File

@ -31,6 +31,8 @@ extern "C" void SyncWrittenPaths_nid_no_patch();
extern "C" void BlockPathAlias_nid_no_patch(const char* guestPrefix);
#ifdef _WIN32
extern "C" int WindowsOpenError_nid_no_patch(unsigned long error);
extern "C" void* HostStandardHandle_nid_no_patch(int descriptor);
extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle);
#endif
#define APS5_INVALID_ARG_EX throw std::invalid_argument(std::string(__func__) + ": invalid argument")

View File

@ -22,6 +22,7 @@
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <io.h>
#else
#include <ctime>
#include <fcntl.h>
@ -475,6 +476,28 @@ extern "C" int WindowsOpenError_nid_no_patch(unsigned long error) {
return EINVAL;
}
}
extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler);
static DWORD StandardHandleId(int descriptor) {
return descriptor == 0 ? STD_INPUT_HANDLE : descriptor == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE;
}
extern "C" void* HostStandardHandle_nid_no_patch(int descriptor) {
if (descriptor < 0 || descriptor > 2) return nullptr;
const HANDLE current = GetStdHandle(StandardHandleId(descriptor));
if (current == nullptr || current == INVALID_HANDLE_VALUE) return nullptr;
const auto previous = _set_thread_local_invalid_parameter_handler([](const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {});
const auto owned = ::_get_osfhandle(descriptor);
_set_thread_local_invalid_parameter_handler(previous);
if (reinterpret_cast<HANDLE>(owned) != current) return current;
HANDLE kept = nullptr;
if (!DuplicateHandle(GetCurrentProcess(), current, GetCurrentProcess(), &kept, 0, TRUE, DUPLICATE_SAME_ACCESS))
throw std::runtime_error("Cannot keep the host standard handle of descriptor " + std::to_string(descriptor));
return kept;
}
extern "C" void KeepStandardHandle_nid_no_patch(int descriptor, void* handle) {
if (handle != nullptr && !SetStdHandle(StandardHandleId(descriptor), handle))
throw std::runtime_error("Cannot keep the host standard handle of descriptor " + std::to_string(descriptor));
}
#endif
extern "C" void NotImplemented_nid_no_patch(const char* funcName) {

View File

@ -579,7 +579,10 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
if (WithoutParameterHandler([from] { return ::_get_osfhandle(from) == -1 ? -1 : 0; }) != 0) return PosixFailure(GUEST_EBADF);
if (from == to) return to;
if (to == 2) PreserveHostStandardError();
if (WithoutParameterHandler([from, to] { return ::_dup2(from, to); }) != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
auto* const standard = HostStandardHandle_nid_no_patch(to);
const int duplicated = WithoutParameterHandler([from, to] { return ::_dup2(from, to); });
KeepStandardHandle_nid_no_patch(to, standard);
if (duplicated != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
EqueueDescriptorClosed(to);
File::ForgetDirectoryDescriptor(to);
File::DuplicateDirectoryDescriptor(from, to);

View File

@ -278,6 +278,34 @@ int main() {
std::rewind(hostCapture);
Require(std::fread(debugText, 1, sizeof(debugText), hostCapture) == 15 && std::memcmp(debugText, "[debug:1] kept\n", 15) == 0);
Require(std::fclose(hostCapture) == 0);
#ifdef _WIN32
for (const bool reopen : {false, true}) {
auto* hostCapture = std::tmpfile();
const int hostError = ::_dup(2);
Require(hostCapture != nullptr && hostError >= 0);
int redirect[2] = {-1, -1};
Require(reopen || pipe_nid_postfix(redirect) == 0);
const bool captured = ::_dup2(::_fileno(hostCapture), 2) == 0 && SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast<HANDLE>(::_get_osfhandle(2)));
const bool redirected = reopen ? freopen_nid_postfix("stderr_probe.txt", "w", &_Stderr_nid_postfix) == &_Stderr_nid_postfix : dup2_nid_postfix(redirect[1], 2) == 2;
const HANDLE kept = GetStdHandle(STD_ERROR_HANDLE);
DWORD count = 0;
const bool separate = kept != reinterpret_cast<HANDLE>(::_get_osfhandle(2)) && WriteFile(kept, "kept", 4, &count, nullptr) && count == 4;
const bool written = write_nid_postfix(2, "G", 1) == 1;
const bool restored = ::_dup2(hostError, 2) == 0 && ::_close(hostError) == 0 && SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast<HANDLE>(::_get_osfhandle(2)));
Require(captured && redirected && separate && written && restored && CloseHandle(kept));
std::rewind(hostCapture);
char keptText[8]{};
Require(std::fread(keptText, 1, sizeof(keptText), hostCapture) == 4 && std::memcmp(keptText, "kept", 4) == 0);
Require(std::fclose(hostCapture) == 0);
char guestText = 0;
if (reopen) {
Require(std::filesystem::file_size("stderr_probe.txt") == 1 && std::filesystem::remove("stderr_probe.txt"));
} else {
Require(close_nid_postfix(redirect[1]) == 0 && read_nid_postfix(redirect[0], &guestText, 1) == 1 && guestText == 'G');
Require(close_nid_postfix(redirect[0]) == 0);
}
}
#endif
const auto root = std::filesystem::path("anyps5-filesystem-test-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
Require(mkdir_nid_postfix(root.string().c_str(), 0700) == 0);