From aa2ce7d30d83ca1a6271139157a75f6d7cb301c1 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 8 Nov 2026 09:06:43 -0800 Subject: [PATCH] Don't terminate when fmt::ostream fails to write in the destructor The destructor flushed the buffer and let write errors escape, calling std::terminate on errors like a full disk. Report the error instead, like file and buffered_file do on close errors. Also clear the buffer before writing in flush so that data from a failed write, e.g. in close, is not written again by the destructor. --- include/fmt/os.h | 6 ++++-- src/os.cc | 3 ++- test/os-test.cc | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/include/fmt/os.h b/include/fmt/os.h index cef6bae0..39c7473c 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -376,9 +376,11 @@ class ostream : private detail::buffer { } inline void flush() { - if (size() == 0) return; - file_.write(data(), size() * sizeof(data()[0])); + 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); } template diff --git a/src/os.cc b/src/os.cc index 927deaff..133238d6 100644 --- a/src/os.cc +++ b/src/os.cc @@ -396,7 +396,8 @@ ostream::ostream(ostream&& other) noexcept } ostream::~ostream() { - flush(); + FMT_TRY { flush(); } + FMT_CATCH(...) { report_system_error(errno, "cannot write to file"); } delete[] data(); } #endif // FMT_USE_FCNTL diff --git a/test/os-test.cc b/test/os-test.cc index 43303f98..772b18b4 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -328,6 +328,23 @@ TEST(ostream_test, flush) { EXPECT_READ(in, "x"); } +TEST(ostream_test, write_error_in_dtor) { + auto test_file = uniq_file_name(__LINE__); + EXPECT_WRITE( + stderr, + fmt::output_file(test_file, file::RDONLY | file::CREATE).print("x"), + system_error_message(EBADF, "cannot write to file") + "\n"); +} + +TEST(ostream_test, close_error) { + auto out = + fmt::output_file(uniq_file_name(__LINE__), file::RDONLY | file::CREATE); + out.print("x"); + EXPECT_THROW(out.close(), std::system_error); + // Check that the destructor doesn't try to write the data again. + EXPECT_WRITE(stderr, { auto moved = std::move(out); }, ""); +} + TEST(file_test, default_ctor) { file f; EXPECT_EQ(-1, f.descriptor());