diff mbox

[v3,0/1,v3] stdio-common: Add more tests of the setvbuf() function.

Message ID 878qv3sx6c.fsf@redhat.com
State New
Headers show

Commit Message

Nick Clifton Oct. 4, 2024, 3:30 p.m. UTC
From c8907f4230c42a7453314dc4774483adc3975f58 Mon Sep 17 00:00:00 2001
From: Nick Clifton <nickc@redhat.com>
Date: Fri, 4 Oct 2024 16:22:36 +0100
Subject: [PATCH v3 1/1] [PATCH v3] stdio-common: Add more tests of the
 setvbuf() function.
To: libc-alpha@sourceware.org

This patch series extends the current test of the setvbuf() function in order to cover more use cases.
In particular it checks that:
  * stdout and stderr can be set into unbuffered mode.
  * stdin and stderr can be set into line buffered mode.
  * full buffering can be set on reads and writes to an ordinary file and that buffering does take place.
  * line buffering can be set on reads and writes to an ordinary file and that buffering on writes does happen.
  * buffering can be disabled for reads and writes to/from an ordinary file and that data written is immediately available for reading.
---
 stdio-common/Makefile       |  11 +++
 stdio-common/tst-setvbuf2.c |  70 +++++++++++++++
 stdio-common/tst-setvbuf3.c | 101 +++++++++++++++++++++
 stdio-common/tst-setvbuf4.c | 154 ++++++++++++++++++++++++++++++++
 stdio-common/tst-setvbuf5.c | 171 ++++++++++++++++++++++++++++++++++++
 stdio-common/tst-setvbuf6.c | 136 ++++++++++++++++++++++++++++
 6 files changed, 643 insertions(+)
 create mode 100644 stdio-common/tst-setvbuf2.c
 create mode 100644 stdio-common/tst-setvbuf3.c
 create mode 100644 stdio-common/tst-setvbuf4.c
 create mode 100644 stdio-common/tst-setvbuf5.c
 create mode 100644 stdio-common/tst-setvbuf6.c

Comments

Florian Weimer Oct. 29, 2024, 5:08 p.m. UTC | #1
* Nick Clifton:

> From c8907f4230c42a7453314dc4774483adc3975f58 Mon Sep 17 00:00:00 2001
> From: Nick Clifton <nickc@redhat.com>
> Date: Fri, 4 Oct 2024 16:22:36 +0100
> Subject: [PATCH v3 1/1] [PATCH v3] stdio-common: Add more tests of the
>  setvbuf() function.
> To: libc-alpha@sourceware.org
>
> This patch series extends the current test of the setvbuf() function in order to cover more use cases.
> In particular it checks that:
>   * stdout and stderr can be set into unbuffered mode.
>   * stdin and stderr can be set into line buffered mode.
>   * full buffering can be set on reads and writes to an ordinary file and that buffering does take place.
>   * line buffering can be set on reads and writes to an ordinary file and that buffering on writes does happen.
>   * buffering can be disabled for reads and writes to/from an ordinary file and that data written is immediately available for reading.

(lines are too long)

> diff --git a/stdio-common/tst-setvbuf4.c b/stdio-common/tst-setvbuf4.c
> new file mode 100644
> index 0000000000..f918432ab6
> --- /dev/null
> +++ b/stdio-common/tst-setvbuf4.c
> @@ -0,0 +1,154 @@
> +/* Test using setvbuf to set full buffered mode on a non-terminal 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 <stdlib.h>
> +#include <string.h>
> +#include <sys/mman.h>
> +#include <unistd.h>
> +
> +#include <support/check.h>
> +#include <support/temp_file.h>
> +static char file_buf [SMALL_BUF_SIZE];
> +static char small_buf [SMALL_BUF_SIZE];
> +static char big_buf [BIG_BUF_SIZE];

I added:

static void
dump (const char *ctx, FILE *fp)
{
  printf ("step: %s _flags=0x%x buf_base=%p buf_end=+%td\n",
          ctx, fp->_flags, fp->_IO_buf_base,
          fp->_IO_buf_end - fp->_IO_buf_base);
  printf ("  read_base=%p  read_ptr=+%td read_end=+%td\n",
          fp->_IO_read_base, fp->_IO_read_ptr - fp->_IO_read_base,
          fp->_IO_read_end - fp->_IO_read_base);
  printf ("  write_base=%p  write_ptr=+%td write_end=+%td\n",
          fp->_IO_write_base, fp->_IO_write_ptr - fp->_IO_write_base,
          fp->_IO_write_end - fp->_IO_write_base);
}

> +
> +static int
> +do_test (void)
> +{
> +  FILE * file;
> +  char * mem_page;
> +  size_t page_size;
> +  int    fd;
> +  int    val;
> +
> +  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
> +  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
> +
> +  /* Create a temporay file.  */
> +  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf4", NULL)) != -1);
> +
> +  /* Create a stream attached to the file.  */
> +  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);

  dump ("fdopen", file);

> +  /* Set full buffering on the file, using our (small) file buffer.
> +
> +     Note - this has to be done now, right after opening the file.  The POSIX
> +     standard states:
> +
> +        The setvbuf() function may be used after the stream
> +	pointed to by stream is associated with an open file
> +	but before any other operation (other than an unsuccessful
> +	call to setvbuf( )) is performed on the stream.  */
> +  TEST_VERIFY_EXIT (setvbuf (file, file_buf, _IOFBF, sizeof file_buf) == 0);

  dump ("setvbuf", file);

> +  /* Map the file into memory.
> +     FIXME: Using xmmap would simplify this statement, but it would be
> +     inconsistent with how we test all of the other library function calls.  */
> +  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
> +				      MAP_SHARED, fd, 0)) != MAP_FAILED);
> +  
> +  /* The page backing the file has not been initialised yet, so attempts
> +     to read from it will produce a SIGBUS error.  We fix that by setting
> +     the file to the size of the page.  */
> +  TEST_VERIFY (ftruncate (fd, page_size) == 0);
> +  
> +  /* Create our test data using a value that should not
> +     be the same as the contents of the memory page.  */
> +  val = mem_page[0] + 1;
> +  memset (small_buf, val, sizeof small_buf);
> +  
> +  /* Write one byte (which is less than our file buffer size) to the file.  */

  dump ("before fseek", file);

> +  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);

  dump("fseek", file);

> +  TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1);

  dump("fwrite", file);

> +  /* Check that the byte has not made it into the file.  */
> +  TEST_VERIFY (mem_page[0] != val);

And that's what fails.

The additional logging yields:

step: fdopen _flags=0xfbad2480 buf_base=(nil) buf_end=+0
  read_base=(nil)  read_ptr=+0 read_end=+0
  write_base=(nil)  write_ptr=+0 write_end=+0
step: setvbuf _flags=0xfbad2481 buf_base=0x408290 buf_end=+12
  read_base=0x408290  read_ptr=+0 read_end=+0
  write_base=0x408290  write_ptr=+0 write_end=+0
step: before fseek _flags=0xfbad2481 buf_base=0x408290 buf_end=+12
  read_base=0x408290  read_ptr=+0 read_end=+0
  write_base=0x408290  write_ptr=+0 write_end=+0
step: fseek _flags=0xfbad2481 buf_base=0x408290 buf_end=+12
  read_base=0x408290  read_ptr=+0 read_end=+0
  write_base=0x408290  write_ptr=+0 write_end=+0
step: fwrite _flags=0xfbad2c81 buf_base=0x408290 buf_end=+12
  read_base=0x408290  read_ptr=+0 read_end=+0
  write_base=0x408290  write_ptr=+0 write_end=+12
error: tst-setvbuf4.c:147: not true: mem_page[0] != val
error: 1 test failures

So the write window is not opened before the fwrite call

With the setvbuf call commented out, we get further:

step: fdopen _flags=0xfbad2480 buf_base=(nil) buf_end=+0
  read_base=(nil)  read_ptr=+0 read_end=+0
  write_base=(nil)  write_ptr=+0 write_end=+0
step: before fseek _flags=0xfbad2480 buf_base=(nil) buf_end=+0
  read_base=(nil)  read_ptr=+0 read_end=+0
  write_base=(nil)  write_ptr=+0 write_end=+0
step: fseek _flags=0xfbad2480 buf_base=0x2ac454e0 buf_end=+4096
  read_base=0x2ac454e0  read_ptr=+0 read_end=+0
  write_base=0x2ac454e0  write_ptr=+0 write_end=+0
step: fwrite _flags=0xfbad2c80 buf_base=0x2ac454e0 buf_end=+4096
  read_base=0x2ac454e0  read_ptr=+0 read_end=+0
  write_base=0x2ac454e0  write_ptr=+1 write_end=+4096
error: tst-setvbuf4.c:160: not true: memcmp (small_buf, mem_page, sizeof small_buf) == 0
error: 1 test failures

So even though the write window was not open, the (now internal) buffer
was used, and nothing was written via the kernel yet.

The answer to this puzzle is a bit silly.  The test passes with:

#define BIG_BUF_SIZE    256
#define SMALL_BUF_SIZE  128

The reason is that 128 is a magic number, and below that, the
implementation does not attempt to achieve buffer alignment.  From the
guts of _IO_new_file_xsputn in libio/fileops.c:

      /* Try to maintain alignment: write a whole number of blocks.  */
      block_size = f->_IO_buf_end - f->_IO_buf_base;
      do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

      if (do_write)
	{
	  count = new_do_write (f, s, do_write);
	  to_do -= count;
	  if (count < do_write)
	    return n - to_do;
	}

We take the do_write !=0 path, and that writes out the passed bytes even
if they could have fit into the buffer.  For buffer sizes larger than
128 and small write requests, do_write will be 0, and a different code
path is used that eventually ends up in _IO_new_file_xsputn, and that
opens up the write window as if by fputc in its loop.

So we'd need two different tests, one with a buffer size smaller than
128 bytes (where the first write goes through immediately), and one with
a buffer size larger than that (where it does not).

However, given this diverging code paths and the surprising complexity
of the interface, I'm not sure if we should be testing this further.

Thanks,
Florian
diff mbox

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 44165a9c59..a3f7fb0b3f 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -262,6 +262,11 @@  tests := \
   tst-scanf-round \
   tst-scanf-to_inpunct \
   tst-setvbuf1 \
+  tst-setvbuf2 \
+  tst-setvbuf3 \
+  tst-setvbuf4 \
+  tst-setvbuf5 \
+  tst-setvbuf6 \
   tst-sprintf \
   tst-sprintf-errno \
   tst-sprintf2 \
@@ -284,6 +289,12 @@  tests := \
   xbug \
   # tests
 
+# The tst-setvbuf4 test examines the use of full buffering on non-terminal
+# based files.  Currently it fails because somewhere inside glibc the buffering
+# is disabled and writes to the file appear there immediately.  This needs
+# investigating.
+test-xfail-tst-setvbuf4 = yes
+
 ifeq ($(run-built-tests),yes)
 ifeq (yes,$(build-shared))
 ifneq ($(PERL),no)
diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c
new file mode 100644
index 0000000000..cc0c086e7d
--- /dev/null
+++ b/stdio-common/tst-setvbuf2.c
@@ -0,0 +1,70 @@ 
+/* Test using setvbuf to set unbuffered mode on standard streams.
+   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 <unistd.h>
+
+/* Check that the standard streams (stdout and stderr) can be set to
+   unbuffered.  Note - the POSIX standard indicates that setting the
+   buffering on a stream might not work.  It states:
+     
+     The setvbuf( ) function may be used after the stream
+     pointed to by stream is associated with an open file
+     but before any other operation (other than an unsuccessful
+     call to setvbuf( )) is performed on the stream.
+
+  Hence if messages have already been written to stdout and/or stderr
+  before this code is executed then we may not be able to change
+  the buffering.  The standard does not provide a way to detect if
+  this has happened, so we have to hope for the best.  */
+
+/* By default the test harness code will call setvbuf on stdout.
+   Since we want to do this ourselves, we disable the harness'
+   behaviour here.  */
+#define TEST_NO_SETVBUF 1
+
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+  TEST_VERIFY_EXIT (setvbuf (stdin,  NULL, _IONBF, 0) == 0);
+  TEST_VERIFY_EXIT (setvbuf (stderr, NULL, _IONBF, 0) == 0);
+  TEST_VERIFY_EXIT (setvbuf (stdout, NULL, _IONBF, 0) == 0);
+
+  /* The theory was that this test would be run with stdout and stderr redirected
+     into a single file.  Then writes to stdout and stderr would be performed
+     with and without newlines and finally the contents of the file would be
+     examined to find out if any buffering has taken place.  Unfortunately whilt
+     this works when run by hand, or from a makefile running on an ordinary
+     terminal, it does not work when run by Linaro's CI system.
+
+     There is no way to distinguish Linaro's execution environment from a normal
+     execution environment.  (Testing that stdout/stderr are attached to
+     terminals does not work, since they are not - they are attached to an output
+     file: tst-setvbuf.out).  All of which means that in the end we cannot test
+     the behaviour of buffering when writing to standard files.  (See
+     tst-setvuf4.c and tst-setvbuf5.c for tests that do work when writing to disk
+     based files).
+
+     Hence if we get this far, we consider that the test has passed.  */
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf3.c b/stdio-common/tst-setvbuf3.c
new file mode 100644
index 0000000000..69004aa0f9
--- /dev/null
+++ b/stdio-common/tst-setvbuf3.c
@@ -0,0 +1,101 @@ 
+/* Test using setvbuf to set line buffered mode on standard streams.
+   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 <unistd.h>
+
+/* Check that the standard streams (stdout and stderr) can be set to
+   line buffered.  Note - the POSIX standard indicates that setting
+   the buffering on a file might not work.  It states:
+     
+     The setvbuf( ) function may be used after the stream
+     pointed to by stream is associated with an open file
+     but before any other operation (other than an unsuccessful
+     call to setvbuf( )) is performed on the stream.
+
+  Hence if messages have already been written to stdout and/or stderr,
+  eg by the test harness code, but before this code is executed then
+  the calls to setvbuf might not actually change anything.  */
+
+/* By default the test harness code will call setvbuf on stdout.
+   Since we do not want that to happen we use the define below.  */
+#define TEST_NO_SETVBUF 1
+
+#include <support/check.h>
+
+#define LOCAL_BUF_SIZE 128
+
+static int
+do_test (void)
+{
+  static char local_buf [LOCAL_BUF_SIZE];
+
+  /* Note - the POSIX standard indicates that setting the buffering on a
+     stream might not give the results expected.  It states:
+
+       If BUF is not a null pointer, the array it points to *may*
+       be used instead of a buffer allocated by setvbuf( ) and the
+       argument SIZE specifies the size of the array; otherwise,
+       SIZE *may* determine the size of a buffer allocated by the
+       setvbuf( ) function.
+
+    So whilst we can set the buffering mode, we cannot be certain of the size
+    of the buffer that will be used, or where that buffer will be held.  */
+
+  /* Use a library allocated line buffer for stdin.  */
+  TEST_VERIFY_EXIT (setvbuf (stdin, NULL, _IOLBF, LOCAL_BUF_SIZE) == 0);
+
+  /* Note - the POSIX standard also indicates that line buffering might only
+     work on input files:
+     
+        Applications should note that many implementations
+	only provide line buffering on input from terminal
+	devices.
+
+     So if the following two calls to setvbuf fail, it is not an error, just
+     an indication that the test cannot be run.  */
+
+  /* Use a library allocated line buffer for stderr.  */
+  if (setvbuf (stderr, NULL, _IOLBF, LOCAL_BUF_SIZE) != 0)
+    FAIL_UNSUPPORTED ("tst-setvbuf3.c: POSIX standard does not guarantee being able to set line buffering mode on stderr");
+
+  /* Use a program allocated line buffer for stdout.  */
+  if (setvbuf (stdout, local_buf, _IOLBF, sizeof local_buf) != 0)
+    FAIL_UNSUPPORTED ("tst-setvbuf3.c: POSIX standard does not guarantee being able to set line buffering mode on stdout");
+
+  /* The theory was that this test would be run with stdout and stderr redirected
+     into a single file.  Then writes to stdout and stderr would be performed
+     with and without newlines and finally the contents of the file would be
+     examined to find out if any buffering has taken place.  Unfortunately whilt
+     this works when run by hand, or from a makefile running on an ordinary
+     terminal, it does not work when run by Linaro's CI system.
+
+     There is no way to distinguish Linaro's execution environment from a normal
+     execution environment.  (Testing that stdout/stderr are attached to
+     terminals does not work, since they are not - they are attached to an output
+     file: tst-setvbuf.out).  All of which means that in the end we cannot test
+     the behaviour of buffering when writing to standard files.  (See
+     tst-setvuf4.c and tst-setvbuf5.c for tests that do work when writing to disk
+     based files).
+
+     Hence if we get this far, we consider that the test has passed.  */
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf4.c b/stdio-common/tst-setvbuf4.c
new file mode 100644
index 0000000000..f918432ab6
--- /dev/null
+++ b/stdio-common/tst-setvbuf4.c
@@ -0,0 +1,154 @@ 
+/* Test using setvbuf to set full buffered mode on a non-terminal 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 <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+/* Check the behaviour of enabling full buffering on a non-terminal stream.
+
+   Note - the POSIX standard indicates that setting the buffering on a file
+   might not produce the expected results.  It states:
+
+     If BUF is not a null pointer, the array it points to *may*
+     be used instead of a buffer allocated by setvbuf() and the
+     argument SIZE specifies the size of the array; otherwise,
+     SIZE *may* determine the size of a buffer allocated by the
+     setvbuf() function.
+
+  So whilst we can set the buffering mode, we cannot be certain of the size of
+  the buffer that will be used, or where that buffer will be held.  For now we
+  proceed with the assumption that if the calls to setvbuf succeed then the
+  buffers are the size we expect.  But we do not test to see if the buffer we
+  have supplied are the ones actually being used.
+
+  Note - because of the POSIX rules on the interactions of multiple handles
+  on the same stream (see section 2.5.1 "Interaction of File Descriptors
+  and Standard I/O Streams" in the POSIX specification) we cannot just open a
+  file twice, once for reading and once for writing and then check that writes
+  to the file do not happen until the buffer is full.  Nor can we open a
+  single stream for both reading and writing and test that way because any
+  time we reposition the file pointer (ie by calling fseek) the buffer is
+  flushed.
+
+  In theory we could use fmemopen() to create a memory backed stream and
+  then check the buffering behaviour that way.  But it turns out the glibc's
+  implementation does not support buffering, so that does not work.
+
+  Another alternative is open_memstream() - which does use glibc's default
+  I/O code.  But it turns out that the function is not suitable for this test
+  as it specifically does not support having the memory buffer examined after
+  a write has completed but before a flush has been performed.
+  
+  So we resort to opening an ordinary file and using mmap to provide us with
+  a memory page that we can examine.  */
+
+#define BIG_BUF_SIZE    128
+#define SMALL_BUF_SIZE  12
+
+_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE,
+		"test assumes that its small buffer is shorter than its large buffer");
+_Static_assert (SMALL_BUF_SIZE > 1,
+		"test assumes that its small buffer is more than one byte long");
+
+static char file_buf [SMALL_BUF_SIZE];
+static char small_buf [SMALL_BUF_SIZE];
+static char big_buf [BIG_BUF_SIZE];
+
+static int
+do_test (void)
+{
+  FILE * file;
+  char * mem_page;
+  size_t page_size;
+  int    fd;
+  int    val;
+
+  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
+  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
+
+  /* Create a temporay file.  */
+  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf4", NULL)) != -1);
+
+  /* Create a stream attached to the file.  */
+  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);
+  
+  /* Set full buffering on the file, using our (small) file buffer.
+
+     Note - this has to be done now, right after opening the file.  The POSIX
+     standard states:
+
+        The setvbuf() function may be used after the stream
+	pointed to by stream is associated with an open file
+	but before any other operation (other than an unsuccessful
+	call to setvbuf( )) is performed on the stream.  */
+  TEST_VERIFY_EXIT (setvbuf (file, file_buf, _IOFBF, sizeof file_buf) == 0);
+
+  /* Map the file into memory.
+     FIXME: Using xmmap would simplify this statement, but it would be
+     inconsistent with how we test all of the other library function calls.  */
+  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) != MAP_FAILED);
+  
+  /* The page backing the file has not been initialised yet, so attempts
+     to read from it will produce a SIGBUS error.  We fix that by setting
+     the file to the size of the page.  */
+  TEST_VERIFY (ftruncate (fd, page_size) == 0);
+  
+  /* Create our test data using a value that should not
+     be the same as the contents of the memory page.  */
+  val = mem_page[0] + 1;
+  memset (small_buf, val, sizeof small_buf);
+  
+  /* Write one byte (which is less than our file buffer size) to the file.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1);
+
+  /* Check that the byte has not made it into the file.  */
+  TEST_VERIFY (mem_page[0] != val);
+  
+  /* Try reading the byte.  This would fail, except for the
+     fact that we call fseek() first, which flushes the buffer.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, 1, file) == 1);
+  TEST_VERIFY (big_buf[0] == small_buf[0]);
+
+  /* Write a whole buffer full.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, sizeof small_buf, file) == sizeof small_buf);
+
+  /* Check that the in-memory buffer now contains these bytes.  */
+  TEST_VERIFY (memcmp (small_buf, mem_page, sizeof small_buf) == 0);
+
+  /* Try reading lots of data.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, sizeof big_buf, file) == sizeof big_buf);
+
+  /* Verify that what we have read the expected bytes.  */
+  TEST_VERIFY (memcmp (big_buf, small_buf, sizeof small_buf) == 0);
+  
+  fclose (file);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf5.c b/stdio-common/tst-setvbuf5.c
new file mode 100644
index 0000000000..4a32e19ebb
--- /dev/null
+++ b/stdio-common/tst-setvbuf5.c
@@ -0,0 +1,171 @@ 
+/* Test using setvbuf to set line buffered mode on a non-terminal 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 <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+/* Check the behaviour of enabling line buffering mode on a non-terminal stream.
+
+   Note - the POSIX standard indicates that setting the buffering on a file
+   might not produce the expected results.  It states:
+
+     If BUF is not a null pointer, the array it points to *may*
+     be used instead of a buffer allocated by setvbuf( ) and the
+     argument SIZE specifies the size of the array; otherwise,
+     SIZE *may* determine the size of a buffer allocated by the
+     setvbuf( ) function.
+
+  So whilst we can set the buffering mode, we cannot be certain of the size of
+  the buffer that will be used, or where that buffer will be held.  For now we
+  proceed with the assumption that if the calls to setvbuf succeed then the
+  buffers are the size we expect.  But we do not test to see if the buffer we
+  have supplied are the ones actually being used.
+
+  Note - the POSIX standard also indicates that line buffering mode might not
+  be supported:
+  
+    Applications should note that many implementations only
+    provide line buffering on input from terminal devices.
+
+  So the test first tries to write less than a line of characters.  If this
+  shows up in the memory buffer, then we know that line buffering is not
+  supported and the test exits.  Otherwise it continues and tests that
+  writing a full line does cause the buffer to be flushed to memory.
+  
+  Note - because of the POSIX rules on the interactions of multiple handles
+  on the same stream (see section 2.5.1 "Interaction of File Descriptors
+  and Standard I/O Streams" in the POSIX specification) we cannot just open a
+  file twice, once for reading and once for writing and then check that writes
+  to the file do not happen until the buffer is full.  Nor can we open a
+  single stream for both reading and writing and test that way because any
+  time we reposition the file pointer (ie by calling fseek) the buffer is
+  flushed.
+
+  In theory we could use fmemopen() to create a memory backed stream and
+  then check the buffering behaviour that way.  But it turns out the glibc's
+  implementation does not support buffering, so that does not work.
+
+  Another alternative is open_memstream() - which does use glibc's default
+  I/O code.  But it turns out that the function is not suitable for this test
+  as it specifically does not support having the memory buffer examined after
+  a write has completed but before a flush has been performed.
+  
+  So we resort to opening an ordinary file and using mmap to provide us with
+  a memory page that we can examine.  */
+
+#define BIG_BUF_SIZE    128
+#define SMALL_BUF_SIZE  12
+
+_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE,
+		"test assumes that its small buffer is shorter than its large buffer");
+_Static_assert (SMALL_BUF_SIZE > 1,
+		"test assumes that its small buffer is more than one byte long");
+
+static char line_buf [SMALL_BUF_SIZE];
+static char in_buf [BIG_BUF_SIZE];
+
+const char string_without_newline[] = "test";
+
+static int
+do_test (void)
+{
+  FILE * file;
+  char * mem_page;
+  size_t page_size;
+  int    fd;
+  int    len;
+
+  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
+  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
+
+  /* Create a temporay file.  */
+  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf5", NULL)) != -1);
+
+  /* Create a stream attached to the file.  */
+  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);
+  
+  /* Set line buffering on the file, using our (small) file buffer.
+
+     Note - this has to be done now, right after opening the file.  The POSIX
+     standard states:
+
+        The setvbuf() function may be used after the stream
+	pointed to by stream is associated with an open file
+	but before any other operation (other than an unsuccessful
+	call to setvbuf( )) is performed on the stream.  */
+  TEST_VERIFY_EXIT (setvbuf (file, line_buf, _IOLBF, sizeof line_buf) == 0);
+
+  /* Map the file into memory.
+     FIXME: Using xmmap would simplify this statement, but it would be
+     inconsistent with how we test all of the other library function calls.  */
+  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) != MAP_FAILED);
+  
+  /* The page backing the file has not been initialised yet, so attempts
+     to read from it will produce a SIGBUS error.  We fix that by setting
+     the file to the size of the page.  */
+  TEST_VERIFY (ftruncate (fd, page_size) == 0);
+
+  /* Write one byte to the file.  */
+  TEST_VERIFY (fwrite (string_without_newline, 1, 1, file) == 1);
+
+  /* Check that the byte has not made it into the file.  */
+  if (mem_page[0] == string_without_newline[0])
+    {
+      printf ("info: tst-setvbuf5.c: line buffering is not supported on non-terminal I/O streams\n");
+      printf ("info: tst-setvbuf5.c: this is allowed by the POSIX standard\n");
+      close (fd);
+      return 0;
+    }
+  
+  /* Try reading the byte.  This would fail, except for the
+     fact that we call fseek() first, which flushes the buffer.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (in_buf, 1, 1, file) == 1);
+  TEST_VERIFY (in_buf[0] == string_without_newline[0]);
+
+  /* Write one and half lines to the file.  */
+  len = strlen (string_without_newline);
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fprintf (file, "%s\n%s", string_without_newline,
+			string_without_newline) == len * 2 + 1);
+
+  /* Check that the in-memory buffer now contains the first line.  */	       
+  TEST_VERIFY (strncmp (mem_page, string_without_newline, len) == 0);
+
+  /* And that it does not contain the second line.  */
+  TEST_VERIFY (strncmp (mem_page + len + 1, string_without_newline, len) != 0);
+
+  /* Read back what we have written.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (in_buf, 1, len * 2 + 1, file) == len * 2 + 1);
+
+  /* Check that we read in the second, not-newline-terminated string.  */
+  TEST_VERIFY (strncmp (in_buf + len + 1, string_without_newline, len) == 0);
+
+  fclose (file);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf6.c b/stdio-common/tst-setvbuf6.c
new file mode 100644
index 0000000000..10be391e3b
--- /dev/null
+++ b/stdio-common/tst-setvbuf6.c
@@ -0,0 +1,136 @@ 
+/* Test using setvbuf to set unbuffered mode on a non-terminal 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 <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+/* Check the behaviour of setting unbuffered mode on a non-terminal stream.
+
+  Note - because of the POSIX rules on the interactions of multiple handles
+  on the same stream (see section 2.5.1 "Interaction of File Descriptors
+  and Standard I/O Streams" in the POSIX specification) we cannot just open a
+  file twice, once for reading and once for writing and then check that writes
+  to the file happen immediately.  Nor can we open a single stream for both
+  reading and writing and test that way because any time we reposition the
+  file pointer (ie by calling fseek) the buffer is flushed.
+
+  In theory we can use fmemopen() to create a memory backed stream and
+  then check the buffering behaviour that way.  But it turns out the glibc's
+  implementation does not support buffering, which would not matter for this
+  test except that we want to be sure that buffering is not enabled because
+  of our actions, rather than the library's internal code.
+
+  Another alternative is open_memstream() - which does use glibc's default
+  I/O code.  But it turns out that the function is not suitable for this test
+  as it specifically does not support having the memory buffer examined after
+  a write has completed but before a flush has been performed.
+  
+  So we resort to opening an ordinary file and using mmap to provide us with
+  a memory page that we can examine.  */
+  
+#define BIG_BUF_SIZE    128
+#define SMALL_BUF_SIZE  12
+
+_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE,
+		"test assumes that its small buffer is shorter than its large buffer");
+
+static char small_buf [SMALL_BUF_SIZE];
+static char big_buf [BIG_BUF_SIZE];
+
+static int
+do_test (void)
+{
+  FILE * file;
+  char * mem_page;
+  size_t page_size;
+  int    fd;
+  int    val;
+
+  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
+  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
+
+  /* Create a temporay file.  */
+  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf6", NULL)) != -1);
+
+  /* Create a stream attached to the file.  */
+  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);
+  
+  /* Disable buffering on the file.
+
+     Note - this has to be done now, right after opening the file.  The POSIX
+     standard states:
+
+        The setvbuf() function may be used after the stream
+	pointed to by stream is associated with an open file
+	but before any other operation (other than an unsuccessful
+	call to setvbuf( )) is performed on the stream.  */
+  TEST_VERIFY_EXIT (setvbuf (file, NULL, _IONBF, 0) == 0);
+
+  /* Map the file into memory.
+     FIXME: Using xmmap would simplify this statement, but it would be
+     inconsistent with how we test all of the other library function calls.  */
+  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) != MAP_FAILED);
+  
+  /* The page backing the file has not been initialised yet, so attempts
+     to read from it will produce a SIGBUS error.  We fix that by setting
+     the file to the size of the page.  */
+  TEST_VERIFY (ftruncate (fd, page_size) == 0);
+  
+  /* Create our test data using a value that should not
+     be the same as the contents of the memory page.  */
+  val = mem_page[0] + 1;
+  memset (small_buf, val, sizeof small_buf);
+  
+  /* Write one byte to the file.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1);
+
+  /* Check that the byte has made it into the file.  */
+  TEST_VERIFY (mem_page[0] == val);
+  
+  /* Try reading the byte.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, 1, file) == 1);
+  TEST_VERIFY (big_buf[0] == small_buf[0]);
+
+  /* Write a whole buffer full.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, sizeof small_buf, file) == sizeof small_buf);
+
+  /* Check that the in-memory buffer now contains these bytes.  */
+  TEST_VERIFY (memcmp (small_buf, mem_page, sizeof small_buf) == 0);
+
+  /* Try reading lots of data.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, sizeof big_buf, file) == sizeof big_buf);
+
+  /* Verify that what we have read the expected bytes.  */
+  TEST_VERIFY (memcmp (big_buf, small_buf, sizeof small_buf) == 0);
+  
+  fclose (file);
+  return 0;
+}
+
+#include <support/test-driver.c>