From f79f2dd729b5c7f722713e402fcd6fce6fd512a4 Mon Sep 17 00:00:00 2001 From: Emil Laine Date: Fri, 2 Oct 2026 04:04:30 +0300 Subject: [PATCH] Fix fflush(NULL) to flush all output streams Per C11 7.21.5.2p2; previously it segfaulted on NULL. --- libsrc/stdio/_file.h | 1 + libsrc/stdio/_fileman.c | 2 +- libsrc/stdio/fflush.c | 17 ++++++++++++++++- libsrc/tests/file_test.c | 20 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/libsrc/stdio/_file.h b/libsrc/stdio/_file.h index 20ff2c9f4..fe2a0d2fe 100644 --- a/libsrc/stdio/_file.h +++ b/libsrc/stdio/_file.h @@ -43,6 +43,7 @@ extern int _fflush(FILE *fp); extern void _add_opened_file(FILE *fp); extern void _remove_opened_file(FILE *fp); +extern void _flush_opened_files(void); extern int _detect_open_flag(const char *mode); static inline int _fputc(int c, FILE *fp) { diff --git a/libsrc/stdio/_fileman.c b/libsrc/stdio/_fileman.c index 5aded15f4..6723174e8 100644 --- a/libsrc/stdio/_fileman.c +++ b/libsrc/stdio/_fileman.c @@ -40,7 +40,7 @@ void _remove_opened_file(FILE *fp) { } __attribute__((destructor)) -static void flush_opened_files(void) { +void _flush_opened_files(void) { struct FILE **files = __fileman.opened; for (int i = 0, length = __fileman.length; i < length; ++i) { FILE *fp = files[i]; diff --git a/libsrc/stdio/fflush.c b/libsrc/stdio/fflush.c index 7e303b729..491cb80d4 100644 --- a/libsrc/stdio/fflush.c +++ b/libsrc/stdio/fflush.c @@ -3,5 +3,20 @@ #include "./_file.h" int fflush(FILE *fp) { - return (fp->flush)(fp); + if (fp != NULL) + return (fp->flush)(fp); + + // C11 7.21.5.2p2: If stream is a null pointer, the fflush function + // performs this flushing action on all streams for which the behavior + // is defined above (i.e. all output streams). + // Note: stdin is input-only and intentionally not flushed. + int result = 0; + // Standard output streams are statically allocated and not tracked in + // __fileman, so flush them explicitly. + if ((stdout->flush)(stdout) != 0) + result = EOF; + if ((stderr->flush)(stderr) != 0) + result = EOF; + _flush_opened_files(); + return result; } diff --git a/libsrc/tests/file_test.c b/libsrc/tests/file_test.c index ca00a6ac5..76740d575 100644 --- a/libsrc/tests/file_test.c +++ b/libsrc/tests/file_test.c @@ -115,4 +115,24 @@ TEST(ungetc) { } } +TEST(fflush_null) { + // fflush(NULL) must flush all output streams (C11 7.21.5.2p2). + const char fn[] = "tmp_fflush_null.txt"; + FILE *fp = fopen(fn, "w"); + if (EXPECT_NOT_NULL(fp)) { + // No newline, so the data stays buffered until flushed. + EXPECT_EQ(5, fwrite("hello", 1, 5, fp)); + EXPECT_EQ(0, fflush(NULL)); + // Data must have reached the file before fclose. + struct stat st; + if (EXPECT_EQ(0, stat(fn, &st))) { + EXPECT_EQ(5, st.st_size); + } + EXPECT_EQ(0, fclose(fp)); + EXPECT_EQ(0, remove(fn)); + } + // No opened files: still succeeds. + EXPECT_EQ(0, fflush(NULL)); +} + XTEST_MAIN();