+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).
@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
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.
--- /dev/null
+/* 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;
+}
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)
}
#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
# 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@
Files:
lib/stdio.in.h
+lib/stdio-consolesafe.c
lib/stdio-read.c
lib/stdio-write.c
m4/stdio_h.m4
ssize_t
stddef-h
sys_types-h
+stdckdint-h
configure.ac-early:
gl_STDIO_H_EARLY
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])
$(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