]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
Fix fflush after ungetc on input file (bug 5994)
authorJoseph Myers <josmyers@redhat.com>
Tue, 28 Jan 2025 19:38:27 +0000 (19:38 +0000)
committerJoseph Myers <josmyers@redhat.com>
Tue, 28 Jan 2025 19:38:27 +0000 (19:38 +0000)
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.

libio/fileops.c
stdio-common/Makefile
stdio-common/tst-ungetc-fflush.c [new file with mode: 0644]

index 12b440b09d11abc56f1f5c7075d301fe70b38be3..42e695265d15268f6042971daf5c12b53d821e9e 100644 (file)
@@ -800,6 +800,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);
index 1e7bca641e47afecc878f7760bd8690735aa9cb0..4a3810cdf21d89b7d65f5ce6daa654bbd1491687 100644 (file)
@@ -305,6 +305,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 (file)
index 0000000..a86d1fd
--- /dev/null
@@ -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>