Fix hang/assertion when printing to a pipe with closed read end (#4797)

This commit is contained in:
Victor Zverovich 2026-06-17 08:36:43 +02:00
parent 1be298e1bd
commit 9200553b22
2 changed files with 27 additions and 2 deletions

View File

@ -1526,7 +1526,10 @@ template <typename F> class file_base {
FMT_THROW(system_error(errno, FMT_STRING("ungetc failed")));
}
void flush() { fflush(this->file_); }
void flush() {
if (fflush(this->file_) != 0)
FMT_THROW(system_error(errno, FMT_STRING("fflush failed")));
}
};
// A FILE wrapper for glibc.
@ -1572,7 +1575,10 @@ template <typename F> class glibc_file : public file_base<F> {
return memchr(end, '\n', static_cast<size_t>(size));
}
void flush() { fflush_unlocked(this->file_); }
void flush() {
if (fflush_unlocked(this->file_) != 0)
FMT_THROW(system_error(errno, FMT_STRING("fflush failed")));
}
};
// A FILE wrapper for Apple's libc.

View File

@ -18,6 +18,7 @@
#include <cfenv> // fegetexceptflag and FE_ALL_EXCEPT
#include <climits> // INT_MAX
#include <cmath> // std::signbit
#include <csignal> // std::signal, SIGPIPE
#include <cstring> // std::strlen
#include <iterator> // std::back_inserter
#include <list> // std::list
@ -2592,6 +2593,24 @@ TEST(format_test, invalid_glibc_buffer) {
fmt::print(file, "------\n");
}
TEST(format_test, print_to_broken_pipe) {
// Ignore SIGPIPE so that a failing write reports EPIPE instead of
// terminating the test process. It must stay ignored until the file is
// closed below because closing also flushes the remaining buffered data.
auto old_handler = std::signal(SIGPIPE, SIG_IGN);
{
auto pipe = fmt::pipe();
pipe.read_end.close();
auto write_end = pipe.write_end.fdopen("w");
// The data must exceed the file's buffer to force a flush during
// formatting, whose underlying write() then fails with EPIPE.
auto data = std::string(1024 * 1024, 'x');
EXPECT_THROW(fmt::print(write_end.get(), "{}", data), std::system_error);
}
std::signal(SIGPIPE, old_handler);
}
#endif // FMT_USE_FCNTL
// Only defined after the test case.