From: Bruno Haible Date: Wed, 17 Sep 2025 06:38:14 +0000 (+0200) Subject: stdio-h: Work around fwrite bug in msvcrt. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dc8590b1ff4bebe52d4d08d3770b95298ae2575b;p=thirdparty%2Fgnulib.git stdio-h: Work around fwrite bug in msvcrt. Reported by 松延 英樹 in . * 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. --- diff --git a/ChangeLog b/ChangeLog index ef87fda436..03b2f5bc3b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2025-09-17 Bruno Haible + + stdio-h: Work around fwrite bug in msvcrt. + Reported by 松延 英樹 in + . + * 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 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 @@ Gnulib module: stdio-h, nonblocking, sigpipe @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 @@ Portability problems not fixed by Gnulib: 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..5cd0b12297 --- /dev/null +++ b/lib/stdio-consolesafe.c @@ -0,0 +1,72 @@ +/* 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 . */ + +#include + +/* Specification. */ +#include + +#include +#include +#include + +/* 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 . 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