Yesterday I wrote: > Bug 2: > > When the application outputs a string, that starts with a non-ASCII > character, using the function fwrite(), the console shows no output, > and the stream's error indicator gets set. > > How to reproduce: > 1. Use Windows 10 or 11. Switch it to Japanese as main language. > 2. Use the attached program. In the dev environment: > $ gcc -Wall foo.c > 3. In a cmd.exe console: > $ chcp 932 > $ .\a > Look at the output of the parts E and F. > ... > * Normal applications use fwrite() for binary I/O and fputs() or > [v][f]printf or similar for text I/O.
Well, actually Gnulib's vfprintf function override uses fwrite() for text I/O: lib/fzprintf.c:64: if (fwrite (output, 1, len, fp) < len) lib/vfzprintf.c:61: if (fwrite (output, 1, len, fp) < len) Therefore I'm adding this workaround. 2025-09-17 Bruno Haible <[email protected]> stdio-h: Work around fwrite bug in msvcrt. Reported by 松延 英樹 <[email protected]> in <https://github.com/mlocati/gettext-iconv-windows/issues/52>. * lib/stdio.in.h (gl_consolesafe_fwrite): New declaration. (fwrite): When msvcrt is in use, use gl_consolesafe_fwrite. * lib/stdio-consolesafe.c: New file. * lib/stdio-write.c (fwrite): When msvcrt is in use, use gl_consolesafe_fwrite. * modules/stdio.h (Files): Add lib/stdio-consolesafe.c. (Depends-on): Add stdckdint-h. (configure.ac): Define condition GL_COND_OBJ_STDIO_CONSOLESAFE. (Makefile.am): Arrange to compile stdio-consolesafe.c. * doc/posix-functions/fwrite.texi: Document the workaround.
From daafafb5943a7f992e111be2e2897a9649a05b32 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Wed, 17 Sep 2025 08:38:14 +0200 Subject: [PATCH 1/2] stdio-h: Work around fwrite bug in msvcrt. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported by ?????? ?????? <[email protected]> in <https://github.com/mlocati/gettext-iconv-windows/issues/52>. * lib/stdio.in.h (gl_consolesafe_fwrite): New declaration. (fwrite): When msvcrt is in use, use gl_consolesafe_fwrite. * lib/stdio-consolesafe.c: New file. * lib/stdio-write.c (fwrite): When msvcrt is in use, use gl_consolesafe_fwrite. * modules/stdio.h (Files): Add lib/stdio-consolesafe.c. (Depends-on): Add stdckdint-h. (configure.ac): Define condition GL_COND_OBJ_STDIO_CONSOLESAFE. (Makefile.am): Arrange to compile stdio-consolesafe.c. * doc/posix-functions/fwrite.texi: Document the workaround. --- ChangeLog | 16 +++++++ doc/posix-functions/fwrite.texi | 16 ++++--- lib/stdio-consolesafe.c | 74 +++++++++++++++++++++++++++++++++ lib/stdio-write.c | 3 ++ lib/stdio.in.h | 13 ++++++ modules/stdio-h | 17 ++++++++ 6 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 lib/stdio-consolesafe.c diff --git a/ChangeLog b/ChangeLog index ef87fda436..03b2f5bc3b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2025-09-17 Bruno Haible <[email protected]> + + stdio-h: Work around fwrite bug in msvcrt. + Reported by ?????? ?????? <[email protected]> in + <https://github.com/mlocati/gettext-iconv-windows/issues/52>. + * lib/stdio.in.h (gl_consolesafe_fwrite): New declaration. + (fwrite): When msvcrt is in use, use gl_consolesafe_fwrite. + * lib/stdio-consolesafe.c: New file. + * lib/stdio-write.c (fwrite): When msvcrt is in use, use + gl_consolesafe_fwrite. + * modules/stdio.h (Files): Add lib/stdio-consolesafe.c. + (Depends-on): Add stdckdint-h. + (configure.ac): Define condition GL_COND_OBJ_STDIO_CONSOLESAFE. + (Makefile.am): Arrange to compile stdio-consolesafe.c. + * doc/posix-functions/fwrite.texi: Document the workaround. + 2025-09-16 Bruno Haible <[email protected]> strerror_r-posix: Fix truncation code (regression today). diff --git a/doc/posix-functions/fwrite.texi b/doc/posix-functions/fwrite.texi index 3922a9f1db..3f105befae 100644 --- a/doc/posix-functions/fwrite.texi +++ b/doc/posix-functions/fwrite.texi @@ -9,6 +9,16 @@ @mindex nonblocking @mindex sigpipe +Portability problems fixed by Gnulib module @code{stdio-h}: +@itemize +@item +This function fails and produces no output +when the argument string starts with a non-ASCII character in double-byte encoding, +corresponding to the locale, on some platforms: +mingw in combination with msvcrt, +when the output goes to a Windows console. +@end itemize + Portability problems fixed by Gnulib module @code{stdio-h}, together with module @code{nonblocking}: @itemize @item @@ -32,12 +42,6 @@ On Windows platforms (excluding Cygwin), this function does not set @code{errno} upon failure. @item -This function fails and produces no output -when the argument string starts with a non-ASCII character in double-byte encoding, -corresponding to the locale, on some platforms: -mingw in combination with msvcrt, -when the output goes to a Windows console. -@item On some platforms, this function does not set @code{errno} or the stream error indicator on attempts to write to a read-only stream: Cygwin 1.7.9. diff --git a/lib/stdio-consolesafe.c b/lib/stdio-consolesafe.c new file mode 100644 index 0000000000..12a79fb7c3 --- /dev/null +++ b/lib/stdio-consolesafe.c @@ -0,0 +1,74 @@ +/* msvcrt workarounds. + Copyright (C) 2025 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +#include <config.h> + +/* Specification. */ +#include <stdio.h> + +#include <stdckdint.h> +#include <stdlib.h> +#include <string.h> + +/* Outputs N bytes starting at S to FP. + These N bytes are known to be followed by a NUL. + Finally frees the string at S. + Returns the number of written bytes. */ +static size_t +workaround_fwrite0 (char *s, size_t n, FILE *fp) +{ + const char *ptr = s; + /* Use fputs instead of fwrite, which is buggy in msvcrt. */ + size_t written = 0; + while (n > 0) + { + size_t l = strlen (ptr); /* 0 <= l <= n */ + if (l > 0) + { + if (fputs (ptr, fp) == EOF) + break; + written += l; + n -= l; + } + if (n == 0) + break; + if (fputc ('\0', fp) == EOF) + break; + written++; + n--; + ptr += l + 1; + } + free (s); + return written; +} + +size_t +gl_consolesafe_fwrite (const void *ptr, size_t size, size_t nmemb, FILE *fp) +{ + size_t nbytes; + if (ckd_mul (&nbytes, size, nmemb) || nbytes == 0) + /* Overflow, or nothing to do. */ + return 0; + char *tmp = malloc (nbytes + 1); + if (tmp == NULL) + return 0; + memcpy (tmp, ptr, nbytes); + tmp[nbytes] = '\0'; + size_t written = workaround_fwrite0 (tmp, nbytes, fp); + return written / size; +} + + diff --git a/lib/stdio-write.c b/lib/stdio-write.c index b5073ad229..1115111eda 100644 --- a/lib/stdio-write.c +++ b/lib/stdio-write.c @@ -198,6 +198,9 @@ puts (const char *string) size_t fwrite (const void *ptr, size_t s, size_t n, FILE *stream) #undef fwrite +#if (defined _WIN32 && !defined __CYGWIN__) && !defined _UCRT +# define fwrite gl_consolesafe_fwrite +#endif { CALL_WITH_SIGPIPE_EMULATION (size_t, fwrite (ptr, s, n, stream), ret < n) } diff --git a/lib/stdio.in.h b/lib/stdio.in.h index 5370583470..08db0290cd 100644 --- a/lib/stdio.in.h +++ b/lib/stdio.in.h @@ -271,6 +271,14 @@ #endif +#if (defined _WIN32 && !defined __CYGWIN__) && !defined _UCRT +/* Workarounds against msvcrt bugs. */ +_GL_FUNCDECL_SYS (gl_consolesafe_fwrite, size_t, + (const void *ptr, size_t size, size_t nmemb, FILE *fp), + _GL_ARG_NONNULL ((1, 4))); +#endif + + #if @GNULIB_DZPRINTF@ /* Prints formatted output to file descriptor FD. Returns the number of bytes written to the file descriptor. Upon @@ -961,6 +969,11 @@ _GL_EXTERN_C size_t __REDIRECT (rpl_fwrite_unlocked, # if __GLIBC__ >= 2 _GL_CXXALIASWARN (fwrite); # endif +#elif (defined _WIN32 && !defined __CYGWIN__) && !defined _UCRT +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef fwrite +# define fwrite gl_consolesafe_fwrite +# endif #endif #if @GNULIB_GETC@ diff --git a/modules/stdio-h b/modules/stdio-h index 6190bb95aa..02a08688ec 100644 --- a/modules/stdio-h +++ b/modules/stdio-h @@ -3,6 +3,7 @@ A GNU-like <stdio.h>. Files: lib/stdio.in.h +lib/stdio-consolesafe.c lib/stdio-read.c lib/stdio-write.c m4/stdio_h.m4 @@ -18,6 +19,7 @@ snippet/warn-on-use ssize_t stddef-h sys_types-h +stdckdint-h configure.ac-early: gl_STDIO_H_EARLY @@ -26,6 +28,18 @@ configure.ac: gl_STDIO_H gl_STDIO_H_REQUIRE_DEFAULTS AC_PROG_MKDIR_P +USES_MSVCRT=0 +case "$host_os" in + mingw* | windows*) + AC_EGREP_CPP([Special], [ +#ifndef _UCRT + Special +#endif + ], + [USES_MSVCRT=1]) + ;; +esac +gl_CONDITIONAL([GL_COND_OBJ_STDIO_CONSOLESAFE], [test $USES_MSVCRT = 1]) gl_CONDITIONAL([GL_COND_OBJ_STDIO_READ], [test $REPLACE_STDIO_READ_FUNCS = 1]) gl_CONDITIONAL([GL_COND_OBJ_STDIO_WRITE], [test $REPLACE_STDIO_WRITE_FUNCS = 1]) @@ -204,6 +218,9 @@ stdio.h: stdio.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(AM_V_at)mv $@-t3 $@ MOSTLYCLEANFILES += stdio.h stdio.h-t1 stdio.h-t2 stdio.h-t3 +if GL_COND_OBJ_STDIO_CONSOLESAFE +lib_SOURCES += stdio-consolesafe.c +endif if GL_COND_OBJ_STDIO_READ lib_SOURCES += stdio-read.c endif -- 2.50.1
