Files
AnyPS5/core/libs/tests/FilePosition.cpp
T
Opensource55 d09671fecb fix(libc): keep the stream position when a seek before the start is rejected on Windows
The UCRT drops a stream's read buffer before its `lseek` to a negative target fails with
`EINVAL`, so a rejected `fseeko` or `fsetpos` left the stream at the descriptor's read-ahead
offset: after 4 of 8 buffered bytes, a rejected `fsetpos` to -1 reported the position as 8 and
the next read returned EOF. FreeBSD 11.4's `_fseeko` keeps the buffer and the position when
`_sseek` fails.

`fseeko` now rejects a target before the start of the file before the host stream is touched,
computing the base from `_ftelli64` for `SEEK_CUR` and `_filelengthi64` for `SEEK_END`, and
answers `EINVAL` with the stream unchanged. A target that is not negative still goes to
`_fseeki64`, and a host seek that fails for another reason behaves as before.

`file_position` gains the three rejected targets: `fsetpos` to -1 after 4 buffered bytes,
`fseeko` with `SEEK_CUR` past the start, and `fseeko` with `SEEK_END` past the start; each
asserts `EINVAL`, an unchanged `fgetpos` and the next byte read.
2026-11-06 01:10:18 +08:00

118 lines
4.3 KiB
C++

#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "tests/TemporaryFile.hpp"
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "File position check failed at line %d\n", line);
std::abort();
}
}
#define Require(value) Check((value), __LINE__)
static void CheckFseekBounds() {
FileStream file(TemporaryFile::Open());
Require(fwrite_nid_postfix("0123456789", 1, 10, &file) == 10);
rewind_nid_postfix(&file);
errno = 0;
Require(fseek_nid_postfix(&file, -1, SEEK_SET) == -1 && errno == EINVAL);
Require(ftello_nid_postfix(&file) == 0);
Require(fseek_nid_postfix(&file, -3, SEEK_END) == 0 && ftello_nid_postfix(&file) == 7);
Require(fgetc_nid_postfix(&file) == '7');
Require(fseek_nid_postfix(&file, 100, SEEK_SET) == 0 && ftello_nid_postfix(&file) == 100);
Require(fgetc_nid_postfix(&file) == EOF && feof_nid_postfix(&file) != 0);
file.Close();
}
static void CheckFailedSeekKeepsPosition() {
FileStream file(TemporaryFile::Open());
Require(fwrite_nid_postfix("01234567", 1, 8, &file) == 8);
rewind_nid_postfix(&file);
for (const char expected : {'0', '1', '2', '3'}) Require(fgetc_nid_postfix(&file) == expected);
std::int64_t position = -1;
Require(fgetpos_nid_postfix(&file, &position) == 0 && position == 4);
const std::int64_t negative = -1;
errno = 0;
Require(fsetpos_nid_postfix(&file, &negative) == -1 && errno == EINVAL);
Require(fgetpos_nid_postfix(&file, &position) == 0 && position == 4);
Require(fgetc_nid_postfix(&file) == '4');
errno = 0;
Require(fseeko_nid_postfix(&file, -6, SEEK_CUR) == -1 && errno == EINVAL);
Require(fgetpos_nid_postfix(&file, &position) == 0 && position == 5);
Require(fgetc_nid_postfix(&file) == '5');
errno = 0;
Require(fseeko_nid_postfix(&file, -9, SEEK_END) == -1 && errno == EINVAL);
Require(fgetpos_nid_postfix(&file, &position) == 0 && position == 6);
Require(fgetc_nid_postfix(&file) == '6');
file.Close();
}
int main() {
CheckFseekBounds();
CheckFailedSeekKeepsPosition();
FileStream file(TemporaryFile::Open());
Require(std::fputs("position", file.GetHandle()) >= 0);
std::int64_t position = -1;
Require(fgetpos_nid_postfix(&file, &position) == 0);
Require(position == 8);
const std::int64_t beginning = 0;
Require(fsetpos_nid_postfix(&file, &beginning) == 0);
Require(std::fgetc(file.GetHandle()) == 'p');
Require(fgetpos_nid_postfix(&file, &position) == 0 && position == 1);
file.Close();
FileStream reread(TemporaryFile::Open());
Require(std::fputs("position", reread.GetHandle()) >= 0);
Require(fsetpos_nid_postfix(&reread, &beginning) == 0);
for (const char expected : {'p', 'o', 's'}) Require(fgetc_nid_postfix(&reread) == expected);
Require(fgetpos_nid_postfix(&reread, &position) == 0 && position == 3);
const std::int64_t third = position;
while (fgetc_nid_postfix(&reread) != EOF) {}
Require(feof_nid_postfix(&reread) != 0);
Require(fsetpos_nid_postfix(&reread, &third) == 0);
Require(feof_nid_postfix(&reread) == 0);
Require(fgetc_nid_postfix(&reread) == 'i');
const std::int64_t negative = -1;
errno = 0;
Require(fsetpos_nid_postfix(&reread, &negative) != 0);
Require(errno == EINVAL);
reread.Close();
int descriptors[2];
#ifdef _WIN32
Require(_pipe(descriptors, 4096, _O_BINARY) == 0);
FileStream pipe(_fdopen(descriptors[0], "rb"));
Require(_close(descriptors[1]) == 0);
Require(CloseHandle(reinterpret_cast<HANDLE>(_get_osfhandle(descriptors[0]))) != 0);
#else
Require(::pipe(descriptors) == 0);
FileStream pipe(::fdopen(descriptors[0], "rb"));
Require(::close(descriptors[1]) == 0);
#endif
Require(ftello_nid_postfix(&pipe) == -1);
const int expectedError = errno;
Require(expectedError != 0);
position = 123;
errno = 0;
Require(fgetpos_nid_postfix(&pipe, &position) != 0);
Require(errno == expectedError);
Require(position == 123);
#ifdef _WIN32
Require(std::fclose(pipe.GetHandle()) == EOF);
#else
pipe.Close();
#endif
}