> > > FAIL: test-fflush2.sh > > > FAIL: test-fpurge > > > FAIL: test-ftell.sh > > > FAIL: test-ftell2.sh > > > FAIL: test-ftello.sh > > > FAIL: test-ftello2.sh > > These would need debugging on a macOS 10.15 machine. Anyone willing to take > this challenge?
The compilefarm now has a macOS 11.2 machine. These tests fail there as well. I could debug it, and found that it is a regression introduced in macOS 10.15.0: --- Libc-1272.250.1/stdio/FreeBSD/fflush.c 2015-02-04 23:57:51.000000000 +0100 +++ Libc-1353.11.2/stdio/FreeBSD/fflush.c 2019-09-21 00:18:36.000000000 +0200 ... @@ -110,12 +90,52 @@ int n, t; t = fp->_flags; - if ((t & __SWR) == 0) - return (0); if ((p = fp->_bf._base) == NULL) return (0); + /* + * SUSv3 requires that fflush() on a seekable input stream updates the file + * position indicator with the underlying seek function. Use a dumb fseek + * for this (don't attempt to preserve the buffers). + */ + if ((t & __SRD) != 0) { + if (fp->_seek == NULL) { + /* + * No way to seek this file -- just return "success." + */ + return (0); + } + + n = fp->_r; + + if (n > 0) { + /* + * See _fseeko's dumb path. + */ + if (_sseek(fp, (fpos_t)-n, SEEK_CUR) == -1) { + if (errno == ESPIPE) { + /* + * Ignore ESPIPE errors, since there's no way to put the bytes + * back into the pipe. + */ + return (0); + } + return (EOF); + } + + if (HASUB(fp)) { + FREEUB(fp); + } + fp->_p = fp->_bf._base; + fp->_r = 0; + fp->_flags &= ~__SEOF; + memset(&fp->_mbstate, 0, sizeof(mbstate_t)); + } + return (0); + } + + if ((t & __SWR) != 0) { n = fp->_p - p; /* write this much */ /* @@ -142,6 +162,7 @@ return (EOF); } } + } return (0); } They added new code to the function __sflush, which makes sense (in order to make fflush() POSIX compliant). But __sflush is being called by ftello(), and the new code in __sflush severely disturbs the functioning of ftello: - it causes the ungetc buffer to be discarded, - it causes a flush of buffered data (which no one has asked for), - it returns a broken file position. The bug was already spotted in the wild, in some non-GNU program: <https://github.com/espeak-ng/espeak-ng/issues/674>. This patch provides a workaround. 2021-03-20 Bruno Haible <br...@clisp.org> ftello: Work around bug in macOS >= 10.15. Reported by Martin Storsjö <mar...@martin.st> in <https://lists.gnu.org/archive/html/bug-gnulib/2020-12/msg00002.html>. * m4/ungetc.m4 (gl_FUNC_UNGETC_WORKS): On macOS, don't define FUNC_UNGETC_BROKEN. Instead, set gl_ftello_broken_after_ungetc to yes. * m4/ftello.m4 (gl_FUNC_FTELLO): Invoke gl_FUNC_UNGETC_WORKS, and arrange to provide the workaround if ftello is broken after ungetc. * lib/ftello.c: Include <errno.h>, intprops.h. (ftello) [FTELLO_BROKEN_AFTER_UNGETC]: Implement from scratch. * modules/ftello (Files): Add m4/ungetc.m4. (Depends-on): Add intprops. * doc/posix-functions/ftello.texi: Mention the macOS bug. diff --git a/doc/posix-functions/ftello.texi b/doc/posix-functions/ftello.texi index 2561c5d..eab591f 100644 --- a/doc/posix-functions/ftello.texi +++ b/doc/posix-functions/ftello.texi @@ -20,6 +20,11 @@ This function produces incorrect results after @code{putc} that followed a @code{getc} call that reached EOF on some platforms: Solaris 11 2010-11. @item +This function, when invoked after @code{ungetc}, throws away the @code{ungetc} +buffer, changes the stream's file position, and returns the wrong position on +some platforms: +macOS 10.15 and newer. +@item This function fails on seekable stdin, stdout, and stderr: cygwin <= 1.5.24. @item On platforms where @code{off_t} is a 32-bit type, @code{ftello} does not work diff --git a/lib/ftello.c b/lib/ftello.c index c5a4e0c..da13694 100644 --- a/lib/ftello.c +++ b/lib/ftello.c @@ -19,6 +19,9 @@ /* Specification. */ #include <stdio.h> +#include <errno.h> +#include "intprops.h" + /* Get lseek. */ #include <unistd.h> @@ -40,13 +43,79 @@ ftello (FILE *fp) # endif #endif { -#if LSEEK_PIPE_BROKEN +#if FTELLO_BROKEN_AFTER_UNGETC /* macOS >= 10.15 */ + /* The system's ftello() is completely broken, because it calls __sflush, + which makes side effects on the stream. */ + + /* Handle non-seekable files first. */ + if (fp->_file < 0 || fp->_seek == NULL) + { + errno = ESPIPE; + return -1; + } + + /* Determine the current offset, ignoring buffered and pushed-back bytes. */ + off_t pos; + + if (fp->_flags & __SOFF) + pos = fp->_offset; + else + { + pos = fp->_seek (fp->_cookie, 0, SEEK_CUR); + if (pos < 0) + return -1; + if (fp->_flags & __SOPT) + { + fp->_offset = pos; + fp->_flags |= __SOFF; + } + } + + if (fp->_flags & __SRD) + { + /* Now consider buffered and pushed-back bytes from ungetc. */ + if (fp->_ub._base != NULL) + /* Considering the buffered bytes, we are at position + pos - fp->_ur. + Considering also the pushed-back bytes, we are at position + pos - fp->_ur - fp->_r. */ + pos = pos - fp->_ur - fp->_r; + else + /* Considering the buffered bytes, we are at position + pos - fp->_r. */ + pos = pos - fp->_r; + if (pos < 0) + { + errno = EIO; + return -1; + } + } + else if ((fp->_flags & __SWR) && fp->_p != NULL) + { + /* Consider the buffered bytes. */ + off_t buffered = fp->_p - fp->_bf._base; + + /* Compute pos + buffered, with overflow check. */ + off_t sum; + if (! INT_ADD_OK (pos, buffered, &sum)) + { + errno = EOVERFLOW; + return -1; + } + pos = sum; + } + + return pos; + +#else + +# if LSEEK_PIPE_BROKEN /* mingw gives bogus answers rather than failure on non-seekable files. */ if (lseek (fileno (fp), 0, SEEK_CUR) == -1) return -1; -#endif +# endif -#if FTELLO_BROKEN_AFTER_SWITCHING_FROM_READ_TO_WRITE /* Solaris */ +# if FTELLO_BROKEN_AFTER_SWITCHING_FROM_READ_TO_WRITE /* Solaris */ /* The Solaris stdio leaves the _IOREAD flag set after reading from a file reaches EOF and the program then starts writing to the file. ftello gets confused by this. */ @@ -66,9 +135,9 @@ ftello (FILE *fp) } return pos; } -#endif +# endif -#if defined __SL64 && defined __SCLE /* Cygwin */ +# if defined __SL64 && defined __SCLE /* Cygwin */ if ((fp->_flags & __SL64) == 0) { /* Cygwin 1.5.0 through 1.5.24 failed to open stdin in 64-bit @@ -80,6 +149,9 @@ ftello (FILE *fp) fp->_seek64 = tmp->_seek64; fclose (tmp); } -#endif +# endif + return ftello (fp); + +#endif } diff --git a/m4/ftello.m4 b/m4/ftello.m4 index 1a0f7bc..65bec39 100644 --- a/m4/ftello.m4 +++ b/m4/ftello.m4 @@ -1,4 +1,4 @@ -# ftello.m4 serial 13 +# ftello.m4 serial 14 dnl Copyright (C) 2007-2021 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -130,6 +130,15 @@ main (void) ;; esac fi + if test $REPLACE_FTELLO = 0; then + dnl Detect bug on macOS >= 10.15. + gl_FUNC_UNGETC_WORKS + if test $gl_ftello_broken_after_ungetc = yes; then + REPLACE_FTELLO=1 + AC_DEFINE([FTELLO_BROKEN_AFTER_UNGETC], [1], + [Define to 1 if the system's ftello function has the macOS bug.]) + fi + fi fi ]) diff --git a/m4/ungetc.m4 b/m4/ungetc.m4 index b648fea..dd5d1dd 100644 --- a/m4/ungetc.m4 +++ b/m4/ungetc.m4 @@ -1,4 +1,4 @@ -# ungetc.m4 serial 9 +# ungetc.m4 serial 10 dnl Copyright (C) 2009-2021 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -55,11 +55,19 @@ AC_DEFUN_ONCE([gl_FUNC_UNGETC_WORKS], esac ]) ]) + gl_ftello_broken_after_ungetc=no case "$gl_cv_func_ungetc_works" in *yes) ;; *) - AC_DEFINE([FUNC_UNGETC_BROKEN], [1], - [Define to 1 if ungetc is broken when used on arbitrary bytes.]) + dnl On macOS >= 10.15, where the above program fails with exit code 6, + dnl we fix it through an ftello override. + case "$host_os" in + darwin*) gl_ftello_broken_after_ungetc=yes ;; + *) + AC_DEFINE([FUNC_UNGETC_BROKEN], [1], + [Define to 1 if ungetc is broken when used on arbitrary bytes.]) + ;; + esac ;; esac ]) diff --git a/modules/ftello b/modules/ftello index 6de0859..fc6bafb 100644 --- a/modules/ftello +++ b/modules/ftello @@ -6,6 +6,7 @@ lib/ftello.c lib/stdio-impl.h m4/fseeko.m4 m4/ftello.m4 +m4/ungetc.m4 Depends-on: stdio @@ -13,6 +14,7 @@ extensions largefile sys_types lseek [test $HAVE_FTELLO = 0 || test $REPLACE_FTELLO = 1] +intprops [test $HAVE_FTELLO = 0 || test $REPLACE_FTELLO = 1] # Just to guarantee consistency between ftell() and ftello(). ftell