Fix stdio input stream flushing and positioning behavior (RHEL-119434)

Resolves: RHEL-119434
This commit is contained in:
Frédéric Bérat 2025-10-23 10:18:53 +02:00
parent 02111ad8a7
commit d686f6fe8b
8 changed files with 1622 additions and 0 deletions

71
glibc-RHEL-119434-1.patch Normal file
View File

@ -0,0 +1,71 @@
commit 04b1eb161fdc8b88876bf78b34d2bb92584aba45
Author: Siddhesh Poyarekar <siddhesh@sourceware.org>
Date: Fri Nov 8 12:33:47 2024 -0500
support: Add xdup
Add xdup as the error-checking version of dup for test cases.
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/support/Makefile b/support/Makefile
index af3f69f5df76e6f9..6b859e85ddb157f4 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -124,6 +124,7 @@ libsupport-routines = \
xcopy_file_range \
xdlfcn \
xdlmopen \
+ xdup \
xdup2 \
xfchmod \
xfclose \
diff --git a/support/xdup.c b/support/xdup.c
new file mode 100644
index 0000000000000000..1eab317354f1f353
--- /dev/null
+++ b/support/xdup.c
@@ -0,0 +1,30 @@
+/* dup with error checking.
+ Copyright The GNU Toolchain Authors.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/xunistd.h>
+#include <support/check.h>
+
+int
+xdup (int from)
+{
+ int ret = dup (from);
+ if (ret < 0)
+ FAIL_EXIT1 ("dup (%d): %m", from);
+
+ return ret;
+}
diff --git a/support/xunistd.h b/support/xunistd.h
index 204951bce75f576b..0c6d837ac073ca2b 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -35,6 +35,7 @@ pid_t xfork (void);
pid_t xwaitpid (pid_t, int *status, int flags);
void xpipe (int[2]);
void xdup2 (int, int);
+int xdup (int);
int xopen (const char *path, int flags, mode_t);
void support_check_stat_fd (const char *name, int fd, int result);
void support_check_stat_path (const char *name, const char *path, int result);

119
glibc-RHEL-119434-2.patch Normal file
View File

@ -0,0 +1,119 @@
commit 377e9733b50ce41e496c467ddcc112f73c88f3bd
Author: Joseph Myers <josmyers@redhat.com>
Date: Tue Jan 28 19:38:27 2025 +0000
Fix fflush after ungetc on input file (bug 5994)
As discussed in bug 5994 (plus duplicates), POSIX requires fflush
after ungetc to discard pushed-back characters but preserve the file
position indicator. For this purpose, each ungetc decrements the file
position indicator by 1; it is unspecified after ungetc at the start
of the file, and after ungetwc, so no special handling is needed for
either of those cases.
This is fixed with appropriate logic in _IO_new_file_sync. I haven't
made any attempt to test or change things in this area for the "old"
functions; the case of files using mmap is addressed in a subsequent
patch (and there seem to be no problems in this area with files opened
with fmemopen).
Tested for x86_64.
diff --git a/libio/fileops.c b/libio/fileops.c
index d49e489f55d3a283..715bcf69328e769e 100644
--- a/libio/fileops.c
+++ b/libio/fileops.c
@@ -799,6 +799,11 @@ _IO_new_file_sync (FILE *fp)
if (fp->_IO_write_ptr > fp->_IO_write_base)
if (_IO_do_flush(fp)) return EOF;
delta = fp->_IO_read_ptr - fp->_IO_read_end;
+ if (_IO_in_backup (fp))
+ {
+ _IO_switch_to_main_get_area (fp);
+ delta += fp->_IO_read_ptr - fp->_IO_read_end;
+ }
if (delta != 0)
{
off64_t new_pos = _IO_SYSSEEK (fp, delta, 1);
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 2a842488fb69541b..501b6341711c1781 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -260,6 +260,7 @@ tests := \
tst-swscanf \
tst-tmpnam \
tst-ungetc \
+ tst-ungetc-fflush \
tst-ungetc-leak \
tst-ungetc-nomem \
tst-unlockedio \
diff --git a/stdio-common/tst-ungetc-fflush.c b/stdio-common/tst-ungetc-fflush.c
new file mode 100644
index 0000000000000000..a86d1fdb7f8cac88
--- /dev/null
+++ b/stdio-common/tst-ungetc-fflush.c
@@ -0,0 +1,64 @@
+/* Test flushing input file after ungetc (bug 5994).
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+int
+do_test (void)
+{
+ char *filename = NULL;
+ int fd = create_temp_file ("tst-ungetc-fflush", &filename);
+ TEST_VERIFY_EXIT (fd != -1);
+ xclose (fd);
+
+ /* Test as in bug 5994. */
+ FILE *fp = xfopen (filename, "w");
+ TEST_VERIFY_EXIT (fputs ("#include", fp) >= 0);
+ xfclose (fp);
+ fp = xfopen (filename, "r");
+ TEST_COMPARE (fgetc (fp), '#');
+ TEST_COMPARE (fgetc (fp), 'i');
+ TEST_COMPARE (ungetc ('@', fp), '@');
+ TEST_COMPARE (fflush (fp), 0);
+ TEST_COMPARE (lseek (fileno (fp), 0, SEEK_CUR), 1);
+ TEST_COMPARE (fgetc (fp), 'i');
+ TEST_COMPARE (fgetc (fp), 'n');
+ xfclose (fp);
+
+ /* Test as in bug 12799 (duplicate of 5994). */
+ fp = xfopen (filename, "w+");
+ TEST_VERIFY_EXIT (fputs ("hello world", fp) >= 0);
+ rewind (fp);
+ TEST_VERIFY (fileno (fp) >= 0);
+ char buffer[10];
+ TEST_COMPARE (fread (buffer, 1, 5, fp), 5);
+ TEST_COMPARE (fgetc (fp), ' ');
+ TEST_COMPARE (ungetc ('@', fp), '@');
+ TEST_COMPARE (fflush (fp), 0);
+ TEST_COMPARE (fgetc (fp), ' ');
+ xfclose (fp);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

355
glibc-RHEL-119434-3.patch Normal file
View File

@ -0,0 +1,355 @@
commit be6818be31e756398e45f70e2819d78be0961223
Author: Joseph Myers <josmyers@redhat.com>
Date: Tue Jan 28 20:22:56 2025 +0000
Make fclose seek input file to right offset (bug 12724)
As discussed in bug 12724 and required by POSIX, before an input file
(based on an underlying seekable file descriptor) is closed, fclose is
sometimes required to seek that file descriptor to the correct offset,
so that any other file descriptors sharing the underlying open file
description are left at that offset (as a motivating example, a script
could call a sequence of commands each of which processes some data
from (seekable) stdin using stdio; fclose needs to do this so that
each successive command can read exactly the data not handled by
previous commands), but glibc fails to do this.
The precise POSIX wording has changed a few times; in the 2024 edition
it's "If the file is not already at EOF, and the file is one capable
of seeking, the file offset of the underlying open file description
shall be set to the file position of the stream if the stream is the
active handle to the underlying file description.".
Add appropriate logic to _IO_new_file_close_it to handle this case. I
haven't made any attempt to test or change things in this area for the
"old" functions.
Note that there was a previous attempt to fix bug 12724, reverted in
commit eb6cbd249f4465b01f428057bf6ab61f5f0c07e3. The fix version here
addresses the original test in that bug report without breaking the
one given in a subsequent comment in that bug report (which works with
glibc before the patch, but maybe was broken by the original fix that
was reverted).
The logic here tries to take care not to seek the file, even to its
newly computed current offset, if at EOF / possibly not the active
handle; even seeking to the current offset would be problematic
because of a potential race (fclose computes the current offset,
another thread or process with the active handle does its own seek,
fclose does a seek (not permitted by POSIX in this case) that loses
the effect of the seek on the active handle in another thread or
process). There are tests included for various cases of being or not
being the active handle, though there aren't tests for the potential
race condition.
Tested for x86_64.
diff --git a/libio/fileops.c b/libio/fileops.c
index 715bcf69328e769e..d96941be9e2bccd6 100644
--- a/libio/fileops.c
+++ b/libio/fileops.c
@@ -127,15 +127,48 @@ _IO_new_file_init (struct _IO_FILE_plus *fp)
int
_IO_new_file_close_it (FILE *fp)
{
- int write_status;
+ int flush_status = 0;
if (!_IO_file_is_open (fp))
return EOF;
if ((fp->_flags & _IO_NO_WRITES) == 0
&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
- write_status = _IO_do_flush (fp);
- else
- write_status = 0;
+ flush_status = _IO_do_flush (fp);
+ else if (fp->_fileno >= 0
+ /* If this is the active handle, we must seek the
+ underlying open file description (possibly shared with
+ other file descriptors that remain open) to the correct
+ offset. But if this stream is in a state such that some
+ other handle might have become the active handle, then
+ (a) at the time it entered that state, the underlying
+ open file description had the correct offset, and (b)
+ seeking the underlying open file description, even to
+ its newly determined current offset, is not safe because
+ it can race with operations on a different active
+ handle. So check here for cases where it is necessary
+ to seek, while avoiding seeking in cases where it is
+ unsafe to do so. */
+ && (_IO_in_backup (fp)
+ || (fp->_mode <= 0 && fp->_IO_read_ptr < fp->_IO_read_end)
+ || (_IO_vtable_offset (fp) == 0
+ && fp->_mode > 0 && (fp->_wide_data->_IO_read_ptr
+ < fp->_wide_data->_IO_read_end))))
+ {
+ off64_t o = _IO_SEEKOFF (fp, 0, _IO_seek_cur, 0);
+ if (o == EOF)
+ {
+ if (errno != ESPIPE)
+ flush_status = EOF;
+ }
+ else
+ {
+ if (_IO_in_backup (fp))
+ o -= fp->_IO_save_end - fp->_IO_save_base;
+ flush_status = (_IO_SYSSEEK (fp, o, SEEK_SET) < 0 && errno != ESPIPE
+ ? EOF
+ : 0);
+ }
+ }
_IO_unsave_markers (fp);
@@ -160,7 +193,7 @@ _IO_new_file_close_it (FILE *fp)
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
- return close_status ? close_status : write_status;
+ return close_status ? close_status : flush_status;
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 501b6341711c1781..aa4ba158e8ea9b44 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -206,6 +206,7 @@ tests := \
tst-bz11319-fortify2 \
tst-cookie \
tst-dprintf-length \
+ tst-fclose-offset \
tst-fdopen \
tst-fdopen2 \
tst-ferror \
diff --git a/stdio-common/tst-fclose-offset.c b/stdio-common/tst-fclose-offset.c
new file mode 100644
index 0000000000000000..a31de1117c7dfeec
--- /dev/null
+++ b/stdio-common/tst-fclose-offset.c
@@ -0,0 +1,225 @@
+/* Test offset of input file descriptor after close (bug 12724).
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+int
+do_test (void)
+{
+ char *filename = NULL;
+ int fd = create_temp_file ("tst-fclose-offset", &filename);
+ TEST_VERIFY_EXIT (fd != -1);
+
+ /* Test offset of open file description for output and input streams
+ after fclose, case from bug 12724. */
+
+ const char buf[] = "hello world";
+ xwrite (fd, buf, sizeof buf);
+ TEST_COMPARE (lseek (fd, 1, SEEK_SET), 1);
+ int fd2 = xdup (fd);
+ FILE *f = fdopen (fd2, "w");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fputc (buf[1], f), buf[1]);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 2);
+
+ /* Likewise for an input stream. */
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fgetc (f), buf[2]);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 3);
+
+ /* Test offset of open file description for output and input streams
+ after fclose, case from comment on bug 12724 (failed after first
+ attempt at fixing that bug). This verifies that the offset is
+ not reset when there has been no input or output on the FILE* (in
+ that case, the FILE* might not be the active handle). */
+
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ xwrite (fd, buf, sizeof buf);
+ TEST_COMPARE (lseek (fd, 1, SEEK_SET), 1);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "w");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Likewise for an input stream. */
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Further cases without specific tests in bug 12724, to verify
+ proper operation of the rules about the offset only being set
+ when the stream is the active handle. */
+
+ /* Test offset set by fclose after fseek and fgetc. */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fseek (f, 1, SEEK_SET), 0);
+ TEST_COMPARE (fgetc (f), buf[1]);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 2);
+
+ /* Test offset not set by fclose after fseek and fgetc, if that
+ fgetc is at EOF (in which case the active handle might have
+ changed). */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fseek (f, sizeof buf, SEEK_SET), 0);
+ TEST_COMPARE (fgetc (f), EOF);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Test offset not set by fclose after fseek and fgetc and fflush
+ (active handle might have changed after fflush). */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fseek (f, 1, SEEK_SET), 0);
+ TEST_COMPARE (fgetc (f), buf[1]);
+ TEST_COMPARE (fflush (f), 0);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Test offset not set by fclose after fseek and fgetc, if the
+ stream is unbuffered (active handle might change at any
+ time). */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ setbuf (f, NULL);
+ TEST_COMPARE (fseek (f, 1, SEEK_SET), 0);
+ TEST_COMPARE (fgetc (f), buf[1]);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Also test such cases with the stream in wide mode. */
+
+ /* Test offset set by fclose after fseek and fgetwc. */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fseek (f, 1, SEEK_SET), 0);
+ TEST_COMPARE (fgetwc (f), (wint_t) buf[1]);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 2);
+
+ /* Test offset not set by fclose after fseek and fgetwc, if that
+ fgetwc is at EOF (in which case the active handle might have
+ changed). */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fseek (f, sizeof buf, SEEK_SET), 0);
+ TEST_COMPARE (fgetwc (f), WEOF);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Test offset not set by fclose after fseek and fgetwc and fflush
+ (active handle might have changed after fflush). */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ TEST_COMPARE (fseek (f, 1, SEEK_SET), 0);
+ TEST_COMPARE (fgetwc (f), (wint_t) buf[1]);
+ TEST_COMPARE (fflush (f), 0);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ /* Test offset not set by fclose after fseek and fgetwc, if the
+ stream is unbuffered (active handle might change at any
+ time). */
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ fd2 = xdup (fd);
+ f = fdopen (fd2, "r");
+ TEST_VERIFY_EXIT (f != NULL);
+ setbuf (f, NULL);
+ TEST_COMPARE (fseek (f, 1, SEEK_SET), 0);
+ TEST_COMPARE (fgetwc (f), (wint_t) buf[1]);
+ TEST_COMPARE (lseek (fd, 4, SEEK_SET), 4);
+ xfclose (f);
+ errno = 0;
+ TEST_COMPARE (lseek (fd2, 0, SEEK_CUR), -1);
+ TEST_COMPARE (errno, EBADF);
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), 4);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

150
glibc-RHEL-119434-4.patch Normal file
View File

@ -0,0 +1,150 @@
commit 94251ae99edaa911f4cb8056748dca0874ea268c
Author: Joseph Myers <josmyers@redhat.com>
Date: Tue Jan 28 21:53:49 2025 +0000
Make fflush (NULL) flush input files (bug 32369)
As discussed in bug 32369 and required by POSIX, the POSIX feature
fflush (NULL) should flush input files, not just output files. The
POSIX requirement is that "fflush() shall perform this flushing action
on all streams for which the behavior is defined above", and the
definition for input files is for "a stream open for reading with an
underlying file description, if the file is not already at EOF, and
the file is one capable of seeking".
Implement this requirement in glibc. (The underlying flushing
implementation is what deals with avoiding errors for seeking on an
unseekable file.)
Tested for x86_64.
diff --git a/libio/genops.c b/libio/genops.c
index 6b2f508f853b8969..0b5344d4cca4e6d9 100644
--- a/libio/genops.c
+++ b/libio/genops.c
@@ -705,6 +705,13 @@ _IO_flush_all (void)
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
+ if (_IO_fileno (fp) >= 0
+ && ((fp->_mode <= 0 && fp->_IO_read_ptr < fp->_IO_read_end)
+ || (_IO_vtable_offset (fp) == 0
+ && fp->_mode > 0 && (fp->_wide_data->_IO_read_ptr
+ < fp->_wide_data->_IO_read_end)))
+ && _IO_SYNC (fp) != 0)
+ result = EOF;
_IO_funlockfile (fp);
run_fp = NULL;
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index aa4ba158e8ea9b44..c795fd752a39b1e7 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -210,6 +210,7 @@ tests := \
tst-fdopen \
tst-fdopen2 \
tst-ferror \
+ tst-fflush-all-input \
tst-fgets \
tst-fgets2 \
tst-fileno \
diff --git a/stdio-common/tst-fflush-all-input.c b/stdio-common/tst-fflush-all-input.c
new file mode 100644
index 0000000000000000..8e3fca3a087a1699
--- /dev/null
+++ b/stdio-common/tst-fflush-all-input.c
@@ -0,0 +1,94 @@
+/* Test fflush (NULL) flushes input files (bug 32369).
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include <support/check.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+int
+do_test (void)
+{
+ FILE *temp = tmpfile ();
+ TEST_VERIFY_EXIT (temp != NULL);
+ fprintf (temp, "abc");
+ TEST_COMPARE (fflush (temp), 0);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_SET), 0);
+ TEST_COMPARE (fgetc (temp), 'a');
+ TEST_COMPARE (fflush (NULL), 0);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_CUR), 1);
+ xfclose (temp);
+
+ /* Likewise, but in wide mode. */
+ temp = tmpfile ();
+ TEST_VERIFY_EXIT (temp != NULL);
+ fwprintf (temp, L"abc");
+ TEST_COMPARE (fflush (temp), 0);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_SET), 0);
+ TEST_COMPARE (fgetwc (temp), L'a');
+ TEST_COMPARE (fflush (NULL), 0);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_CUR), 1);
+ xfclose (temp);
+
+ /* Similar tests, but with the flush implicitly occurring on exit
+ (in a forked subprocess). */
+
+ temp = tmpfile ();
+ TEST_VERIFY_EXIT (temp != NULL);
+ pid_t pid = xfork ();
+ if (pid == 0)
+ {
+ fprintf (temp, "abc");
+ TEST_COMPARE (fflush (temp), 0);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_SET), 0);
+ TEST_COMPARE (fgetc (temp), 'a');
+ exit (EXIT_SUCCESS);
+ }
+ else
+ {
+ TEST_COMPARE (xwaitpid (pid, NULL, 0), pid);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_CUR), 1);
+ xfclose (temp);
+ }
+
+ temp = tmpfile ();
+ TEST_VERIFY_EXIT (temp != NULL);
+ pid = xfork ();
+ if (pid == 0)
+ {
+ fwprintf (temp, L"abc");
+ TEST_COMPARE (fflush (temp), 0);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_SET), 0);
+ TEST_COMPARE (fgetwc (temp), L'a');
+ exit (EXIT_SUCCESS);
+ }
+ else
+ {
+ TEST_COMPARE (xwaitpid (pid, NULL, 0), pid);
+ TEST_COMPARE (lseek (fileno (temp), 0, SEEK_CUR), 1);
+ xfclose (temp);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>

115
glibc-RHEL-119434-5.patch Normal file
View File

@ -0,0 +1,115 @@
commit 0dcc0b2f63051863187dc678964eb17761b1a820
Author: Joseph Myers <josmyers@redhat.com>
Date: Tue Jan 28 22:35:21 2025 +0000
Fix fseek handling for mmap files after ungetc or fflush (bug 32529)
As discussed in bug 32529, fseek fails on files opened for reading
using mmap after ungetc. The implementation of fseek for such files
has an offset computation that's also incorrect after fflush. A
combined fix addresses both problems (with tests for both included as
well) and it seems reasonable to consider them a single bug.
Tested for x86_64.
diff --git a/libio/fileops.c b/libio/fileops.c
index d96941be9e2bccd6..e3967c1eb8ee6993 100644
--- a/libio/fileops.c
+++ b/libio/fileops.c
@@ -1106,11 +1106,18 @@ _IO_file_seekoff_mmap (FILE *fp, off64_t offset, int dir, int mode)
if (mode == 0)
return fp->_offset - (fp->_IO_read_end - fp->_IO_read_ptr);
+ if (_IO_in_backup (fp))
+ {
+ if (dir == _IO_seek_cur)
+ offset += fp->_IO_read_ptr - fp->_IO_read_end;
+ _IO_switch_to_main_get_area (fp);
+ }
+
switch (dir)
{
case _IO_seek_cur:
/* Adjust for read-ahead (bytes is buffer). */
- offset += fp->_IO_read_ptr - fp->_IO_read_base;
+ offset += fp->_offset - (fp->_IO_read_end - fp->_IO_read_ptr);
break;
case _IO_seek_set:
break;
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index c795fd752a39b1e7..9a5d324495946385 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -223,6 +223,7 @@ tests := \
tst-fphex-wide \
tst-fread \
tst-fseek \
+ tst-fseek-mmap \
tst-fwrite \
tst-getline \
tst-getline-enomem \
diff --git a/stdio-common/tst-fseek-mmap.c b/stdio-common/tst-fseek-mmap.c
new file mode 100644
index 0000000000000000..86fa99a1a2e3e7e9
--- /dev/null
+++ b/stdio-common/tst-fseek-mmap.c
@@ -0,0 +1,59 @@
+/* Test fseek on files using mmap (bug 32529).
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+int
+do_test (void)
+{
+ char *filename = NULL;
+ int fd = create_temp_file ("tst-fseek-mmap", &filename);
+ TEST_VERIFY_EXIT (fd != -1);
+ xclose (fd);
+
+ /* Test fseek after ungetc (bug 32529). */
+ FILE *fp = xfopen (filename, "w");
+ TEST_VERIFY (0 <= fputs ("test", fp));
+ xfclose (fp);
+
+ fp = xfopen (filename, "rm");
+ TEST_COMPARE (fgetc (fp), 't');
+ TEST_COMPARE (ungetc ('u', fp), 'u');
+ TEST_COMPARE (fseek (fp, 0, SEEK_CUR), 0);
+ xfclose (fp);
+
+ /* Test fseek positioning after fflush (another issue covered by the
+ same fix). */
+ fp = xfopen (filename, "rm");
+ TEST_COMPARE (fgetc (fp), 't');
+ TEST_COMPARE (fflush (fp), 0);
+ TEST_COMPARE (ftell (fp), 1);
+ TEST_COMPARE (fseek (fp, 0, SEEK_CUR), 0);
+ TEST_COMPARE (ftell (fp), 1);
+ TEST_COMPARE (fgetc (fp), 'e');
+ xfclose (fp);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

110
glibc-RHEL-119434-6.patch Normal file
View File

@ -0,0 +1,110 @@
commit 3ff3b9997cfef891ba33a14f1dcba0310d96369c
Author: Joseph Myers <josmyers@redhat.com>
Date: Tue Jan 28 23:20:08 2025 +0000
Fix fflush handling for mmap files after ungetc (bug 32535)
As discussed in bug 32535, fflush fails on files opened for reading
using mmap after ungetc. Fix the logic to handle this case and still
compute the file offset correctly.
Tested for x86_64.
diff --git a/libio/fileops.c b/libio/fileops.c
index e3967c1eb8ee6993..ac5ff9a4353d0201 100644
--- a/libio/fileops.c
+++ b/libio/fileops.c
@@ -858,17 +858,21 @@ libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)
int
_IO_file_sync_mmap (FILE *fp)
{
+ off64_t o = fp->_offset - (fp->_IO_read_end - fp->_IO_read_ptr);
if (fp->_IO_read_ptr != fp->_IO_read_end)
{
- if (__lseek64 (fp->_fileno, fp->_IO_read_ptr - fp->_IO_buf_base,
- SEEK_SET)
- != fp->_IO_read_ptr - fp->_IO_buf_base)
+ if (_IO_in_backup (fp))
+ {
+ _IO_switch_to_main_get_area (fp);
+ o -= fp->_IO_read_end - fp->_IO_read_base;
+ }
+ if (__lseek64 (fp->_fileno, o, SEEK_SET) != o)
{
fp->_flags |= _IO_ERR_SEEN;
return EOF;
}
}
- fp->_offset = fp->_IO_read_ptr - fp->_IO_buf_base;
+ fp->_offset = o;
fp->_IO_read_end = fp->_IO_read_ptr = fp->_IO_read_base;
return 0;
}
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 9a5d324495946385..710120820ee4e337 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -211,6 +211,7 @@ tests := \
tst-fdopen2 \
tst-ferror \
tst-fflush-all-input \
+ tst-fflush-mmap \
tst-fgets \
tst-fgets2 \
tst-fileno \
diff --git a/stdio-common/tst-fflush-mmap.c b/stdio-common/tst-fflush-mmap.c
new file mode 100644
index 0000000000000000..3bddb909e072caf2
--- /dev/null
+++ b/stdio-common/tst-fflush-mmap.c
@@ -0,0 +1,50 @@
+/* Test fflush after ungetc on files using mmap (bug 32535).
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+int
+do_test (void)
+{
+ char *filename = NULL;
+ int fd = create_temp_file ("tst-fflush-mmap", &filename);
+ TEST_VERIFY_EXIT (fd != -1);
+ xclose (fd);
+
+ /* Test fflush after ungetc (bug 32535). */
+ FILE *fp = xfopen (filename, "w");
+ TEST_VERIFY (0 <= fputs ("test", fp));
+ xfclose (fp);
+
+ fp = xfopen (filename, "rm");
+ TEST_COMPARE (fgetc (fp), 't');
+ TEST_COMPARE (ungetc ('u', fp), 'u');
+ TEST_COMPARE (fflush (fp), 0);
+ TEST_COMPARE (fgetc (fp), 't');
+ TEST_COMPARE (fgetc (fp), 'e');
+ xfclose (fp);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

595
glibc-RHEL-119434-7.patch Normal file
View File

@ -0,0 +1,595 @@
commit 203452a460143c2b0bf80e0e92027e0fd6e19fa4
Author: Joseph Myers <josmyers@redhat.com>
Date: Tue Jan 28 23:39:12 2025 +0000
Add test of input file flushing / offset issues
Having fixed several bugs relating to flushing of FILE* streams (with
fflush and other operations) and their offsets (both the file position
indicator in the FILE*, and the offset in the underlying open file
description), especially after ungetc but not limited to that case,
add a test that more systematically covers different combinations of
cases for such issues, with 57220 separate scenarios tested (which
include examples of all the five separate fixed bugs), all of which
pass given the five previous bug fixes.
Tested for x86_64.
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 710120820ee4e337..31ff420432453dfe 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -244,6 +244,7 @@ tests := \
tst-printf-round \
tst-printfsz \
tst-put-error \
+ tst-read-offset \
tst-renameat2 \
tst-rndseek \
tst-scanf-binary-c11 \
diff --git a/stdio-common/tst-read-offset.c b/stdio-common/tst-read-offset.c
new file mode 100644
index 0000000000000000..b8706607fc86da99
--- /dev/null
+++ b/stdio-common/tst-read-offset.c
@@ -0,0 +1,560 @@
+/* Test offsets in files being read, in particular with ungetc.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+static volatile bool fail = false;
+
+/* Induce a malloc failure whenever FAIL is set. */
+void *
+malloc (size_t sz)
+{
+ if (fail)
+ return NULL;
+
+ static void *(*real_malloc) (size_t);
+
+ if (real_malloc == NULL)
+ real_malloc = dlsym (RTLD_NEXT, "malloc");
+
+ return real_malloc (sz);
+}
+
+/* The name of the temporary file used by all the tests. */
+static char *filename;
+
+/* st_blksize value for that file, or BUFSIZ if out of range. */
+static int blksize = BUFSIZ;
+
+/* Test data, both written to that file and used as an in-memory
+ stream. */
+char test_data[2 * BUFSIZ];
+
+/* Ways to open a test stream for reading (that may use different code
+ paths in libio). */
+enum test_open_case
+ {
+ test_open_fopen,
+ test_open_fopen_m,
+ test_open_fopen64,
+ test_open_fopen64_m,
+ test_open_fmemopen,
+ test_open_max
+ };
+
+static const char *const test_open_case_name[test_open_max] =
+ {
+ "fopen", "fopen(mmap)", "fopen64", "fopen64(mmap)", "fmemopen"
+ };
+
+static FILE *
+open_test_stream (enum test_open_case c)
+{
+ FILE *fp;
+ switch (c)
+ {
+ case test_open_fopen:
+ fp = fopen (filename, "r");
+ break;
+
+ case test_open_fopen_m:
+ fp = fopen (filename, "rm");
+ break;
+
+ case test_open_fopen64:
+ fp = fopen64 (filename, "r");
+ break;
+
+ case test_open_fopen64_m:
+ fp = fopen64 (filename, "rm");
+ break;
+
+ case test_open_fmemopen:
+ fp = fmemopen (test_data, 2 * BUFSIZ, "r");
+ break;
+
+ default:
+ abort ();
+ }
+ TEST_VERIFY_EXIT (fp != NULL);
+ return fp;
+}
+
+/* Base locations at which the main test (ungetc calls then doing
+ something that clears ungetc characters, then checking offset)
+ starts. */
+enum test_base_loc
+ {
+ base_loc_start,
+ base_loc_blksize,
+ base_loc_half,
+ base_loc_bufsiz,
+ base_loc_eof,
+ base_loc_max
+ };
+
+static int
+base_loc_to_bytes (enum test_base_loc loc, int offset)
+{
+ switch (loc)
+ {
+ case base_loc_start:
+ return offset;
+
+ case base_loc_blksize:
+ return blksize + offset;
+
+ case base_loc_half:
+ return BUFSIZ / 2 + offset;
+
+ case base_loc_bufsiz:
+ return BUFSIZ + offset;
+
+ case base_loc_eof:
+ return 2 * BUFSIZ + offset;
+
+ default:
+ abort ();
+ }
+}
+
+/* Ways to clear data from ungetc. */
+enum clear_ungetc_case
+ {
+ clear_fseek,
+ clear_fseekm1,
+ clear_fseekp1,
+ clear_fseeko,
+ clear_fseekom1,
+ clear_fseekop1,
+ clear_fseeko64,
+ clear_fseeko64m1,
+ clear_fseeko64p1,
+ clear_fsetpos,
+ clear_fsetposu,
+ clear_fsetpos64,
+ clear_fsetpos64u,
+ clear_fflush,
+ clear_fflush_null,
+ clear_fclose,
+ clear_max
+ };
+
+static const char *const clear_ungetc_case_name[clear_max] =
+ {
+ "fseek", "fseek(-1)", "fseek(1)", "fseeko", "fseeko(-1)", "fseeko(1)",
+ "fseeko64", "fseeko64(-1)", "fseeko64(1)", "fsetpos", "fsetpos(before)",
+ "fsetpos64", "fsetpos64(before)", "fflush", "fflush(NULL)", "fclose"
+ };
+
+static int
+clear_offset (enum clear_ungetc_case c, int num_ungetc)
+{
+ switch (c)
+ {
+ case clear_fseekm1:
+ case clear_fseekom1:
+ case clear_fseeko64m1:
+ return -1;
+
+ case clear_fseekp1:
+ case clear_fseekop1:
+ case clear_fseeko64p1:
+ return 1;
+
+ case clear_fsetposu:
+ case clear_fsetpos64u:
+ return num_ungetc;
+
+ default:
+ return 0;
+ }
+}
+
+/* The offsets used with fsetpos / fsetpos64. */
+static fpos_t pos;
+static fpos64_t pos64;
+
+static int
+do_clear_ungetc (FILE *fp, enum clear_ungetc_case c, int num_ungetc)
+{
+ int ret;
+ int offset = clear_offset (c, num_ungetc);
+ switch (c)
+ {
+ case clear_fseek:
+ case clear_fseekm1:
+ case clear_fseekp1:
+ ret = fseek (fp, offset, SEEK_CUR);
+ break;
+
+ case clear_fseeko:
+ case clear_fseekom1:
+ case clear_fseekop1:
+ ret = fseeko (fp, offset, SEEK_CUR);
+ break;
+
+ case clear_fseeko64:
+ case clear_fseeko64m1:
+ case clear_fseeko64p1:
+ ret = fseeko64 (fp, offset, SEEK_CUR);
+ break;
+
+ case clear_fsetpos:
+ case clear_fsetposu:
+ ret = fsetpos (fp, &pos);
+ break;
+
+ case clear_fsetpos64:
+ case clear_fsetpos64u:
+ ret = fsetpos64 (fp, &pos64);
+ break;
+
+ case clear_fflush:
+ ret = fflush (fp);
+ break;
+
+ case clear_fflush_null:
+ ret = fflush (NULL);
+ break;
+
+ case clear_fclose:
+ ret = fclose (fp);
+ break;
+
+ default:
+ abort();
+ }
+ TEST_COMPARE (ret, 0);
+ return offset;
+}
+
+static bool
+clear_valid (enum test_open_case c, enum clear_ungetc_case cl)
+{
+ switch (c)
+ {
+ case test_open_fmemopen:
+ /* fflush is not valid for input memory streams, and fclose is
+ useless for this test for such streams because there is no
+ underlying open file description for which an offset could be
+ checked after fclose. */
+ switch (cl)
+ {
+ case clear_fflush:
+ case clear_fflush_null:
+ case clear_fclose:
+ return false;
+
+ default:
+ return true;
+ }
+
+ default:
+ /* All ways of clearing ungetc state are valid for streams with
+ an underlying file. */
+ return true;
+ }
+}
+
+static bool
+clear_closes_file (enum clear_ungetc_case cl)
+{
+ switch (cl)
+ {
+ case clear_fclose:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static void
+clear_getpos_before (FILE *fp, enum clear_ungetc_case c)
+{
+ switch (c)
+ {
+ case clear_fsetposu:
+ TEST_COMPARE (fgetpos (fp, &pos), 0);
+ break;
+
+ case clear_fsetpos64u:
+ TEST_COMPARE (fgetpos64 (fp, &pos64), 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+clear_getpos_after (FILE *fp, enum clear_ungetc_case c)
+{
+ switch (c)
+ {
+ case clear_fsetpos:
+ TEST_COMPARE (fgetpos (fp, &pos), 0);
+ break;
+
+ case clear_fsetpos64:
+ TEST_COMPARE (fgetpos64 (fp, &pos64), 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* Ways to verify results of clearing ungetc data. */
+enum verify_case
+ {
+ verify_read,
+ verify_ftell,
+ verify_ftello,
+ verify_ftello64,
+ verify_fd,
+ verify_max
+ };
+
+static const char *const verify_case_name[verify_max] =
+ {
+ "read", "ftell", "ftello", "ftello64", "fd"
+ };
+
+static bool
+valid_fd_offset (enum test_open_case c, enum clear_ungetc_case cl)
+{
+ switch (c)
+ {
+ case test_open_fmemopen:
+ /* No open file description. */
+ return false;
+
+ default:
+ /* fseek does not necessarily set the offset for the underlying
+ open file description ("If the most recent operation, other
+ than ftell(), on a given stream is fflush(), the file offset
+ in the underlying open file description shall be adjusted to
+ reflect the location specified by fseek()." in POSIX does not
+ include the case here where getc was the last operation).
+ Similarly, fsetpos does not necessarily set that offset
+ either. */
+ switch (cl)
+ {
+ case clear_fflush:
+ case clear_fflush_null:
+ case clear_fclose:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+}
+
+static bool
+verify_valid (enum test_open_case c, enum clear_ungetc_case cl,
+ enum verify_case v)
+{
+ switch (v)
+ {
+ case verify_fd:
+ return valid_fd_offset (c, cl);
+
+ default:
+ switch (cl)
+ {
+ case clear_fclose:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+}
+
+static bool
+verify_uses_fd (enum verify_case v)
+{
+ switch (v)
+ {
+ case verify_fd:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static int
+read_to_test_loc (FILE *fp, enum test_base_loc loc, int offset)
+{
+ int to_read = base_loc_to_bytes (loc, offset);
+ for (int i = 0; i < to_read; i++)
+ TEST_COMPARE (getc (fp), (unsigned char) i);
+ return to_read;
+}
+
+static void
+setup (void)
+{
+ int fd = create_temp_file ("tst-read-offset", &filename);
+ TEST_VERIFY_EXIT (fd != -1);
+ struct stat64 st;
+ xfstat64 (fd, &st);
+ if (st.st_blksize > 0 && st.st_blksize < BUFSIZ)
+ blksize = st.st_blksize;
+ printf ("BUFSIZ = %d, blksize = %d\n", BUFSIZ, blksize);
+ xclose (fd);
+ FILE *fp = xfopen (filename, "w");
+ for (size_t i = 0; i < 2 * BUFSIZ; i++)
+ {
+ unsigned char c = i;
+ TEST_VERIFY_EXIT (fputc (c, fp) == c);
+ test_data[i] = c;
+ }
+ xfclose (fp);
+}
+
+static void
+test_one_case (enum test_open_case c, enum test_base_loc loc, int offset,
+ int num_ungetc, int num_ungetc_diff, bool ungetc_fallback,
+ enum clear_ungetc_case cl, enum verify_case v)
+{
+ int full_offset = base_loc_to_bytes (loc, offset);
+ printf ("Testing %s offset %d ungetc %d different %d %s%s %s\n",
+ test_open_case_name[c], full_offset, num_ungetc, num_ungetc_diff,
+ ungetc_fallback ? "fallback " : "", clear_ungetc_case_name[cl],
+ verify_case_name[v]);
+ FILE *fp = open_test_stream (c);
+ int cur_offset = read_to_test_loc (fp, loc, offset);
+ clear_getpos_before (fp, cl);
+ for (int i = 0; i < num_ungetc; i++)
+ {
+ unsigned char c = (i >= num_ungetc - num_ungetc_diff
+ ? cur_offset
+ : cur_offset - 1);
+ if (ungetc_fallback)
+ fail = true;
+ TEST_COMPARE (ungetc (c, fp), c);
+ fail = false;
+ cur_offset--;
+ }
+ clear_getpos_after (fp, cl);
+ int fd = -1;
+ bool done_dup = false;
+ if (verify_uses_fd (v))
+ {
+ fd = fileno (fp);
+ TEST_VERIFY (fd != -1);
+ if (clear_closes_file (cl))
+ {
+ fd = xdup (fd);
+ done_dup = true;
+ }
+ }
+ cur_offset += do_clear_ungetc (fp, cl, num_ungetc);
+ switch (v)
+ {
+ case verify_read:
+ for (;
+ cur_offset <= full_offset + 1 && cur_offset < 2 * BUFSIZ;
+ cur_offset++)
+ TEST_COMPARE (getc (fp), (unsigned char) cur_offset);
+ break;
+
+ case verify_ftell:
+ TEST_COMPARE (ftell (fp), cur_offset);
+ break;
+
+ case verify_ftello:
+ TEST_COMPARE (ftello (fp), cur_offset);
+ break;
+
+ case verify_ftello64:
+ TEST_COMPARE (ftello64 (fp), cur_offset);
+ break;
+
+ case verify_fd:
+ TEST_COMPARE (lseek (fd, 0, SEEK_CUR), cur_offset);
+ break;
+
+ default:
+ abort ();
+ }
+ if (! clear_closes_file (cl))
+ {
+ int ret = fclose (fp);
+ TEST_COMPARE (ret, 0);
+ }
+ if (done_dup)
+ xclose (fd);
+}
+
+int
+do_test (void)
+{
+ setup ();
+ for (enum test_open_case c = 0; c < test_open_max; c++)
+ for (enum test_base_loc loc = 0; loc < base_loc_max; loc++)
+ for (int offset = -2; offset <= 3; offset++)
+ for (int num_ungetc = 0;
+ num_ungetc <= 2 && num_ungetc <= base_loc_to_bytes (loc, offset);
+ num_ungetc++)
+ for (int num_ungetc_diff = 0;
+ num_ungetc_diff <= num_ungetc;
+ num_ungetc_diff++)
+ for (int ungetc_fallback = 0;
+ ungetc_fallback <= (num_ungetc == 1 ? 1 : 0);
+ ungetc_fallback++)
+ for (enum clear_ungetc_case cl = 0; cl < clear_max; cl++)
+ {
+ if (!clear_valid (c, cl))
+ continue;
+ if (base_loc_to_bytes (loc, offset) > 2 * BUFSIZ)
+ continue;
+ if ((base_loc_to_bytes (loc, offset)
+ - num_ungetc
+ + clear_offset (cl, num_ungetc)) < 0)
+ continue;
+ if ((base_loc_to_bytes (loc, offset)
+ - num_ungetc
+ + clear_offset (cl, num_ungetc)) > 2 * BUFSIZ)
+ continue;
+ for (enum verify_case v = 0; v < verify_max; v++)
+ {
+ if (!verify_valid (c, cl, v))
+ continue;
+ test_one_case (c, loc, offset, num_ungetc,
+ num_ungetc_diff, ungetc_fallback, cl, v);
+ }
+ }
+ return 0;
+}
+
+#include <support/test-driver.c>

107
glibc-RHEL-119434-8.patch Normal file
View File

@ -0,0 +1,107 @@
commit 7b47b3dd214c8ff2c699f13efe5533941be53635
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Apr 8 18:38:38 2025 +0200
libio: Synthesize ESPIPE error if lseek returns 0 after reading bytes
This is required so that fclose, when trying to seek to the right
position after filling the input buffer, does not fail with EINVAL.
This fclose code path only ignores ESPIPE errors.
Reported by Petr Pisar on
<https://bugzilla.redhat.com/show_bug.cgi?id=2358265>.
Fixes commit be6818be31e756398e45f70e2819d78be0961223 ("Make fclose
seek input file to right offset (bug 12724)").
Reviewed-by: Frédéric Bérat <fberat@redhat.com>
diff --git a/libio/fileops.c b/libio/fileops.c
index ac5ff9a4353d0201..cca52c09beb11747 100644
--- a/libio/fileops.c
+++ b/libio/fileops.c
@@ -928,6 +928,16 @@ do_ftell (FILE *fp)
if (result == EOF)
return result;
+ if (result == 0 && offset < 0)
+ {
+ /* This happens for some character devices that always report
+ file offset 0 even after some data has been read (instead of
+ failing with ESPIPE). The fclose path ignores this
+ error. */
+ __set_errno (ESPIPE);
+ return EOF;
+ }
+
result += offset;
if (result < 0)
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 31ff420432453dfe..cee076cb7bcff2d2 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -206,6 +206,7 @@ tests := \
tst-bz11319-fortify2 \
tst-cookie \
tst-dprintf-length \
+ tst-fclose-devzero \
tst-fclose-offset \
tst-fdopen \
tst-fdopen2 \
diff --git a/stdio-common/tst-fclose-devzero.c b/stdio-common/tst-fclose-devzero.c
new file mode 100644
index 0000000000000000..1c7b39a3e04ba80d
--- /dev/null
+++ b/stdio-common/tst-fclose-devzero.c
@@ -0,0 +1,50 @@
+/* Test that always-zero lseek does not cause fclose failure after fread.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library 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.
+
+ The GNU C Library 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 the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <support/check.h>
+#include <support/xstdio.h>
+
+int
+do_test (void)
+{
+ for (int do_ftello = 0; do_ftello < 2; ++do_ftello)
+ {
+ FILE *fp = xfopen ("/dev/zero", "r");
+ char buf[17];
+ memset (buf, 0xcc, sizeof (buf));
+ xfread (buf, 1, sizeof (buf), fp);
+ static const char zeros[sizeof (buf)] = { 0 };
+ TEST_COMPARE_BLOB (buf, sizeof (buf), zeros, sizeof (zeros));
+ if (do_ftello)
+ {
+ errno = 0;
+ TEST_COMPARE (ftello (fp), -1);
+ TEST_COMPARE (errno, ESPIPE);
+ }
+ /* Do not use xfclose because it flushes first. */
+ TEST_COMPARE (fclose (fp), 0);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>