Don't lose data on short writes in fmt::ostream

flush ignored the number of bytes written by write which can be less
than requested, silently dropping the rest of the buffer. Write in a loop
until all data is written.
This commit is contained in:
Victor Zverovich 2026-11-08 09:37:32 -08:00
parent aa2ce7d30d
commit e7e6e3f710
2 changed files with 19 additions and 2 deletions

View File

@ -377,10 +377,14 @@ class ostream : private detail::buffer<char> {
inline void flush() {
size_t n = size();
if (n == 0) return;
// Clear before writing to not write the data again if the write fails.
clear();
file_.write(data(), n);
// Write in a loop because write may write fewer bytes than requested.
for (const char* p = data(); n != 0;) {
size_t written = file_.write(p, n);
p += written;
n -= written;
}
}
template <typename... T>

View File

@ -54,6 +54,7 @@ int fclose_count;
int fileno_count;
size_t read_nbyte;
size_t write_nbyte;
size_t write_max; // Maximum number of bytes written per call if nonzero.
bool sysconf_error;
enum { none, max_size, error } fstat_sim;
@ -142,6 +143,7 @@ test::ssize_t test::read(int fildes, void* buf, test::size_t nbyte) {
test::ssize_t test::write(int fildes, const void* buf, test::size_t nbyte) {
write_nbyte = nbyte;
EMULATE_EINTR(write, -1);
if (write_max != 0 && nbyte > write_max) nbyte = write_max;
return ::FMT_POSIX(write(fildes, buf, nbyte));
}
@ -308,6 +310,17 @@ TEST(file_test, write_retry) {
# endif
}
TEST(ostream_test, short_write) {
write_max = 1;
{
auto out = fmt::output_file("temp");
out.print("abc");
}
write_max = 0;
file f("temp", file::RDONLY);
EXPECT_READ(f, "abc");
}
# ifdef _WIN32
TEST(file_test, convert_read_count) {
auto pipe = fmt::pipe();