From 6a19442f129165b100553092c1dd6644fc14cbe2 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Mon, 12 Oct 2026 02:12:28 +0000 Subject: [PATCH] fix(libc): start append-mode streams at the end of the file fopen and freopen leave an "a+" stream where the host CRT puts it, at the start of the file. On main, after fopen(file, "a+") on a 7-byte file, ftell returns 0 and the first fgetc returns the first byte of the file. FreeBSD seeks every append-mode stream to the end when it opens it, so ftell returns 7 and a read before any fseek returns EOF. Code that opens a log or save file with "a+" and tests ftell(f) == 0 to see whether the file is new treats every existing file as empty. FreeBSD 11.4 lib/libc/stdio/fopen.c (fopen), and the same lines in lib/libc/stdio/freopen.c (freopen): if (oflags & O_APPEND) { fp->_flags2 |= __S2OAP; (void)_sseek(fp, (fpos_t)0, SEEK_END); } lib/libc/stdio/flags.c sets O_APPEND for every mode that starts with 'a': case 'a': /* open for appending */ ret = __SWR; m = O_WRONLY; o = O_CREAT | O_APPEND; fopen and freopen now seek a stream opened with an 'a' mode to the end of the file, ignoring a failed seek as FreeBSD does. Writes still go to the end through O_APPEND. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/FileAndHeap.cpp | 5 ++++- core/libs/tests/GuestStandardStreams.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 87c352e2b..218de952c 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -22,6 +22,7 @@ struct GuestFileMode { std::string native; bool writes = false; bool exclusive = false; + bool append = false; }; static std::optional ParseFileMode(const char* mode) { @@ -34,7 +35,7 @@ static std::optional ParseFileMode(const char* mode) { if (*flag == 'x') exclusive = true; } if (exclusive && access == 'r' && !update) return std::nullopt; - GuestFileMode result{std::string(1, access), access != 'r' || update, exclusive}; + GuestFileMode result{std::string(1, access), access != 'r' || update, exclusive, access == 'a'}; if (update) result.native += '+'; #ifdef _WIN32 result.native += 'b'; @@ -96,6 +97,7 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode } } if (stream->Reopen(path, parsed->native.c_str())) { + if (parsed->append) static_cast(std::fseek(stream->GetHandle(), 0, SEEK_END)); if (!path.empty() && parsed->writes) RecordWrittenPath_nid_no_patch(path); return stream; } @@ -141,6 +143,7 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) throw std::runtime_error(std::string(__func__) + ": " + FOPEN_MSG_OPEN_FAILED + ": \"" + abs_path + "\": " + reason); } // APS5_LOG_OUT("success: \"%s\"", abs_path.c_str()); + if (parsed->append) static_cast(std::fseek(handle.get(), 0, SEEK_END)); if (parsed->writes) RecordWrittenPath_nid_no_patch(fpath); auto stream = std::make_unique(handle.get(), true); handle.release(); diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index 5de08e2cb..c292bd830 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -273,6 +273,12 @@ static bool CheckBinaryModes() { FileStream* stream = reopen ? freopen_nid_postfix(filename.c_str(), mode, &redirected) : fopen_nid_postfix(filename.c_str(), mode); Require(stream != nullptr); const bool update = std::strchr(mode, '+') != nullptr; + if (*mode == 'a') { + const bool atEnd = ftello_nid_postfix(stream) == static_cast(original.size()) && + (!update || fgetc_nid_postfix(stream) == EOF); + if (!atEnd) std::fprintf(stderr, "%s %s: the append stream does not start at the end of the file\n", reopen ? "freopen" : "fopen", mode); + correct &= atEnd; + } if (*mode == 'r' || (*mode == 'a' && update)) { Require(fseeko_nid_postfix(stream, 0, SEEK_SET) == 0); char bytes[16]{};