From dbbd6d582c8e91d5232e2e51ede05ea179a4fb4b Mon Sep 17 00:00:00 2001 From: David Date: Mon, 12 Oct 2026 22:46:41 +0400 Subject: [PATCH] fix(libc): append through a descriptor opened by fdopen in an append mode on Windows --- core/libs/prx/libc/src/FileAndHeap.cpp | 26 ++++++++++++++++++++++++ core/libs/tests/GuestStandardStreams.cpp | 26 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 01a17905b..1c0899a12 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -87,6 +87,31 @@ static std::FILE* OpenNative(const std::filesystem::path& path, const std::strin #endif } +#ifdef _WIN32 +static bool AppendThroughDescriptor(int descriptor) { + const auto handle = reinterpret_cast(::_get_osfhandle(descriptor)); + if (handle == INVALID_HANDLE_VALUE) return false; + if (descriptor <= 2 || GetFileType(handle) != FILE_TYPE_DISK) return true; + const int translation = ::_setmode(descriptor, _O_BINARY); + if (translation == -1 || ::_setmode(descriptor, translation) == -1) return false; + HANDLE copy = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), ©, 0, TRUE, DUPLICATE_SAME_ACCESS)) { + errno = EBADF; + return false; + } + const int appending = ::_open_osfhandle(reinterpret_cast(copy), _O_APPEND | translation); + if (appending < 0) { + CloseHandle(copy); + return false; + } + const int moved = ::_dup2(appending, descriptor); + const int error = errno; + ::_close(appending); + errno = error; + return moved == 0; +} +#endif + using GuestCompare = int (APS5_VABI *)(const void*, const void*); static void SwapElements(unsigned char* left, unsigned char* right, size_t size) { @@ -196,6 +221,7 @@ FileStream* APS5_VABI fdopen_nid_postfix(int descriptor, const char* mode) { const auto release = [](void* pointer) { ::operator delete(pointer); }; std::unique_ptr storage(::operator new(sizeof(FileStream)), release); #ifdef _WIN32 + if (parsed->append && !AppendThroughDescriptor(descriptor)) return nullptr; auto* native = ::_fdopen(descriptor, nativeMode.c_str()); #else auto* native = ::fdopen(descriptor, nativeMode.c_str()); diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index b3cb7d20a..4a214f9d6 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -392,6 +392,31 @@ static void CheckDescriptorModes(std::FILE* original) { #endif } +static void CheckDescriptorAppend(std::FILE* original) { + std::string expected = "retained"; + for (const char* mode : {"a", "ab", "a+", "ae"}) { + const int descriptor = DuplicateDescriptor(original); +#ifdef _WIN32 + Require(descriptor >= 0 && _lseek(descriptor, 2, SEEK_SET) == 2); +#else + Require(descriptor >= 0 && ::lseek(descriptor, 2, SEEK_SET) == 2); +#endif + auto* wrapped = fdopen_nid_postfix(descriptor, mode); + Require(wrapped != nullptr && fileno_nid_postfix(wrapped) == descriptor); + Require(fputc_nid_postfix('+', wrapped) == '+' && fflush_nid_postfix(wrapped) == 0); + Require(fseek_nid_postfix(wrapped, 0, SEEK_SET) == 0 && fputc_nid_postfix('+', wrapped) == '+'); + Require(fclose_nid_postfix(wrapped) == 0); + expected += "++"; + const int reader = DuplicateDescriptor(original); + Require(reader >= 0); + auto* written = fdopen_nid_postfix(reader, "r"); + Require(written != nullptr && fseek_nid_postfix(written, 0, SEEK_SET) == 0); + char contents[32]{}; + Require(fgets_nid_postfix(contents, sizeof(contents), written) == contents && expected == contents); + Require(fclose_nid_postfix(written) == 0); + } +} + int main() { Require(_Getmbcurmax_nid_postfix() == 1 && _Getmbcurmax_nid_postfix() == ___mb_cur_max_nid_postfix()); CheckFlushWhileReading(); @@ -421,6 +446,7 @@ int main() { Require(::close(descriptor) == -1); #endif CheckDescriptorModes(original); + CheckDescriptorAppend(original); Require(std::fclose(original) == 0); char stringOutput[256]; std::memset(stringOutput, '!', sizeof(stringOutput));