diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index bb1792d85bf..e8c74068cb3 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -725,9 +725,14 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File stream->SyncStatus(); return count; } - if (!stream->SwitchToReading()) return 0; + const int previousError = errno; + errno = 0; + if (!stream->SwitchToReading()) { errno = errno != 0 ? GuestErrnoFromHost_nid_no_patch(errno) : previousError; return 0; } const auto result = std::fread(buffer, size, count, handle); + const int nativeError = errno; stream->SyncStatus(); + if (nativeError != 0) errno = GuestErrnoFromHost_nid_no_patch(nativeError); + else errno = previousError; stream->KeepDirectoryReadError(result < count); return result; } diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp index 09978e48c0e..e1fecc50a47 100644 --- a/core/libs/prx/libc/src/StandardStreams.cpp +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/HostErrno.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -95,9 +96,14 @@ int APS5_VABI fgetc_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); stream->Orientation(-1); - if (!stream->SwitchToReading()) return -1; + const int previousError = errno; + errno = 0; + if (!stream->SwitchToReading()) { errno = errno != 0 ? GuestErrnoFromHost_nid_no_patch(errno) : previousError; return -1; } const int result = std::fgetc(native); + const int nativeError = errno; stream->SyncStatus(); + if (nativeError != 0) errno = GuestErrnoFromHost_nid_no_patch(nativeError); + else errno = previousError; stream->KeepDirectoryReadError(result == -1); return result; } @@ -157,13 +163,18 @@ int APS5_VABI getc_unlocked_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); stream->Orientation(-1); - if (!stream->SwitchToReadingUnlocked()) return -1; + const int previousError = errno; + errno = 0; + if (!stream->SwitchToReadingUnlocked()) { errno = errno != 0 ? GuestErrnoFromHost_nid_no_patch(errno) : previousError; return -1; } #ifdef _WIN32 const int result = _fgetc_nolock(native); #else const int result = getc_unlocked(native); #endif + const int nativeError = errno; stream->SyncUnlockedStatus(); + if (nativeError != 0) errno = GuestErrnoFromHost_nid_no_patch(nativeError); + else errno = previousError; stream->KeepDirectoryReadError(result == -1); return result; } @@ -242,9 +253,14 @@ char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream) { return nullptr; } auto* native = GetNativeStream(stream); - if (!stream->SwitchToReading()) return nullptr; + const int previousError = errno; + errno = 0; + if (!stream->SwitchToReading()) { errno = errno != 0 ? GuestErrnoFromHost_nid_no_patch(errno) : previousError; return nullptr; } auto* result = std::fgets(buffer, size, native); + const int nativeError = errno; stream->SyncStatus(); + if (nativeError != 0) errno = GuestErrnoFromHost_nid_no_patch(nativeError); + else errno = previousError; stream->KeepDirectoryReadError(result == nullptr); return result; } diff --git a/core/libs/tests/FileStreams.cpp b/core/libs/tests/FileStreams.cpp index 43503243b34..014d9d281f1 100644 --- a/core/libs/tests/FileStreams.cpp +++ b/core/libs/tests/FileStreams.cpp @@ -12,6 +12,9 @@ #include #include #include +#ifdef __linux__ +#include +#endif #include #include #include @@ -45,6 +48,333 @@ static void ExpectException(TAction action) { throw std::runtime_error("Expected a stream exception"); } +static void CheckReadErrnoAndStatus(const char* path) { + { + auto* stream = fopen_nid_postfix(path, "wb"); + Require(stream != nullptr); + constexpr char payload[] = "hello world\n"; + Require(fwrite_nid_postfix(payload, 1, sizeof(payload) - 1, stream) == sizeof(payload) - 1); + Require(fclose_nid_postfix(stream) == 0); + } + { + auto* stream = fopen_nid_postfix(path, "rb"); + Require(stream != nullptr); + std::array buffer{}; + errno = 35; + Require(fread_nid_postfix(buffer.data(), 0, 10, stream) == 0); + Require(errno == 35); + Require(fread_nid_postfix(buffer.data(), 10, 0, stream) == 0); + Require(errno == 35); + Require(ferror_nid_postfix(stream) == 0); + + Require(fread_nid_postfix(buffer.data(), 1, 5, stream) == 5); + Require(std::memcmp(buffer.data(), "hello", 5) == 0); + Require(errno == 35); + Require(ferror_nid_postfix(stream) == 0); + + Require(fgetc_nid_postfix(stream) == ' '); + Require(errno == 35); + Require(ferror_nid_postfix(stream) == 0); + + Require(getc_unlocked_nid_postfix(stream) == 'w'); + Require(errno == 35); + Require(ferror_unlocked_nid_postfix(stream) == 0); + + std::array lineBuffer{}; + Require(fgets_nid_postfix(lineBuffer.data(), static_cast(lineBuffer.size()), stream) == lineBuffer.data()); + Require(std::strcmp(lineBuffer.data(), "orld\n") == 0); + Require(errno == 35); + Require(ferror_nid_postfix(stream) == 0); + + Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 0); + Require(feof_nid_postfix(stream) != 0); + Require(ferror_nid_postfix(stream) == 0); + Require(errno == 35); + + Require(fgetc_nid_postfix(stream) == EOF); + Require(feof_nid_postfix(stream) != 0); + Require(ferror_nid_postfix(stream) == 0); + Require(errno == 35); + + Require(getc_unlocked_nid_postfix(stream) == EOF); + Require(feof_unlocked_nid_postfix(stream) != 0); + Require(ferror_unlocked_nid_postfix(stream) == 0); + Require(errno == 35); + + Require(fgets_nid_postfix(lineBuffer.data(), static_cast(lineBuffer.size()), stream) == nullptr); + Require(feof_nid_postfix(stream) != 0); + Require(ferror_nid_postfix(stream) == 0); + Require(errno == 35); + + Require(fclose_nid_postfix(stream) == 0); + } +#ifndef _WIN32 + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + buffer.fill('Z'); + errno = 0; + Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), &pipe) == 0); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) != 0); + Require(feof_nid_postfix(&pipe) == 0); + Require(buffer[0] == 'Z'); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + errno = 0; + Require(fgetc_nid_postfix(&pipe) == EOF); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) != 0); + Require(feof_nid_postfix(&pipe) == 0); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + errno = 0; + Require(getc_unlocked_nid_postfix(&pipe) == EOF); + Require(errno == 35); + Require(ferror_unlocked_nid_postfix(&pipe) != 0); + Require(feof_unlocked_nid_postfix(&pipe) == 0); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + buffer.fill('Z'); + errno = 0; + Require(fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), &pipe) == nullptr); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) != 0); + Require(feof_nid_postfix(&pipe) == 0); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + buffer.fill('A'); + errno = 35; + Require(fread_nid_postfix(buffer.data(), 0, 4, &pipe) == 0); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) == 0); + Require(fread_nid_postfix(buffer.data(), 4, 0, &pipe) == 0); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) == 0); + Require(buffer[0] == 'A'); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + Require(::write(descriptors[1], "ab", 2) == 2); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + buffer.fill('Z'); + errno = 0; + Require(fread_nid_postfix(buffer.data(), 4, 1, &pipe) == 0); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) != 0); + Require(buffer[0] == 'a' && buffer[1] == 'b' && buffer[2] == 'Z' && buffer[3] == 'Z'); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + Require(::write(descriptors[1], "abc", 3) == 3); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + buffer.fill('?'); + errno = 0; + char* const result = fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), &pipe); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) != 0); + if (result != nullptr) { + Require(result == buffer.data()); + Require(buffer[0] == 'a' && buffer[1] == 'b' && buffer[2] == 'c'); + } + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + Require(::write(descriptors[1], "hello\n", 6) == 6); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + errno = 35; + Require(fgetc_nid_postfix(&pipe) == 'h'); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) == 0); + Require(getc_unlocked_nid_postfix(&pipe) == 'e'); + Require(errno == 35); + Require(ferror_unlocked_nid_postfix(&pipe) == 0); + Require(fread_nid_postfix(buffer.data(), 1, 2, &pipe) == 2); + Require(buffer[0] == 'l' && buffer[1] == 'l'); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) == 0); + char* const line = fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), &pipe); + Require(line == buffer.data()); + Require(std::strcmp(buffer.data(), "o\n") == 0); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) == 0); + pipe.Close(); + } + Require(::close(descriptors[1]) == 0); + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + Require(::close(descriptors[1]) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + std::array buffer{}; + errno = 35; + Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), &pipe) == 0); + Require(feof_nid_postfix(&pipe) != 0); + Require(ferror_nid_postfix(&pipe) == 0); + Require(errno == 35); + + Require(fgetc_nid_postfix(&pipe) == EOF); + Require(feof_nid_postfix(&pipe) != 0); + Require(ferror_nid_postfix(&pipe) == 0); + Require(errno == 35); + + Require(getc_unlocked_nid_postfix(&pipe) == EOF); + Require(feof_unlocked_nid_postfix(&pipe) != 0); + Require(ferror_unlocked_nid_postfix(&pipe) == 0); + Require(errno == 35); + + Require(fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), &pipe) == nullptr); + Require(feof_nid_postfix(&pipe) != 0); + Require(ferror_nid_postfix(&pipe) == 0); + Require(errno == 35); + + pipe.Close(); + } + } + { + int descriptors[2]; + Require(::pipe(descriptors) == 0); + Require(::fcntl(descriptors[0], F_SETFL, ::fcntl(descriptors[0], F_GETFL) | O_NONBLOCK) == 0); + { + FileStream pipe(::fdopen(descriptors[0], "rb")); + errno = 0; + Require(fgetc_nid_postfix(&pipe) == EOF); + Require(errno == 35); + Require(ferror_nid_postfix(&pipe) != 0); + + Require(::write(descriptors[1], "xy", 2) == 2); + std::array buffer{}; + errno = 35; + Require(fread_nid_postfix(buffer.data(), 1, 2, &pipe) == 2); + Require(buffer[0] == 'x' && buffer[1] == 'y'); + Require(ferror_nid_postfix(&pipe) != 0); + Require(errno == 35); + + Require(::close(descriptors[1]) == 0); + descriptors[1] = -1; + + errno = 35; + Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), &pipe) == 0); + Require(feof_nid_postfix(&pipe) != 0); + Require(ferror_nid_postfix(&pipe) != 0); + Require(errno == 35); + + Require(fgetc_nid_postfix(&pipe) == EOF); + Require(errno == 35); + + Require(getc_unlocked_nid_postfix(&pipe) == EOF); + Require(errno == 35); + + Require(fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), &pipe) == nullptr); + Require(errno == 35); + + pipe.Close(); + } + if (descriptors[1] >= 0) ::close(descriptors[1]); + } +#ifdef __linux__ + for (int reader = 0; reader < 4; ++reader) { + int descriptors[2]; + Require(::socketpair(AF_UNIX, SOCK_STREAM, 0, descriptors) == 0); + const int flags = ::fcntl(descriptors[0], F_GETFL); + Require(flags >= 0 && ::fcntl(descriptors[0], F_SETFL, flags | O_NONBLOCK) == 0); + auto* native = ::fdopen(descriptors[0], "r+"); + Require(native != nullptr); + std::array nativeBuffer{}; + Require(std::setvbuf(native, nativeBuffer.data(), _IOFBF, nativeBuffer.size()) == 0); + FileStream stream(native); + Require(std::fwrite("pending", 1, 7, native) == 7); + std::array chunk{}; + bool blocked = false; + for (int attempt = 0; attempt < 10000; ++attempt) { + if (::write(descriptors[0], chunk.data(), chunk.size()) < 0) { + Require(errno == EAGAIN || errno == EWOULDBLOCK); + blocked = true; + break; + } + } + Require(blocked); + std::array buffer{}; + buffer.fill('!'); + errno = 35; + switch (reader) { + case 0: Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), &stream) == 0); break; + case 1: Require(fgetc_nid_postfix(&stream) == EOF); break; + case 2: Require(getc_unlocked_nid_postfix(&stream) == EOF); break; + case 3: Require(fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), &stream) == nullptr); break; + } + Require(errno == 35, "Read transition leaked native errno"); + Require(ferror_nid_postfix(&stream) != 0 && feof_nid_postfix(&stream) == 0); + Require(std::all_of(buffer.begin(), buffer.end(), [](char byte) { return byte == '!'; })); + static_cast(stream.Close()); + Require(::close(descriptors[1]) == 0); + } +#endif +#endif +} + static void ReplaceWithReadOnlyDescriptor(FileStream* stream, const char* path) { #ifdef _WIN32 const int descriptor = _open(path, _O_RDONLY | _O_BINARY); @@ -632,6 +962,7 @@ int main(int argc, char** argv) { Require(argc == 2); CheckExclusiveCppOpen(argv[1]); CheckShortCharacterScans(); + CheckReadErrnoAndStatus(argv[1]); CheckOrdinaryCharacterScans(); CheckZeroSizedTransfers(argv[1]); CheckUpdateStreamReadsAfterWrites(argv[1]);