Hi, thanks for your feedback!

> This doesn't work the way you want; as much as possible of the base files
from mingw-w64-crt are agnostic of the choice of default CRT

Ah, indeed the same CRT library is used regardless of the -mcrtdll argument 
used in GCC.

> [...] It is calling code outside of __native_startup_state guards.

Yeah, I am now fixing that patch too! Out of curiosity, what is that lock used 
for? I can't quite understand

> Anyway, for such changes it would be very useful to have an automated test

Ok, done! I have attached the patchset V2. Here are the relevant changes:


  1.
Fixed a few typos in the commit message (s/then/than, C23 section is now 
correct)
  2.
Fixed indentation
  3.
I now check the return value of setvbuf. Let me know if we can skip that...
  4.
Call to setvbuf has been moved right before the invocation of .CRT$XC 
callbacks. IMO that's the right time to make the call
  5.
Added a testcase

Slightly off-topic: the mingw-w64-crt testsuite always links against the 
system-provided mingw-w64-crt rather than the built one. Is there any easy way 
to change that?

Thanks,
Luca

________________________________
Da: Pali Rohár <[email protected]>
Inviato: sabato 13 dicembre 2025 20:03
A: Luca Bacci <[email protected]>; Martin Storsjö <[email protected]>
Cc: Mingw W64 Public <[email protected]>
Oggetto: Re: [Mingw-w64-public] Ensure that stderr is not fully buffered

On Saturday 13 December 2025 20:38:36 Martin Storsjö wrote:
> On Sat, 13 Dec 2025, Luca Bacci wrote:
>
> > diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
> > index 94bad6aa..d7497ef0 100644
> > --- a/mingw-w64-crt/crt/crtexe.c
> > +++ b/mingw-w64-crt/crt/crtexe.c
> > @@ -199,6 +199,17 @@ __tmainCRTStartup (void)
> >       if (__globallocalestatus == -1)
> >         _configthreadlocale (-1);
> >
> > +#if !defined (_UCRT)
>
> This doesn't work the way you want; as much as possible of the base files
> from mingw-w64-crt are agnostic of the choice of default CRT. In particular,
> all the files in mingw-w64-crt are built with "-D__MSVCRT_VERSION__=0x600".
> Only the files that go into the individual CRT import libraries can assume
> things about which CRT they're used with.
>
> One way of working around this would be to add a call to a function, which
> in the libmsvcr*.a libraries does what you want, but in libucrt*.a would be
> a dummy no-op function.
>
> Pali may have other suggestions or opinions about how to deal with this.
>
> // Martin

Yes, ifdef for _UCRT in mingw-w64-crt/* directory does not work, it is
never defined. I would suggest to call setvbuf() unconditionally. That
is simple solution and would work with any CRT library.

I looked at second change 
https://sourceforge.net/p/mingw-w64/mailman/message/59272390/
and seems that this one is not correct too. It is calling code outside
of __native_startup_state guards.

Anyway, for such changes it would be very useful to have an automated
test. So we can see what is happening without the change and with the
change. Lot of different tests are in the mingw-w64-crt/testcases/ dir.
For example test t_assert.c is spawning new process and using pipe on
stderr and is checking that stderr was properly transferred.
From 7a85d9538917916d6bb5c93f21d823708ba121f2 Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Tue, 16 Dec 2025 15:08:57 +0100
Subject: [PATCH 1/3] crt: Add test for stderr buffering mode on startup

Signed-off-by: Luca Bacci <[email protected]>
---
 mingw-w64-crt/testcases/Makefile.am          |   1 +
 mingw-w64-crt/testcases/Makefile.in          | 131 +++++++++++--------
 mingw-w64-crt/testcases/t_stderr_buffering.c |  58 ++++++++
 3 files changed, 134 insertions(+), 56 deletions(-)
 create mode 100644 mingw-w64-crt/testcases/t_stderr_buffering.c

diff --git a/mingw-w64-crt/testcases/Makefile.am 
b/mingw-w64-crt/testcases/Makefile.am
index 345c525e..14c59abc 100644
--- a/mingw-w64-crt/testcases/Makefile.am
+++ b/mingw-w64-crt/testcases/Makefile.am
@@ -62,6 +62,7 @@ testcase_progs = \
   t_stat_f64 \
   t_stat_t64 \
   t_stat_t64_f64 \
+  t_stderr_buffering \
   t_stdint \
   t_time \
   t_tls1 \
diff --git a/mingw-w64-crt/testcases/Makefile.in 
b/mingw-w64-crt/testcases/Makefile.in
index 226b90cd..af631187 100644
--- a/mingw-w64-crt/testcases/Makefile.in
+++ b/mingw-w64-crt/testcases/Makefile.in
@@ -194,12 +194,13 @@ am__EXEEXT_2 = tstmainc$(EXEEXT) tstmaincpp$(EXEEXT) \
        t_stprintf0_u$(EXEEXT) t_stprintf1_a$(EXEEXT) \
        t_stprintf1_u$(EXEEXT) t_setjmp$(EXEEXT) t_sigv$(EXEEXT) \
        t_speed_powl$(EXEEXT) t_stat$(EXEEXT) t_stat_f64$(EXEEXT) \
-       t_stat_t64$(EXEEXT) t_stat_t64_f64$(EXEEXT) t_stdint$(EXEEXT) \
-       t_time$(EXEEXT) t_tls1$(EXEEXT) t_tmpfile$(EXEEXT) \
-       t_trycatch$(EXEEXT) t_stat_slash$(EXEEXT) t_utime$(EXEEXT) \
-       t_vsscanf$(EXEEXT) t_wcrtomb$(EXEEXT) t_wcsrtombs$(EXEEXT) \
-       t_wcstok_s$(EXEEXT) t_wctob$(EXEEXT) t_wreaddir$(EXEEXT) \
-       t_fseeko64$(EXEEXT) $(am__EXEEXT_1)
+       t_stat_t64$(EXEEXT) t_stat_t64_f64$(EXEEXT) \
+       t_stderr_buffering$(EXEEXT) t_stdint$(EXEEXT) t_time$(EXEEXT) \
+       t_tls1$(EXEEXT) t_tmpfile$(EXEEXT) t_trycatch$(EXEEXT) \
+       t_stat_slash$(EXEEXT) t_utime$(EXEEXT) t_vsscanf$(EXEEXT) \
+       t_wcrtomb$(EXEEXT) t_wcsrtombs$(EXEEXT) t_wcstok_s$(EXEEXT) \
+       t_wctob$(EXEEXT) t_wreaddir$(EXEEXT) t_fseeko64$(EXEEXT) \
+       $(am__EXEEXT_1)
 @LIB32_TRUE@am__EXEEXT_3 = tests32/cimag$(EXEEXT) \
 @LIB32_TRUE@   tests32/creal$(EXEEXT) tests32/cabs$(EXEEXT) \
 @LIB32_TRUE@   tests32/cacos$(EXEEXT) tests32/cacosh$(EXEEXT) \
@@ -580,6 +581,9 @@ t_stat_t64_LDADD = $(LDADD)
 t_stat_t64_f64_SOURCES = t_stat_t64_f64.c
 t_stat_t64_f64_OBJECTS = t_stat_t64_f64.$(OBJEXT)
 t_stat_t64_f64_LDADD = $(LDADD)
+t_stderr_buffering_SOURCES = t_stderr_buffering.c
+t_stderr_buffering_OBJECTS = t_stderr_buffering.$(OBJEXT)
+t_stderr_buffering_LDADD = $(LDADD)
 t_stdint_SOURCES = t_stdint.c
 t_stdint_OBJECTS = t_stdint.$(OBJEXT)
 t_stdint_LDADD = $(LDADD)
@@ -1006,19 +1010,19 @@ am__depfiles_remade = ./$(DEPDIR)/t__fstat_all.Po \
        ./$(DEPDIR)/t_speed_powl.Po ./$(DEPDIR)/t_stat.Po \
        ./$(DEPDIR)/t_stat_f64.Po ./$(DEPDIR)/t_stat_slash.Po \
        ./$(DEPDIR)/t_stat_t64.Po ./$(DEPDIR)/t_stat_t64_f64.Po \
-       ./$(DEPDIR)/t_stdint.Po ./$(DEPDIR)/t_stprintf0_a.Po \
-       ./$(DEPDIR)/t_stprintf0_u.Po ./$(DEPDIR)/t_stprintf1_a.Po \
-       ./$(DEPDIR)/t_stprintf1_u.Po ./$(DEPDIR)/t_stprintf_a.Po \
-       ./$(DEPDIR)/t_stprintf_u.Po ./$(DEPDIR)/t_swprintf.Po \
-       ./$(DEPDIR)/t_swprintf0.Po ./$(DEPDIR)/t_swprintf1.Po \
-       ./$(DEPDIR)/t_time.Po ./$(DEPDIR)/t_tls1.Po \
-       ./$(DEPDIR)/t_tmain-t_tmain.Po ./$(DEPDIR)/t_tmpfile.Po \
-       ./$(DEPDIR)/t_trycatch.Po ./$(DEPDIR)/t_utime.Po \
-       ./$(DEPDIR)/t_vsscanf.Po ./$(DEPDIR)/t_wcrtomb.Po \
-       ./$(DEPDIR)/t_wcsrtombs.Po ./$(DEPDIR)/t_wcstok_s.Po \
-       ./$(DEPDIR)/t_wctob.Po ./$(DEPDIR)/t_wreaddir.Po \
-       ./$(DEPDIR)/tstmain_sys_xxx.Po ./$(DEPDIR)/tstmainc.Po \
-       ./$(DEPDIR)/tstmaincpp.Po \
+       ./$(DEPDIR)/t_stderr_buffering.Po ./$(DEPDIR)/t_stdint.Po \
+       ./$(DEPDIR)/t_stprintf0_a.Po ./$(DEPDIR)/t_stprintf0_u.Po \
+       ./$(DEPDIR)/t_stprintf1_a.Po ./$(DEPDIR)/t_stprintf1_u.Po \
+       ./$(DEPDIR)/t_stprintf_a.Po ./$(DEPDIR)/t_stprintf_u.Po \
+       ./$(DEPDIR)/t_swprintf.Po ./$(DEPDIR)/t_swprintf0.Po \
+       ./$(DEPDIR)/t_swprintf1.Po ./$(DEPDIR)/t_time.Po \
+       ./$(DEPDIR)/t_tls1.Po ./$(DEPDIR)/t_tmain-t_tmain.Po \
+       ./$(DEPDIR)/t_tmpfile.Po ./$(DEPDIR)/t_trycatch.Po \
+       ./$(DEPDIR)/t_utime.Po ./$(DEPDIR)/t_vsscanf.Po \
+       ./$(DEPDIR)/t_wcrtomb.Po ./$(DEPDIR)/t_wcsrtombs.Po \
+       ./$(DEPDIR)/t_wcstok_s.Po ./$(DEPDIR)/t_wctob.Po \
+       ./$(DEPDIR)/t_wreaddir.Po ./$(DEPDIR)/tstmain_sys_xxx.Po \
+       ./$(DEPDIR)/tstmainc.Po ./$(DEPDIR)/tstmaincpp.Po \
        complex/$(DEPDIR)/tests32_cabs-main.Po \
        complex/$(DEPDIR)/tests32_cacos-main.Po \
        complex/$(DEPDIR)/tests32_cacosh-main.Po \
@@ -1241,34 +1245,35 @@ SOURCES = $(tests32_libnewcomplextests_a_SOURCES) \
        t_snprintf.c t_snprintf0.c t_snprintf1.c t_snwprintf.c \
        t_snwprintf0.c t_snwprintf1.c t_speed_powl.c t_stat.c \
        t_stat_f64.c t_stat_slash.c t_stat_t64.c t_stat_t64_f64.c \
-       t_stdint.c t_stprintf0_a.c t_stprintf0_u.c t_stprintf1_a.c \
-       t_stprintf1_u.c t_stprintf_a.c t_stprintf_u.c t_swprintf.c \
-       t_swprintf0.c t_swprintf1.c t_time.c t_tls1.c t_tmain.c \
-       t_tmpfile.c $(t_trycatch_SOURCES) t_utime.c t_vsscanf.c \
-       t_wcrtomb.c t_wcsrtombs.c t_wcstok_s.c t_wctob.c t_wreaddir.c \
-       $(tests32_cabs_SOURCES) $(tests32_cacos_SOURCES) \
-       $(tests32_cacosh_SOURCES) $(tests32_carg_SOURCES) \
-       $(tests32_casin_SOURCES) $(tests32_casinh_SOURCES) \
-       $(tests32_catan_SOURCES) $(tests32_catanh_SOURCES) \
-       $(tests32_ccos_SOURCES) $(tests32_ccosh_SOURCES) \
-       $(tests32_cexp_SOURCES) $(tests32_cimag_SOURCES) \
-       $(tests32_clog_SOURCES) $(tests32_conj_SOURCES) \
-       $(tests32_cpow_SOURCES) $(tests32_cproj_SOURCES) \
-       $(tests32_creal_SOURCES) $(tests32_csin_SOURCES) \
-       $(tests32_csinh_SOURCES) $(tests32_csqrt_SOURCES) \
-       $(tests32_ctan_SOURCES) $(tests32_ctanh_SOURCES) \
-       $(tests64_cabs_SOURCES) $(tests64_cacos_SOURCES) \
-       $(tests64_cacosh_SOURCES) $(tests64_carg_SOURCES) \
-       $(tests64_casin_SOURCES) $(tests64_casinh_SOURCES) \
-       $(tests64_catan_SOURCES) $(tests64_catanh_SOURCES) \
-       $(tests64_ccos_SOURCES) $(tests64_ccosh_SOURCES) \
-       $(tests64_cexp_SOURCES) $(tests64_cimag_SOURCES) \
-       $(tests64_clog_SOURCES) $(tests64_conj_SOURCES) \
-       $(tests64_cpow_SOURCES) $(tests64_cproj_SOURCES) \
-       $(tests64_creal_SOURCES) $(tests64_csin_SOURCES) \
-       $(tests64_csinh_SOURCES) $(tests64_csqrt_SOURCES) \
-       $(tests64_ctan_SOURCES) $(tests64_ctanh_SOURCES) \
-       tstmain_sys_xxx.c tstmainc.c $(tstmaincpp_SOURCES)
+       t_stderr_buffering.c t_stdint.c t_stprintf0_a.c \
+       t_stprintf0_u.c t_stprintf1_a.c t_stprintf1_u.c t_stprintf_a.c \
+       t_stprintf_u.c t_swprintf.c t_swprintf0.c t_swprintf1.c \
+       t_time.c t_tls1.c t_tmain.c t_tmpfile.c $(t_trycatch_SOURCES) \
+       t_utime.c t_vsscanf.c t_wcrtomb.c t_wcsrtombs.c t_wcstok_s.c \
+       t_wctob.c t_wreaddir.c $(tests32_cabs_SOURCES) \
+       $(tests32_cacos_SOURCES) $(tests32_cacosh_SOURCES) \
+       $(tests32_carg_SOURCES) $(tests32_casin_SOURCES) \
+       $(tests32_casinh_SOURCES) $(tests32_catan_SOURCES) \
+       $(tests32_catanh_SOURCES) $(tests32_ccos_SOURCES) \
+       $(tests32_ccosh_SOURCES) $(tests32_cexp_SOURCES) \
+       $(tests32_cimag_SOURCES) $(tests32_clog_SOURCES) \
+       $(tests32_conj_SOURCES) $(tests32_cpow_SOURCES) \
+       $(tests32_cproj_SOURCES) $(tests32_creal_SOURCES) \
+       $(tests32_csin_SOURCES) $(tests32_csinh_SOURCES) \
+       $(tests32_csqrt_SOURCES) $(tests32_ctan_SOURCES) \
+       $(tests32_ctanh_SOURCES) $(tests64_cabs_SOURCES) \
+       $(tests64_cacos_SOURCES) $(tests64_cacosh_SOURCES) \
+       $(tests64_carg_SOURCES) $(tests64_casin_SOURCES) \
+       $(tests64_casinh_SOURCES) $(tests64_catan_SOURCES) \
+       $(tests64_catanh_SOURCES) $(tests64_ccos_SOURCES) \
+       $(tests64_ccosh_SOURCES) $(tests64_cexp_SOURCES) \
+       $(tests64_cimag_SOURCES) $(tests64_clog_SOURCES) \
+       $(tests64_conj_SOURCES) $(tests64_cpow_SOURCES) \
+       $(tests64_cproj_SOURCES) $(tests64_creal_SOURCES) \
+       $(tests64_csin_SOURCES) $(tests64_csinh_SOURCES) \
+       $(tests64_csqrt_SOURCES) $(tests64_ctan_SOURCES) \
+       $(tests64_ctanh_SOURCES) tstmain_sys_xxx.c tstmainc.c \
+       $(tstmaincpp_SOURCES)
 DIST_SOURCES = $(am__tests32_libnewcomplextests_a_SOURCES_DIST) \
        $(am__tests32_libnewcomplextestsf_a_SOURCES_DIST) \
        $(am__tests32_libnewcomplextestsld_a_SOURCES_DIST) \
@@ -1285,12 +1290,12 @@ DIST_SOURCES = 
$(am__tests32_libnewcomplextests_a_SOURCES_DIST) \
        t_snprintf.c t_snprintf0.c t_snprintf1.c t_snwprintf.c \
        t_snwprintf0.c t_snwprintf1.c t_speed_powl.c t_stat.c \
        t_stat_f64.c t_stat_slash.c t_stat_t64.c t_stat_t64_f64.c \
-       t_stdint.c t_stprintf0_a.c t_stprintf0_u.c t_stprintf1_a.c \
-       t_stprintf1_u.c t_stprintf_a.c t_stprintf_u.c t_swprintf.c \
-       t_swprintf0.c t_swprintf1.c t_time.c t_tls1.c t_tmain.c \
-       t_tmpfile.c $(t_trycatch_SOURCES) t_utime.c t_vsscanf.c \
-       t_wcrtomb.c t_wcsrtombs.c t_wcstok_s.c t_wctob.c t_wreaddir.c \
-       $(am__tests32_cabs_SOURCES_DIST) \
+       t_stderr_buffering.c t_stdint.c t_stprintf0_a.c \
+       t_stprintf0_u.c t_stprintf1_a.c t_stprintf1_u.c t_stprintf_a.c \
+       t_stprintf_u.c t_swprintf.c t_swprintf0.c t_swprintf1.c \
+       t_time.c t_tls1.c t_tmain.c t_tmpfile.c $(t_trycatch_SOURCES) \
+       t_utime.c t_vsscanf.c t_wcrtomb.c t_wcsrtombs.c t_wcstok_s.c \
+       t_wctob.c t_wreaddir.c $(am__tests32_cabs_SOURCES_DIST) \
        $(am__tests32_cacos_SOURCES_DIST) \
        $(am__tests32_cacosh_SOURCES_DIST) \
        $(am__tests32_carg_SOURCES_DIST) \
@@ -1711,9 +1716,9 @@ testcase_progs = tstmainc tstmaincpp tstmain_sys_xxx 
t__fstat_all \
        t_snwprintf t_snwprintf0 t_snwprintf1 t_stprintf_a \
        t_stprintf_u t_stprintf0_a t_stprintf0_u t_stprintf1_a \
        t_stprintf1_u t_setjmp t_sigv t_speed_powl t_stat t_stat_f64 \
-       t_stat_t64 t_stat_t64_f64 t_stdint t_time t_tls1 t_tmpfile \
-       t_trycatch t_stat_slash t_utime t_vsscanf t_wcrtomb \
-       t_wcsrtombs t_wcstok_s t_wctob t_wreaddir t_fseeko64 \
+       t_stat_t64 t_stat_t64_f64 t_stderr_buffering t_stdint t_time \
+       t_tls1 t_tmpfile t_trycatch t_stat_slash t_utime t_vsscanf \
+       t_wcrtomb t_wcsrtombs t_wcstok_s t_wctob t_wreaddir t_fseeko64 \
        $(am__append_1)
 EXTRA_DIST = \
   t_snprintf_tmpl.h \
@@ -2541,6 +2546,10 @@ t_stat_t64_f64$(EXEEXT): $(t_stat_t64_f64_OBJECTS) 
$(t_stat_t64_f64_DEPENDENCIES
        @rm -f t_stat_t64_f64$(EXEEXT)
        $(AM_V_CCLD)$(LINK) $(t_stat_t64_f64_OBJECTS) $(t_stat_t64_f64_LDADD) 
$(LIBS)
 
+t_stderr_buffering$(EXEEXT): $(t_stderr_buffering_OBJECTS) 
$(t_stderr_buffering_DEPENDENCIES) $(EXTRA_t_stderr_buffering_DEPENDENCIES) 
+       @rm -f t_stderr_buffering$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(t_stderr_buffering_OBJECTS) 
$(t_stderr_buffering_LDADD) $(LIBS)
+
 t_stdint$(EXEEXT): $(t_stdint_OBJECTS) $(t_stdint_DEPENDENCIES) 
$(EXTRA_t_stdint_DEPENDENCIES) 
        @rm -f t_stdint$(EXEEXT)
        $(AM_V_CCLD)$(LINK) $(t_stdint_OBJECTS) $(t_stdint_LDADD) $(LIBS)
@@ -2954,6 +2963,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_stat_slash.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_stat_t64.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_stat_t64_f64.Po@am__quote@ 
# am--include-marker
+@AMDEP_TRUE@@am__include@ 
@am__quote@./$(DEPDIR)/t_stderr_buffering.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_stdint.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_stprintf0_a.Po@am__quote@ # 
am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_stprintf0_u.Po@am__quote@ # 
am--include-marker
@@ -6257,6 +6267,13 @@ t_stat_t64_f64.log: t_stat_t64_f64$(EXEEXT)
        --log-file $$b.log --trs-file $$b.trs \
        $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) 
-- $(LOG_COMPILE) \
        "$$tst" $(AM_TESTS_FD_REDIRECT)
+t_stderr_buffering.log: t_stderr_buffering$(EXEEXT)
+       @p='t_stderr_buffering$(EXEEXT)'; \
+       b='t_stderr_buffering'; \
+       $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+       --log-file $$b.log --trs-file $$b.trs \
+       $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) 
-- $(LOG_COMPILE) \
+       "$$tst" $(AM_TESTS_FD_REDIRECT)
 t_stdint.log: t_stdint$(EXEEXT)
        @p='t_stdint$(EXEEXT)'; \
        b='t_stdint'; \
@@ -6822,6 +6839,7 @@ distclean: distclean-am
        -rm -f ./$(DEPDIR)/t_stat_slash.Po
        -rm -f ./$(DEPDIR)/t_stat_t64.Po
        -rm -f ./$(DEPDIR)/t_stat_t64_f64.Po
+       -rm -f ./$(DEPDIR)/t_stderr_buffering.Po
        -rm -f ./$(DEPDIR)/t_stdint.Po
        -rm -f ./$(DEPDIR)/t_stprintf0_a.Po
        -rm -f ./$(DEPDIR)/t_stprintf0_u.Po
@@ -7110,6 +7128,7 @@ maintainer-clean: maintainer-clean-am
        -rm -f ./$(DEPDIR)/t_stat_slash.Po
        -rm -f ./$(DEPDIR)/t_stat_t64.Po
        -rm -f ./$(DEPDIR)/t_stat_t64_f64.Po
+       -rm -f ./$(DEPDIR)/t_stderr_buffering.Po
        -rm -f ./$(DEPDIR)/t_stdint.Po
        -rm -f ./$(DEPDIR)/t_stprintf0_a.Po
        -rm -f ./$(DEPDIR)/t_stprintf0_u.Po
diff --git a/mingw-w64-crt/testcases/t_stderr_buffering.c 
b/mingw-w64-crt/testcases/t_stderr_buffering.c
new file mode 100644
index 00000000..82938c24
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_stderr_buffering.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <process.h>
+#include <io.h>
+#include <fcntl.h>
+#include <windows.h>
+
+#define STRING "hello world!"
+
+int main(int argc, char *argv[]) {
+    if (argc != 2 || strcmp(argv[1], "stderr_buffering_test") != 0) {
+        int exit_code;
+        int pipefd[2];
+        int back_errfd;
+        intptr_t process;
+        ssize_t size;
+        char buf[512];
+
+        assert(_pipe(pipefd, sizeof(buf), _O_NOINHERIT) == 0);
+
+        /* set stderr fd to write side of pipe, will be used by _spawnl() */
+        assert((back_errfd = dup(STDERR_FILENO)) >= 0);
+        assert(dup2(pipefd[1], STDERR_FILENO) == 0);
+        assert(close(pipefd[1]) == 0);
+
+        process = _spawnl(_P_NOWAIT, _pgmptr, argv[0], 
"stderr_buffering_test", NULL);
+
+        /* revert back stderr fd */
+        assert(dup2(back_errfd, STDERR_FILENO) == 0);
+        assert(close(back_errfd) == 0);
+
+        assert(process != -1);
+
+        size = read(pipefd[0], buf, sizeof(buf));
+        close(pipefd[0]);
+
+        /* wait until child process exits */
+        assert(_cwait(&exit_code, process, _WAIT_CHILD) == process);
+        assert(exit_code == 0);
+
+        assert(size > 0); /* some data were written by child process */
+        assert(strncmp(buf, STRING, strlen (STRING)) == 0);
+
+        return 0;
+    }
+
+    /* stderr must not be fully-buffered on startup */
+    fputs(STRING "\n", stderr);
+    assert(!ferror (stderr));
+
+    /* could also use _Exit here... */
+    TerminateProcess(GetCurrentProcess(), 0);
+
+    /* unreachable */
+    assert(0);
+}
-- 
2.49.0.windows.1

From 0fc043eb79cc55e303509077e07639545ba67a1c Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Tue, 16 Dec 2025 15:32:03 +0100
Subject: [PATCH 2/3] crt: Fix comment

Signed-off-by: Luca Bacci <[email protected]>
---
 mingw-w64-crt/crt/crtexe.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
index 94bad6aa..439d1aea 100644
--- a/mingw-w64-crt/crt/crtexe.c
+++ b/mingw-w64-crt/crt/crtexe.c
@@ -215,8 +215,8 @@ __tmainCRTStartup (void)
        if (ret != 0)
          _amsg_exit (8); /* _RT_SPACEARG */
 
-       _initterm (__xc_a, __xc_z);
-       __main (); /* C++ initialization. */
+       _initterm (__xc_a, __xc_z); /* C++ initialization */
+       __main ();
 
        __native_startup_state = __initialized;
       }
-- 
2.49.0.windows.1

From 80886dc4cdf8295c391eac7fb3ba75af52a1b124 Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Sat, 13 Dec 2025 14:22:58 +0100
Subject: [PATCH 3/3] crt: Ensure that stderr is not fully buffered

CRT libraries other than the UCRT can open stderr in full-buffering
mode. This happens for example when output goes to a pipe. The C
standard disallow such mode on stderr (C23 7.23.3 pt. 7) [1]:

> as initially opened, the standard error stream is not fully buffered

Here we ensure that stderr is unbuffered. Note that we can't use line
buffering since it's the same as full buffering [2]:

> _IOLBF: For some systems this mode provides line buffering.
  However on Win32 the behavior is the same as _IOFBF - Full Buffering.

References:

1. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#subsection.7.23.3
2. 
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf?view=msvc-170
3. https://sourceforge.net/p/mingw/mailman/message/27121137/

Signed-off-by: Luca Bacci <[email protected]>
---
 mingw-w64-crt/crt/crtexe.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
index 439d1aea..03a5d083 100644
--- a/mingw-w64-crt/crt/crtexe.c
+++ b/mingw-w64-crt/crt/crtexe.c
@@ -10,6 +10,7 @@
 #include <signal.h>
 #include <math.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <tchar.h>
 #include <sect_attribs.h>
 #include <locale.h>
@@ -215,6 +216,17 @@ __tmainCRTStartup (void)
        if (ret != 0)
          _amsg_exit (8); /* _RT_SPACEARG */
 
+       /* Before the UCRT stderr could be opened in full buffering
+        * mode, for example when output goes to a pipe.
+        *
+        * The C standard disallow full buffering on stderr. Note
+        * that line buffering is the same as full buffering in the
+        * Windows CRT, so we have to disable buffering altogether.
+        */
+       ret = setvbuf (stderr, NULL, _IONBF, 0);
+       if (ret != 0)
+         return 255;
+
        _initterm (__xc_a, __xc_z); /* C++ initialization */
        __main ();
 
-- 
2.49.0.windows.1

_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to