]> git.ipfire.org Git - thirdparty/gnulib.git/commitdiff
stdio-h: Work around fwrite bug in msvcrt.
authorBruno Haible <bruno@clisp.org>
Wed, 17 Sep 2025 06:38:14 +0000 (08:38 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 17 Sep 2025 09:30:19 +0000 (11:30 +0200)
Reported by 松延 英樹 <maznobu@gmail.com> 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
doc/posix-functions/fwrite.texi
lib/stdio-consolesafe.c [new file with mode: 0644]
lib/stdio-write.c
lib/stdio.in.h
modules/stdio-h

index ef87fda436a3191803e21a46bf45548c51ff44f9..03b2f5bc3be8e9ab975ee7d34d4e755932eae4e7 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2025-09-17  Bruno Haible  <bruno@clisp.org>
+
+       stdio-h: Work around fwrite bug in msvcrt.
+       Reported by 松延 英樹 <maznobu@gmail.com> 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  <bruno@clisp.org>
 
        strerror_r-posix: Fix truncation code (regression today).
index 3922a9f1db61c7b58ca925cde27287d5590ca6bd..3f105befae6f04abdf8af01a175cf95c559995fd 100644 (file)
@@ -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 (file)
index 0000000..5cd0b12
--- /dev/null
@@ -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 <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;
+}
index b5073ad22914c2f75edc8818bfe61cf6df76d57c..1115111eda54de454943a7dab65ce15b53eba04e 100644 (file)
@@ -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)
 }
index 5370583470a68cddfa312f6bb6674d5af494106b..08db0290cda9942094ebb97a9448fe3b745dd9ca 100644 (file)
 #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@
index 6190bb95aae65f248a714ac13dcdb722829dadab..02a08688ec6577e6744cd22a4aff783908cd85d4 100644 (file)
@@ -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