[PATCH v2] Add freopen special-case tests: chroot, EFBIG, stdin/stdout/stderr

Carlos O'Donell codonell@redhat.com
Fri Sep 20 21:45:36 GMT 2024


On 9/10/24 6:26 PM, Joseph Myers wrote:
> Add tests of special cases for freopen that were omitted from the more
> general tests of different modes and similar issues.  The special
> cases in the three tests here are logically unconnected, it was simply
> convenient to put these tests in one patch.
> 
> * Test freopen with a NULL path to the new file, in a chroot.  Rather
>   than asserting that this fails (logically, failure in this case is
>   an implementation detail; it's not required for freopen to rely on
>   /proc), verify that either it fails (without memory leaks) or that
>   it succeeds and behaves as expected on success.  There is no check
>   for file descriptor leaks because the machinery for that also
>   depends on /proc, so can't be used in a chroot.
> 
> * Test that freopen and freopen64 are genuinely different in
>   configurations with 32-bit off_t by checking for an EFBIG trying to
>   write past 2GB in a file opened with freopen in such a configuration
>   but no error with 64-bit off_t or when opening with freopen64.
> 
> * Test freopen of stdin, stdout and stderr.

LGTM. Adds Florian's suggestions from v1. Includes additional tests for stdin, stdout
and stderr.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> Tested for x86_64 and x86.
> 
> ---
> 
> Changed in v2: test of freopen of stdin / stdout / stderr added;
> unused OTHER_FREOPEN definitions removed in tst-freopen4 /
> tst-freopen64-4.
> 
> diff --git a/stdio-common/Makefile b/stdio-common/Makefile
> index ce7f7cdd3b..62f8b99b06 100644
> --- a/stdio-common/Makefile
> +++ b/stdio-common/Makefile
> @@ -219,8 +219,13 @@ tests := \
>    tst-fphex-wide \
>    tst-freopen2 \
>    tst-freopen3 \
> +  tst-freopen4 \
> +  tst-freopen5 \
> +  tst-freopen6 \

OK. 3 new tests.

>    tst-freopen64-2 \
>    tst-freopen64-3 \
> +  tst-freopen64-4 \
> +  tst-freopen64-6 \

OK. 2 new tests.

>    tst-fseek \
>    tst-fwrite \
>    tst-fwrite-memstrm \
> @@ -324,8 +329,13 @@ ifneq ($(PERL),no)
>  tests-special += \
>    $(objpfx)tst-freopen2-mem.out \
>    $(objpfx)tst-freopen3-mem.out \
> +  $(objpfx)tst-freopen4-mem.out \
> +  $(objpfx)tst-freopen5-mem.out \
> +  $(objpfx)tst-freopen6-mem.out \

OK. 3 new freopen tests have leak checking.

>    $(objpfx)tst-freopen64-2-mem.out \
>    $(objpfx)tst-freopen64-3-mem.out \
> +  $(objpfx)tst-freopen64-4-mem.out \
> +  $(objpfx)tst-freopen64-6-mem.out \

OK. 2 new freopen64 tests have leak checking.

>    $(objpfx)tst-getline-enomem-mem.out \
>    $(objpfx)tst-getline-mem.out \
>    $(objpfx)tst-printf-bz18872-mem.out \
> @@ -341,10 +351,20 @@ generated += \
>    tst-freopen2.mtrace \
>    tst-freopen3-mem.out \
>    tst-freopen3.mtrace \
> +  tst-freopen4-mem.out \
> +  tst-freopen4.mtrace \
> +  tst-freopen5-mem.out \
> +  tst-freopen5.mtrace \
> +  tst-freopen6-mem.out \
> +  tst-freopen6.mtrace \

OK. out and trace.

>    tst-freopen64-2-mem.out \
>    tst-freopen64-2.mtrace \
>    tst-freopen64-3-mem.out \
>    tst-freopen64-3.mtrace \
> +  tst-freopen64-4-mem.out \
> +  tst-freopen64-4.mtrace \
> +  tst-freopen64-6-mem.out \
> +  tst-freopen64-6.mtrace \


OK. out and trace.

>    tst-getline-enomem-mem.out \
>    tst-getline-enomem.mtrace \
>    tst-getline-mem.out \
> @@ -476,6 +496,21 @@ tst-freopen3-ENV = \
>  tst-freopen64-3-ENV = \
>    MALLOC_TRACE=$(objpfx)tst-freopen64-3.mtrace \
>    LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
> +tst-freopen4-ENV = \
> +  MALLOC_TRACE=$(objpfx)tst-freopen4.mtrace \
> +  LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
> +tst-freopen64-4-ENV = \
> +  MALLOC_TRACE=$(objpfx)tst-freopen64-4.mtrace \
> +  LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
> +tst-freopen5-ENV = \
> +  MALLOC_TRACE=$(objpfx)tst-freopen5.mtrace \
> +  LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
> +tst-freopen6-ENV = \
> +  MALLOC_TRACE=$(objpfx)tst-freopen6.mtrace \
> +  LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
> +tst-freopen64-6-ENV = \
> +  MALLOC_TRACE=$(objpfx)tst-freopen64-6.mtrace \
> +  LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so

OK. Sorted :-)

>  
>  $(objpfx)tst-unbputc.out: tst-unbputc.sh $(objpfx)tst-unbputc
>  	$(SHELL) $< $(common-objpfx) '$(test-program-prefix)'; \
> diff --git a/stdio-common/tst-freopen4-main.c b/stdio-common/tst-freopen4-main.c
> new file mode 100644
> index 0000000000..e169442cf4
> --- /dev/null
> +++ b/stdio-common/tst-freopen4-main.c
> @@ -0,0 +1,100 @@
> +/* Test freopen in chroot.
> +   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 <mcheck.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#include <support/check.h>
> +#include <support/file_contents.h>
> +#include <support/namespace.h>
> +#include <support/support.h>
> +#include <support/temp_file.h>
> +#include <support/test-driver.h>
> +#include <support/xstdio.h>
> +#include <support/xunistd.h>
> +
> +int
> +do_test (void)
> +{
> +  mtrace ();

OK. Start tracing early.

> +  char *temp_dir = support_create_temp_directory ("tst-freopen4");
> +  FILE *fp;
> +  int ret;
> +
> +  /* These chroot tests verify that either reopening a renamed or
> +     deleted file works even in the absence of /proc, or that it fails
> +     (without memory leaks); thus, for example, such reopening does
> +     not crash in the absence of /proc.  */
> +
> +  support_become_root ();
> +  if (!support_can_chroot ())
> +    return EXIT_UNSUPPORTED;
> +  xchroot (temp_dir);
> +
> +  /* Test freopen with NULL, renamed file.  This verifies that
> +     reopening succeeds (and resets the file position indicator to
> +     start of file) even when the original path could no longer be
> +     opened, or fails without a memory leak.  (It is not possible to
> +     use <support/descriptors.h> to test for file descriptor leaks
> +     here, because that also depends on /proc.)  */
> +
> +  verbose_printf ("testing freopen with NULL, renamed file\n");
> +  fp = xfopen ("/file1", "w+");
> +  ret = fputs ("file has been renamed", fp);
> +  TEST_VERIFY (ret >= 0);
> +  ret = rename ("/file1", "/file1a");
> +  TEST_COMPARE (ret, 0);
> +  fp = FREOPEN (NULL, "r+", fp);
> +  if (fp != NULL)
> +    {
> +      puts ("freopen of renamed file succeeded");
> +      TEST_COMPARE_FILE_STRING (fp, "file has been renamed");
> +      xfclose (fp);
> +    }
> +  else
> +    puts ("freopen of renamed file failed (OK)");
> +  ret = rename ("/file1a", "/file1");
> +  TEST_COMPARE (ret, 0);
> +
> +  /* Test freopen with NULL, deleted file.  This verifies that
> +     reopening succeeds (and resets the file position indicator to
> +     start of file) even when the original path could no longer be
> +     opened, or fails without a memory leak.  */
> +
> +  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);
> +  if (fp != NULL)
> +    {
> +      puts ("freopen of deleted file succeeded");
> +      TEST_COMPARE_FILE_STRING (fp, "file has now been deleted");
> +      xfclose (fp);
> +    }
> +  else
> +    puts ("freopen of deleted file failed (OK)");
> +
> +  free (temp_dir);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/stdio-common/tst-freopen4.c b/stdio-common/tst-freopen4.c
> new file mode 100644
> index 0000000000..f39ec0d217
> --- /dev/null
> +++ b/stdio-common/tst-freopen4.c
> @@ -0,0 +1,2 @@
> +#define FREOPEN freopen
> +#include <tst-freopen4-main.c>
> diff --git a/stdio-common/tst-freopen5.c b/stdio-common/tst-freopen5.c
> new file mode 100644
> index 0000000000..f32626bccf
> --- /dev/null
> +++ b/stdio-common/tst-freopen5.c
> @@ -0,0 +1,144 @@
> +/* Test freopen and freopen64 with large offsets.
> +   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 <mcheck.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#include <support/check.h>
> +#include <support/descriptors.h>
> +#include <support/support.h>
> +#include <support/temp_file.h>
> +#include <support/test-driver.h>
> +#include <support/xstdio.h>
> +
> +#define START_TEST(DESC)			\
> +  do						\
> +    {						\
> +      fds = support_descriptors_list ();	\
> +      verbose_printf (DESC);			\
> +    }						\
> +  while (0)
> +
> +#define END_TEST				\
> +  do						\
> +    {						\
> +      support_descriptors_check (fds);		\
> +      support_descriptors_free (fds);		\
> +    }						\
> +  while (0)
> +
> +int
> +do_test (void)
> +{
> +  mtrace ();
> +  struct support_descriptors *fds;
> +  FILE *fp;
> +  int ret;
> +
> +  char *temp_dir = support_create_temp_directory ("tst-freopen5");
> +  /* This file is removed at the end of each test rather than left
> +     around between tests to avoid problems with subsequent tests
> +     reopening it as a large (2GB + 1 byte) file.  */
> +  char *file1 = xasprintf ("%s/file1", temp_dir);
> +
> +  /* fopen with freopen64: large offsets OK.  */
> +  START_TEST ("testing fopen with freopen64\n");
> +  fp = fopen ("/dev/null", "r");
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  fp = freopen64 (file1, "w", fp);
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  setbuf (fp, NULL);
> +  ret = fseeko64 (fp, 1LL << 32, SEEK_SET);
> +  TEST_COMPARE (ret, 0);
> +  ret = fputc ('x', fp);
> +  TEST_COMPARE (ret, 'x');
> +  xfclose (fp);
> +  ret = remove (file1);
> +  TEST_COMPARE (ret, 0);
> +  END_TEST;
> +
> +  /* fopen64 with freopen64: large offsets OK.  */
> +  START_TEST ("testing fopen64 with freopen64\n");
> +  fp = fopen64 ("/dev/null", "r");
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  fp = freopen64 (file1, "w", fp);
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  setbuf (fp, NULL);
> +  ret = fseeko64 (fp, 1LL << 32, SEEK_SET);
> +  TEST_COMPARE (ret, 0);
> +  ret = fputc ('x', fp);
> +  TEST_COMPARE (ret, 'x');
> +  xfclose (fp);
> +  ret = remove (file1);
> +  TEST_COMPARE (ret, 0);
> +  END_TEST;
> +
> +  /* fopen with freopen: large offsets not OK on 32-bit systems.  */
> +  START_TEST ("testing fopen with freopen\n");
> +  fp = fopen ("/dev/null", "r");
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  fp = freopen (file1, "w", fp);
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  setbuf (fp, NULL);
> +  ret = fseeko64 (fp, 1LL << 32, SEEK_SET);
> +  TEST_COMPARE (ret, 0);
> +  errno = 0;
> +  ret = fputc ('x', fp);
> +  if (sizeof (off_t) == 4)
> +    {
> +      TEST_COMPARE (ret, EOF);
> +      TEST_COMPARE (errno, EFBIG);
> +    }
> +  else
> +    TEST_COMPARE (ret, 'x');
> +  fclose (fp);
> +  ret = remove (file1);
> +  TEST_COMPARE (ret, 0);
> +  END_TEST;
> +
> +  /* fopen64 with freopen: large offsets not OK on 32-bit systems.  */
> +  START_TEST ("testing fopen64 with freopen\n");
> +  fp = fopen64 ("/dev/null", "r");
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  fp = freopen (file1, "w", fp);
> +  TEST_VERIFY_EXIT (fp != NULL);
> +  setbuf (fp, NULL);
> +  ret = fseeko64 (fp, 1LL << 32, SEEK_SET);
> +  TEST_COMPARE (ret, 0);
> +  errno = 0;
> +  ret = fputc ('x', fp);
> +  if (sizeof (off_t) == 4)
> +    {
> +      TEST_COMPARE (ret, EOF);
> +      TEST_COMPARE (errno, EFBIG);
> +    }
> +  else
> +    TEST_COMPARE (ret, 'x');
> +  fclose (fp);
> +  ret = remove (file1);
> +  TEST_COMPARE (ret, 0);
> +  END_TEST;
> +
> +  free (temp_dir);
> +  free (file1);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/stdio-common/tst-freopen6-main.c b/stdio-common/tst-freopen6-main.c
> new file mode 100644
> index 0000000000..f493f42fd7
> --- /dev/null
> +++ b/stdio-common/tst-freopen6-main.c
> @@ -0,0 +1,98 @@
> +/* Test freopen of stdin / stdout / stderr.
> +   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 <mcheck.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.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)
> +{
> +  mtrace ();
> +  char *temp_dir = support_create_temp_directory ("tst-freopen6");
> +  char *file1 = xasprintf ("%s/file1", temp_dir);
> +  support_write_file_string (file1, "file1");
> +  add_temp_file (file1);
> +  FILE *fp;
> +  int ret;
> +
> +  verbose_printf ("Testing reopening stdin\n");
> +  fp = FREOPEN (file1, "r", stdin);
> +  TEST_VERIFY_EXIT (fp == stdin);
> +  ret = getchar ();
> +  TEST_COMPARE (ret, 'f');
> +  ret = getchar ();
> +  TEST_COMPARE (ret, 'i');
> +  ret = getchar ();
> +  TEST_COMPARE (ret, 'l');
> +  ret = getchar ();
> +  TEST_COMPARE (ret, 'e');
> +  ret = getchar ();
> +  TEST_COMPARE (ret, '1');
> +  ret = getchar ();
> +  TEST_COMPARE (ret, EOF);
> +  xfclose (fp);
> +
> +  verbose_printf ("Testing reopening stderr\n");
> +  fp = FREOPEN (file1, "w+", stderr);
> +  TEST_VERIFY_EXIT (fp == stderr);
> +  errno = EINVAL;
> +  perror ("test");
> +  ret = fseek (fp, 0, SEEK_SET);
> +  TEST_COMPARE (ret, 0);
> +  TEST_COMPARE_FILE_STRING (fp, "test: Invalid argument\n");
> +  xfclose (fp);
> +
> +  verbose_printf ("Testing reopening stdout\n");
> +  /* Defer checks until the old stdout has been restored to make it
> +     more likely any errors are written to the old stdout (rather than
> +     the temporary file used for the redirected stdout).  */
> +  int old_stdout = dup (STDOUT_FILENO);
> +  TEST_VERIFY_EXIT (old_stdout != -1);
> +  int ret_fseek = 0;
> +  int ret_compare = 0;
> +  fp = FREOPEN (file1, "w+", stdout);
> +  int fp_eq_stdout = fp == stdout;
> +  if (fp != NULL)
> +    {
> +      printf ("reopened\n");
> +      ret_fseek = fseek (fp, 0, SEEK_SET);
> +      ret_compare = support_compare_file_string (fp, "reopened\n");
> +    }
> +  xfclose (fp);
> +  stdout = fdopen (old_stdout, "w");
> +  TEST_VERIFY (fp_eq_stdout);
> +  TEST_COMPARE (ret_fseek, 0);
> +  TEST_COMPARE (ret_compare, 0);
> +  xfclose (stdout);
> +
> +  free (temp_dir);
> +  free (file1);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/stdio-common/tst-freopen6.c b/stdio-common/tst-freopen6.c
> new file mode 100644
> index 0000000000..8fd6957b54
> --- /dev/null
> +++ b/stdio-common/tst-freopen6.c
> @@ -0,0 +1,2 @@
> +#define FREOPEN freopen
> +#include <tst-freopen6-main.c>
> diff --git a/stdio-common/tst-freopen64-4.c b/stdio-common/tst-freopen64-4.c
> new file mode 100644
> index 0000000000..1411be2bfa
> --- /dev/null
> +++ b/stdio-common/tst-freopen64-4.c
> @@ -0,0 +1,2 @@
> +#define FREOPEN freopen64
> +#include <tst-freopen4-main.c>
> diff --git a/stdio-common/tst-freopen64-6.c b/stdio-common/tst-freopen64-6.c
> new file mode 100644
> index 0000000000..3ec509a36c
> --- /dev/null
> +++ b/stdio-common/tst-freopen64-6.c
> @@ -0,0 +1,2 @@
> +#define FREOPEN freopen64
> +#include <tst-freopen6-main.c>
> 



More information about the Libc-alpha mailing list