Here we are! I have attached patchset V4

> Anyway, it is correct to call it before _pei386_runtime_relocator?

>From what I can tell there's no problem with that. The call to setvbuf can be 
>made before applying (pseudo) relocations and setvbuf won't call back into our 
>module (EXE / DLL).

Changes in patchset V4:


  *
Dropped Makefile.in changes
  *
Dropped leftover assignment of setvbuf's return value to the 'ret' variable
  *
Changed testcase to use strict size checks and memcmp

Best Regards,
Luca
________________________________
Da: Pali Rohár <[email protected]>
Inviato: mercoledì 17 dicembre 2025 00:02
A: Luca Bacci <[email protected]>
Cc: Martin Storsjö <[email protected]>; Mingw W64 Public 
<[email protected]>
Oggetto: Re: Ensure that stderr is not fully buffered

On Tuesday 16 December 2025 17:46:10 Luca Bacci wrote:
> Good points! Here's the V3 patchset with all fixes applied
>
> > Is "\n" needed there? It can cause flush for line-buffered settings.
>
> Yes, that was on purpose. Line buffering on stderr is legal according to the 
> C standard. That's done for future-proofing in case support for _IOLBF is 
> ever added to the UCRT (and stderr is made line-buffered).

Ok, then it is fine.

> Best Regards,
> Luca
> ________________________________
> Da: Pali Rohár <[email protected]>
> Inviato: martedì 16 dicembre 2025 16:54
> A: Luca Bacci <[email protected]>
> Cc: Martin Storsjö <[email protected]>; Mingw W64 Public 
> <[email protected]>
> Oggetto: Re: Ensure that stderr is not fully buffered
>
> On Tuesday 16 December 2025 15:05:38 Luca Bacci wrote:
> > 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
>
> I'm also not really sure, but from its logic it looks like that it
> prevents duplicate execution by different threads at the same time.
> Similar logic has also code generated by msvc compiler.
> In past I sent some analysis of it into this list.
>
> > > 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...
>
> IMHO, we should skip it. stderr does not have to be available.
>
> >   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
>
> I think this is too late. Application code can contain other .CRT
> callbacks which are called before. Also some functions called by
> mingw-w64 could in some cases trigger assert which prints to stderr.
>
> Should it be called before or after _pei386_runtime_relocator?
>
> >   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?
>
> You can specify custom CC, CFLAGS and LDFLAGS options when calling
> "make check" command.
>
> >
> > 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.
>
> > 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);
>
> Is "\n" needed there? It can cause flush for line-buffered settings.
>
> > +    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 ();
>
> Comment was correct. gcc __main function is calling gcc generated c++
> constructors (initializations). __xc_a-__xc_z contains only user
> customer callbacks, not generated by gcc.
>
> >
> >        __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;
>
> This will break all GUI Windows applications as they are started by OS
> without the valid stdin, stdout and stderr. Hence setvbuf will fail on stderr.
>
> > +
> >        _initterm (__xc_a, __xc_z); /* C++ initialization */
> >        __main ();
> >
> > --
> > 2.49.0.windows.1
> >

> 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/2] 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

You should not modify Makefile.in files or include them into commits.

> @@ -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);

If we read less than strlen(STRING) bytes, then this strncmp can cause
memory corruption. read() does not fill null term byte.

We are not interested if "some data were written" but rather if the
exact STRING pattern was written.

So this check is wrong, it should rather be check for exact size and
then memcmp on fixed buffer length.

> +
> +        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 d4f7aa8a99cbca6c8bcbbe204aba103712948e36 Mon Sep 17 00:00:00 2001
> From: Luca Bacci <[email protected]>
> Date: Sat, 13 Dec 2025 14:22:58 +0100
> Subject: [PATCH 2/2] 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 | 10 ++++++++++
>  1 file changed, 10 insertions(+)
>
> diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
> index 94bad6aa..c926d229 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>
> @@ -168,6 +169,15 @@ __tmainCRTStartup (void)
>        {
>        __native_startup_state = __initializing;
>
> +     /* 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);

Variable ret is now unused. So assignment should not be there.

Anyway, it is correct to call it before _pei386_runtime_relocator?
This is what I do not know. That why I'm asking.

> +
>        _pei386_runtime_relocator ();
>  #if defined(__x86_64__) && !defined(__SEH__)
>        __mingw_init_ehandler ();
> --
> 2.49.0.windows.1
>
From 2fb7addac4e46d1cc6cc159856c0a5570e8d5974 Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Tue, 16 Dec 2025 15:08:57 +0100
Subject: [PATCH 1/2] 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/t_stderr_buffering.c | 66 ++++++++++++++++++++
 2 files changed, 67 insertions(+)
 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/t_stderr_buffering.c 
b/mingw-w64-crt/testcases/t_stderr_buffering.c
new file mode 100644
index 00000000..7f775438
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_stderr_buffering.c
@@ -0,0 +1,66 @@
+#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!\n"
+
+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;
+        size_t size = 0;
+        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);
+
+        /* read data as required */
+        while (size < strlen (STRING)) {
+            int delta = read(pipefd[0], buf + size, sizeof(buf) - size);
+            assert(delta > 0);
+            size += delta;
+        }
+
+        /* no more data is available */
+        assert(read(pipefd[0], buf + size, sizeof(buf) - size) == 0);
+        close(pipefd[0]);
+
+        /* wait until child process exits */
+        assert(_cwait(&exit_code, process, _WAIT_CHILD) == process);
+        assert(exit_code == 0);
+
+        assert(size == strlen (STRING));
+        assert(memcmp(buf, STRING, strlen (STRING)) == 0);
+
+        return 0;
+    }
+
+    /* stderr must not be fully-buffered on startup */
+    fputs(STRING, stderr);
+    assert(!ferror (stderr));
+
+    /* could also use _Exit here... */
+    TerminateProcess(GetCurrentProcess(), 0);
+
+    /* unreachable */
+    assert(0);
+}
-- 
2.49.0.windows.1

From f0354a2203d01ba7658273094e3f0283271eddbb Mon Sep 17 00:00:00 2001
From: Luca Bacci <[email protected]>
Date: Sat, 13 Dec 2025 14:22:58 +0100
Subject: [PATCH 2/2] 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 | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/mingw-w64-crt/crt/crtexe.c b/mingw-w64-crt/crt/crtexe.c
index 94bad6aa..787a72b8 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>
@@ -168,6 +169,15 @@ __tmainCRTStartup (void)
       {
        __native_startup_state = __initializing;
 
+       /* 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.
+        */
+       setvbuf (stderr, NULL, _IONBF, 0);
+
        _pei386_runtime_relocator ();
 #if defined(__x86_64__) && !defined(__SEH__)
        __mingw_init_ehandler ();
-- 
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