diff mbox series

Add more thorough tests of freopen

Message ID 92e8ff20-fb9a-a289-2e58-faaa8bb71be2@redhat.com
State New
Headers show
Series Add more thorough tests of freopen | expand

Commit Message

Joseph Myers Aug. 30, 2024, 9:59 p.m. UTC
freopen is rather minimally tested in libio/tst-freopen and
libio/test-freopen.  Add some more thorough tests, covering different
cases for change of mode in particular.

Note that there are two parts of the test disabled because of bugs
discovered through running the test; one already has an open bug as
indicated in the comment, the other (failure to clear FD_CLOEXEC when
the mode indicates that should be done) doesn't appear to have an open
bug at present.  I expect to address those (including filing the
required bug for the latter) separately.  The test also doesn't cover
changes to cancellation ("c" in mode); I think that will better be
handled through a separate test.

There are support/ changes to add helper interfaces used in checking
file contents; those are worth careful review.

Tested for x86_64.

Comments

Florian Weimer Sept. 2, 2024, 2:38 p.m. UTC | #1
* Joseph Myers:

> freopen is rather minimally tested in libio/tst-freopen and
> libio/test-freopen.  Add some more thorough tests, covering different
> cases for change of mode in particular.

I think it's clear that it's possible to use freopen on streams created
by fopen (where it is somewhat redundant for us) and on the (in glibc's
case initial) values of stdin, stdout, stderr, where it is essential for
non-POSIX platforms.  But there are other ways to create streams.  It
seems to be the interaction with popen, fmemopen, open_memstream, and
glibc's fopencookie has been ignored so far.

Some of these combinations do not work at all in the glibc
implementation, something that cannot be fixed easily, so I think we
should document these limitations in the glibc manual.  This seems to be
unaddressed in the POSIX text.

Considering these gaps in specification, the selection of additional
tests looks useful to me.

You could add mtrace and <support/descriptors.h> checks around all the
tests to verify that there are now leaks, as in this test:

  [PATCH v4 09/13] dirent: Add tst-closedir-leaks
  <https://inbox.sourceware.org/libc-alpha/c507e98237dc4cec6da0536eed3ac8f13697468f.1725047142.git.fweimer@redhat.com/>

Descriptor checks around each subtest would provide more coverage, but
just around everything would be okay, too.

A separate test (using support_become_root, support_can_chroot) could
check what happens if /proc is not mounted and the /proc/self/fd code
path is used.

> There are support/ changes to add helper interfaces used in checking
> file contents; those are worth careful review.

The support/ changes should be in a separate commit, so that we can
backport them independently if necessary.

> diff --git a/stdio-common/tst-freopen2.c b/stdio-common/tst-freopen2.c
> new file mode 100644
> index 0000000000..4a5a3c016e
> --- /dev/null
> +++ b/stdio-common/tst-freopen2.c
> @@ -0,0 +1,446 @@

> +  /* Test each pair of old and new modes from r w a.  */
> +
> +  verbose_printf ("Testing r -> r\n");
> +  fp = xfopen (file1, "r");
> +  fp = freopen (file2, "r", fp);
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  TEST_COMPARE_FILE_STRING (fp, "file2");
> +  xfclose (fp);

As a general comment, I would have expected a check that the
non-modified file is in fact not modified.  Seems very likely given our
implementation, but still.  (I wonder if it would be conforming to
create a hard link to the new name on freopen.)

> +  verbose_printf ("testing freopen with NULL, different mode\n");
> +  fp = xfopen (file1, "w");
> +  ret = fputs ("different mode", fp);
> +  TEST_VERIFY (ret >= 0);
> +  fp = freopen (NULL, "r", fp);
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  TEST_COMPARE_FILE_STRING (fp, "different mode");
> +  xfclose (fp);
> +
> +  /* Test freopen with NULL, renamed file.  */

This is supposed to exercise the /proc/self/fd path and show that no new
pathname lookup is peformed, right?  (Along with the following test.)  A
comment could mention that.

> diff --git a/support/file_contents.h b/support/file_contents.h
> new file mode 100644
> index 0000000000..eea05eebc0
> --- /dev/null
> +++ b/support/file_contents.h
> @@ -0,0 +1,56 @@

> +#include <support/check.h>
> +#include <stdio.h>
> +
> +__BEGIN_DECLS
> +
> +/* Check that an already-open file has exactly the given bytes,
> +   starting at the current offset.  */
> +int support_compare_file_bytes (FILE *fp, const char *contents, size_t length);

The comment should say if the file position indicator is updated or not.
Missing description of the sense of the return value (0 apparently means
equal, 1 means unequal or read error).  Should this report read errors
via process termination?

> +/* Check that an already-open file has exactly the given string as
> +   contents, starting at the current offset.  */
> +int support_compare_file_string (FILE *fp, const char *contents);

Likewise.

> +/* Check that a not-currently-open file has exactly the given
> +   bytes.  */
> +int support_open_and_compare_file_bytes (const char *file,
> +					 const char *contents,
> +					 size_t length);
> +
> +/* Check that a not-currently-open file has exactly the given string
> +   as contents, starting at the current offset.  */
> +int support_open_and_compare_file_string (const char *file,
> +					  const char *contents);

Missing return value description.

> +/* Compare bytes read from an open file with the given string.  */
> +#define TEST_COMPARE_FILE_STRING(FP, CONTENTS)			\
> +  TEST_COMPARE (support_compare_file_string (FP, CONTENTS), 0)

Similar question about the file position indicator.

> +/* Open a file and compare bytes read from it with the given string.  */
> +#define TEST_OPEN_AND_COMPARE_FILE_STRING(FILE, CONTENTS)		\
> +  TEST_COMPARE (support_open_and_compare_file_string (FILE, CONTENTS), 0)

Suggest: “[Read] a file”

The file does not remain open after the call.

Thanks,
Florian
diff mbox series

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 948d960ccc..3c3d8a2bcd 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -216,6 +216,7 @@  tests := \
   tst-fmemopen4 \
   tst-fphex \
   tst-fphex-wide \
+  tst-freopen2 \
   tst-fseek \
   tst-fwrite \
   tst-getline \
diff --git a/stdio-common/tst-freopen2.c b/stdio-common/tst-freopen2.c
new file mode 100644
index 0000000000..4a5a3c016e
--- /dev/null
+++ b/stdio-common/tst-freopen2.c
@@ -0,0 +1,446 @@ 
+/* Test freopen.
+   Copyright (C) 2024 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 <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <support/check.h>
+#include <support/file_contents.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/test-driver.h>
+#include <support/xstdio.h>
+
+int
+do_test (void)
+{
+  char *temp_dir = support_create_temp_directory ("tst-freopen2");
+  char *file1 = xasprintf ("%s/file1", temp_dir);
+  support_write_file_string (file1, "file1");
+  add_temp_file (file1);
+  char *file2 = xasprintf ("%s/file2", temp_dir);
+  support_write_file_string (file2, "file2");
+  add_temp_file (file2);
+  char *file3 = xasprintf ("%s/file3", temp_dir);
+  char *file4 = xasprintf ("%s/file4", temp_dir);
+  char *file_nodir = xasprintf ("%s/nodir/file", temp_dir);
+  char *file1a = xasprintf ("%s/file1a", temp_dir);
+  FILE *fp;
+  int ret;
+  wint_t wc;
+  int fd;
+
+  /* Test each pair of old and new modes from r w a.  */
+
+  verbose_printf ("Testing r -> r\n");
+  fp = xfopen (file1, "r");
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "file2");
+  xfclose (fp);
+
+  verbose_printf ("Testing r -> w\n");
+  fp = xfopen (file1, "r");
+  fp = freopen (file2, "w", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("File2new", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "File2new");
+
+  verbose_printf ("Testing r -> a\n");
+  fp = xfopen (file1, "r");
+  fp = freopen (file2, "a", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("3", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "File2new3");
+
+  verbose_printf ("Testing w -> r\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "File2new3");
+  xfclose (fp);
+
+  verbose_printf ("Testing w -> w\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file2, "w", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("next", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "next");
+
+  verbose_printf ("Testing w -> a\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file2, "a", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("4", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "next4");
+
+  verbose_printf ("Testing a -> r\n");
+  fp = xfopen (file1, "a");
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "next4");
+  xfclose (fp);
+
+  verbose_printf ("Testing a -> w\n");
+  fp = xfopen (file1, "a");
+  fp = freopen (file2, "w", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("another", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "another");
+
+  verbose_printf ("Testing a -> a\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file2, "a", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("5", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "another5");
+
+  /* Test changing to/from b (binary, no-op).  */
+
+  verbose_printf ("Testing rb -> r\n");
+  fp = xfopen (file1, "rb");
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "another5");
+  xfclose (fp);
+
+  verbose_printf ("Testing r -> rb\n");
+  fp = xfopen (file1, "r");
+  fp = freopen (file2, "rb", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "another5");
+  xfclose (fp);
+
+  /* Test changing to/from + (read-and-write).  */
+
+  verbose_printf ("Testing r -> w+\n");
+  fp = xfopen (file1, "r");
+  fp = freopen (file2, "w+", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("latest", fp);
+  TEST_VERIFY (ret >= 0);
+  ret = fseek (fp, 0, SEEK_SET);
+  TEST_COMPARE (ret, 0);
+  TEST_COMPARE_FILE_STRING (fp, "latest");
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "latest");
+
+  verbose_printf ("Testing w -> a+\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file2, "a+", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("suffix", fp);
+  TEST_VERIFY (ret >= 0);
+  ret = fseek (fp, 0, SEEK_SET);
+  TEST_COMPARE (ret, 0);
+  TEST_COMPARE_FILE_STRING (fp, "latestsuffix");
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "latestsuffix");
+
+  verbose_printf ("Testing a -> r+\n");
+  fp = xfopen (file1, "a");
+  fp = freopen (file2, "r+", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "latestsuffix");
+  ret = fseek (fp, 0, SEEK_SET);
+  TEST_COMPARE (ret, 0);
+  ret = fputs ("new", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "newestsuffix");
+
+  verbose_printf ("Testing r+ -> w\n");
+  fp = xfopen (file1, "r+");
+  fp = freopen (file2, "w", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("plusto", fp);
+  TEST_VERIFY (ret >= 0);
+  ret = fseek (fp, 0, SEEK_SET);
+  TEST_COMPARE (ret, 0);
+  errno = 0;
+  TEST_COMPARE (fgetc (fp), EOF);
+  TEST_COMPARE (errno, EBADF);
+  clearerr (fp);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "plusto");
+
+  verbose_printf ("Testing w+ -> a\n");
+  fp = xfopen (file1, "w+");
+  fp = freopen (file2, "a", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("more", fp);
+  TEST_VERIFY (ret >= 0);
+  ret = fseek (fp, 0, SEEK_SET);
+  TEST_COMPARE (ret, 0);
+  errno = 0;
+  TEST_COMPARE (fgetc (fp), EOF);
+  TEST_COMPARE (errno, EBADF);
+  clearerr (fp);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "plustomore");
+
+  verbose_printf ("Testing a+ -> r\n");
+  fp = xfopen (file1, "a+");
+  fp = freopen (file2, "rr", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "plustomore");
+  ret = fputs ("2", fp);
+  TEST_COMPARE (ret, EOF);
+  clearerr (fp);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "plustomore");
+
+  /* Test changing to/from e (FD_CLOEXEC).  */
+
+  verbose_printf ("Testing re -> r\n");
+  fp = xfopen (file1, "re");
+  ret = fcntl (fileno (fp), F_GETFD);
+  TEST_VERIFY (ret != -1);
+  TEST_COMPARE (ret & FD_CLOEXEC, FD_CLOEXEC);
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fcntl (fileno (fp), F_GETFD);
+  TEST_VERIFY (ret != -1);
+#if 0 /* Fails to clear FD_CLOEXEC.  */
+  TEST_COMPARE (ret & FD_CLOEXEC, 0);
+#endif
+  TEST_COMPARE_FILE_STRING (fp, "plustomore");
+  xfclose (fp);
+
+  verbose_printf ("Testing r -> re\n");
+  fp = xfopen (file1, "r");
+  ret = fcntl (fileno (fp), F_GETFD);
+  TEST_VERIFY (ret != -1);
+  TEST_COMPARE (ret & FD_CLOEXEC, 0);
+  fp = freopen (file2, "re", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fcntl (fileno (fp), F_GETFD);
+  TEST_VERIFY (ret != -1);
+  TEST_COMPARE (ret & FD_CLOEXEC, FD_CLOEXEC);
+  TEST_COMPARE_FILE_STRING (fp, "plustomore");
+  xfclose (fp);
+
+  /* Test changing to/from m (mmap) (a no-op as far as testing
+     semantics is concerned).  */
+
+  verbose_printf ("Testing rm -> r\n");
+  fp = xfopen (file1, "rm");
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "plustomore");
+  xfclose (fp);
+
+  verbose_printf ("Testing r -> rm\n");
+  fp = xfopen (file1, "r");
+  fp = freopen (file2, "rm", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "plustomore");
+  xfclose (fp);
+
+  /* Test changing to/from x (O_EXCL).  */
+
+  verbose_printf ("Testing wx -> w\n");
+  fp = xfopen (file3, "wx");
+  add_temp_file (file3);
+  fp = freopen (file2, "w", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  ret = fputs ("wxtow", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file2, "wxtow");
+
+  verbose_printf ("Testing w -> wx (file does not exist)\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file4, "wx", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  add_temp_file (file4);
+  ret = fputs ("wtowx", fp);
+  TEST_VERIFY (ret >= 0);
+  xfclose (fp);
+  TEST_OPEN_AND_COMPARE_FILE_STRING (file4, "wtowx");
+
+  verbose_printf ("Testing w -> wx (file exists)\n");
+  fp = xfopen (file1, "w");
+  fp = freopen (file4, "wx", fp);
+  TEST_VERIFY (fp == NULL);
+
+  /* Test with ,ccs=CHARSET.  */
+
+  verbose_printf ("testing w,ccs=utf-8 -> r\n");
+  fp = xfopen (file1, "w,ccs=utf-8");
+  ret = fputws (L"\xc0\xc1", fp);
+  TEST_VERIFY (ret >= 0);
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "wxtow");
+  xfclose (fp);
+
+  verbose_printf ("testing w,ccs=iso-8859-1 -> r,ccs=utf-8\n");
+  fp = xfopen (file2, "w,ccs=iso-8859-1");
+  ret = fputws (L"\xc0\xc1", fp);
+  TEST_VERIFY (ret >= 0);
+#if 0 /* Doesn't work (bug 23675).  */
+  fp = freopen (file1, "r,ccs=utf-8", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+#else /* Works instead.  */
+  xfclose (fp);
+  fp = xfopen (file1, "r,ccs=utf-8");
+#endif
+  wc = fgetwc (fp);
+  TEST_COMPARE (wc, (wint_t) 0xc0);
+  wc = fgetwc (fp);
+  TEST_COMPARE (wc, (wint_t) 0xc1);
+  wc = fgetwc (fp);
+  TEST_COMPARE (wc, WEOF);
+  xfclose (fp);
+
+  verbose_printf ("testing r,ccs=utf-8 -> r\n");
+  fp = xfopen (file1, "r,ccs=utf-8");
+  fp = freopen (file1, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "\u00c0\u00c1");
+  xfclose (fp);
+
+  /* Test old file is closed even when opening the new file fails.  */
+
+  verbose_printf ("testing r -> r (opening new file fails)\n");
+  fp = xfopen (file1, "r");
+  fd = fileno (fp);
+  fp = freopen (file_nodir, "r", fp);
+  TEST_VERIFY (fp == NULL);
+  errno = 0;
+  ret = fcntl (fd, F_GETFL);
+  TEST_COMPARE (ret, -1);
+  TEST_COMPARE (errno, EBADF);
+
+  /* Test that errors closing the old file are ignored.  */
+
+  verbose_printf ("testing errors closing old file ignored\n");
+  fp = xfopen ("/dev/full", "w");
+  fputc ('x', fp);
+  fp = freopen (file1, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "\u00c0\u00c1");
+  xfclose (fp);
+
+  /* Test that error / EOF state from the old file are cleared.  */
+
+  verbose_printf ("testing error state from old file cleared\n");
+  fp = xfopen ("/dev/full", "w");
+  fputc ('x', fp);
+  fflush (fp);
+  TEST_VERIFY (ferror (fp));
+  TEST_VERIFY (!feof (fp));
+  fp = freopen (file2, "w", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_VERIFY (!ferror (fp));
+  TEST_VERIFY (!feof (fp));
+  xfclose (fp);
+
+  verbose_printf ("testing EOF state from old file cleared\n");
+  fp = xfopen ("/dev/null", "r");
+  fgetc (fp);
+  TEST_VERIFY (!ferror (fp));
+  TEST_VERIFY (feof (fp));
+  fp = freopen (file2, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_VERIFY (!ferror (fp));
+  TEST_VERIFY (!feof (fp));
+  xfclose (fp);
+
+  /* Test freopen with NULL, same mode (should flush content and reset
+     file offset).  */
+
+  verbose_printf ("testing freopen with NULL, same mode\n");
+  fp = xfopen (file1, "r+");
+  ret = fputs ("same mode", fp);
+  TEST_VERIFY (ret >= 0);
+  fp = freopen (NULL, "r+", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "same mode");
+  xfclose (fp);
+
+  /* Test freopen with NULL, different mode.  */
+
+  verbose_printf ("testing freopen with NULL, different mode\n");
+  fp = xfopen (file1, "w");
+  ret = fputs ("different mode", fp);
+  TEST_VERIFY (ret >= 0);
+  fp = freopen (NULL, "r", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "different mode");
+  xfclose (fp);
+
+  /* Test freopen with NULL, renamed file.  */
+
+  verbose_printf ("testing freopen with NULL, renamed file\n");
+  fp = xfopen (file1, "r+");
+  ret = fputs ("file has been renamed", fp);
+  TEST_VERIFY (ret >= 0);
+  ret = rename (file1, file1a);
+  TEST_COMPARE (ret, 0);
+  fp = freopen (NULL, "r+", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "file has been renamed");
+  xfclose (fp);
+  ret = rename (file1a, file1);
+  TEST_COMPARE (ret, 0);
+
+  /* Test freopen with NULL, deleted file.  */
+
+  verbose_printf ("testing freopen with NULL, deleted file\n");
+  fp = xfopen (file1, "r+");
+  ret = fputs ("file has now been deleted", fp);
+  TEST_VERIFY (ret >= 0);
+  ret = remove (file1);
+  TEST_COMPARE (ret, 0);
+  fp = freopen (NULL, "r+", fp);
+  TEST_VERIFY_EXIT (fp != NULL);
+  TEST_COMPARE_FILE_STRING (fp, "file has now been deleted");
+  xfclose (fp);
+  /* Recreate the file so it is present when expected for temporary
+     file deletion.  */
+  support_write_file_string (file1, "file1");
+
+  free (temp_dir);
+  free (file1);
+  free (file2);
+  free (file3);
+  free (file4);
+  free (file_nodir);
+  free (file1a);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/Makefile b/support/Makefile
index 6e3c55394f..26bd3d38e4 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -49,6 +49,8 @@  libsupport-routines = \
   support_check_stat_fd \
   support_check_stat_path \
   support_chroot \
+  support_compare_file_bytes \
+  support_compare_file_string \
   support_copy_file \
   support_copy_file_range \
   support_create_timer \
@@ -65,6 +67,8 @@  libsupport-routines = \
   support_isolate_in_subprocess \
   support_mutex_pi_monotonic \
   support_need_proc \
+  support_open_and_compare_file_bytes \
+  support_open_and_compare_file_string \
   support_openpty \
   support_path_support_time64 \
   support_paths \
diff --git a/support/file_contents.h b/support/file_contents.h
new file mode 100644
index 0000000000..eea05eebc0
--- /dev/null
+++ b/support/file_contents.h
@@ -0,0 +1,56 @@ 
+/* Functionality for checking file contents.
+   Copyright (C) 2024 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/>.  */
+
+#ifndef SUPPORT_FILE_CONTENTS_H
+#define SUPPORT_FILE_CONTENTS_H
+
+#include <support/check.h>
+#include <stdio.h>
+
+__BEGIN_DECLS
+
+/* Check that an already-open file has exactly the given bytes,
+   starting at the current offset.  */
+int support_compare_file_bytes (FILE *fp, const char *contents, size_t length);
+
+/* Check that an already-open file has exactly the given string as
+   contents, starting at the current offset.  */
+int support_compare_file_string (FILE *fp, const char *contents);
+
+/* Check that a not-currently-open file has exactly the given
+   bytes.  */
+int support_open_and_compare_file_bytes (const char *file,
+					 const char *contents,
+					 size_t length);
+
+/* Check that a not-currently-open file has exactly the given string
+   as contents, starting at the current offset.  */
+int support_open_and_compare_file_string (const char *file,
+					  const char *contents);
+
+/* Compare bytes read from an open file with the given string.  */
+#define TEST_COMPARE_FILE_STRING(FP, CONTENTS)			\
+  TEST_COMPARE (support_compare_file_string (FP, CONTENTS), 0)
+
+/* Open a file and compare bytes read from it with the given string.  */
+#define TEST_OPEN_AND_COMPARE_FILE_STRING(FILE, CONTENTS)		\
+  TEST_COMPARE (support_open_and_compare_file_string (FILE, CONTENTS), 0)
+
+__END_DECLS
+
+#endif /* SUPPORT_FILE_CONTENTS_H */
diff --git a/support/support_compare_file_bytes.c b/support/support_compare_file_bytes.c
new file mode 100644
index 0000000000..6e3b51b0a0
--- /dev/null
+++ b/support/support_compare_file_bytes.c
@@ -0,0 +1,42 @@ 
+/* Compare bytes from an open file.
+   Copyright (C) 2024 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/file_contents.h>
+
+/* Check that an already-open file has exactly the given bytes,
+   starting at the current offset.  */
+
+int
+support_compare_file_bytes (FILE *fp, const char *contents, size_t length)
+{
+  int c;
+  while (length > 0)
+    {
+      c = getc (fp);
+      if (c == EOF || (unsigned char) c != (unsigned char) contents[0])
+	return 1;
+      contents++;
+      length--;
+    }
+  c = getc (fp);
+  if (c != EOF)
+    return 1;
+  return 0;
+}
diff --git a/support/support_compare_file_string.c b/support/support_compare_file_string.c
new file mode 100644
index 0000000000..04513c3af1
--- /dev/null
+++ b/support/support_compare_file_string.c
@@ -0,0 +1,28 @@ 
+/* Compare string from an open file.
+   Copyright (C) 2024 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 <string.h>
+
+#include <support/file_contents.h>
+
+int
+support_compare_file_string (FILE *fp, const char *contents)
+{
+  return support_compare_file_bytes (fp, contents, strlen (contents));
+}
diff --git a/support/support_open_and_compare_file_bytes.c b/support/support_open_and_compare_file_bytes.c
new file mode 100644
index 0000000000..f804ed8e46
--- /dev/null
+++ b/support/support_open_and_compare_file_bytes.c
@@ -0,0 +1,33 @@ 
+/* Compare bytes from a file.
+   Copyright (C) 2024 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 <support/file_contents.h>
+#include <support/xstdio.h>
+
+/* Check that a not-currently-open file has exactly the given
+   bytes.  */
+
+int
+support_open_and_compare_file_bytes (const char *file, const char *contents,
+				     size_t length)
+{
+  FILE *fp = xfopen (file, "r");
+  int ret = support_compare_file_bytes (fp, contents, length);
+  xfclose (fp);
+  return ret;
+}
diff --git a/support/support_open_and_compare_file_string.c b/support/support_open_and_compare_file_string.c
new file mode 100644
index 0000000000..2b596d4c88
--- /dev/null
+++ b/support/support_open_and_compare_file_string.c
@@ -0,0 +1,32 @@ 
+/* Compare string from a file.
+   Copyright (C) 2024 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 <string.h>
+
+#include <support/file_contents.h>
+#include <support/xstdio.h>
+
+/* Check that a not-currently-open file has exactly the given string
+   as contents, starting at the current offset.  */
+
+int
+support_open_and_compare_file_string (const char *file, const char *contents)
+{
+  return support_open_and_compare_file_bytes (file, contents,
+					      strlen (contents));
+}