[v7] stdio-common: Add more tests of the setvbuf() function

Message ID 87zfjqx2fg.fsf@redhat.com (mailing list archive)
State Under Review
Delegated to: Florian Weimer
Headers
Series [v7] stdio-common: Add more tests of the setvbuf() function |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed

Commit Message

Nick Clifton Jan. 16, 2025, 2:23 p.m. UTC
  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 and that writes
    to either stream appear immediately.
  * stdin and stderr can be set into line buffered mode and that
    writes to either are buffered until a new-line character is
    encountered.
  * full buffering can be set on reads and writes to an in-memory
    file and that buffering does take place.
  * line buffering can be set on reads and writes to an in-memory
    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.

Change History:
  v1: Original version with just a test of unbuffered standard stream access.
  v2: Extended version with more tests.
  v3: File stream tests rewritten to use in-memory streams.
  v4: File stream tests rewritten to use mmap'ed files.
  v5: Full buffer file stream test switched to larger buffer sizes.
  v6: Fix snafu with the ordering of the tests in the Makefile. -
  v7: FIx whitespace and line length issues.
---
 stdio-common/Makefile       |   5 ++
 stdio-common/tst-setvbuf2.c |  71 +++++++++++++++
 stdio-common/tst-setvbuf3.c | 104 ++++++++++++++++++++++
 stdio-common/tst-setvbuf4.c | 168 ++++++++++++++++++++++++++++++++++
 stdio-common/tst-setvbuf5.c | 173 ++++++++++++++++++++++++++++++++++++
 stdio-common/tst-setvbuf6.c | 137 ++++++++++++++++++++++++++++
 6 files changed, 658 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 Jan. 17, 2025, 11:49 a.m. UTC | #1
* Nick Clifton:

> 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 and that writes
>     to either stream appear immediately.
>   * stdin and stderr can be set into line buffered mode and that
>     writes to either are buffered until a new-line character is
>     encountered.
>   * full buffering can be set on reads and writes to an in-memory
>     file and that buffering does take place.
>   * line buffering can be set on reads and writes to an in-memory
>     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.

The FIXMEs need to be resolved or removed.  Using xmmap as suggested is
fine, but as explained below, it does not allow us to detect incorrectly
written trailing null bytes, so maybe reading directly from the file is
better.

The copyright year should be 2025 (only) now.

As far as I can see, we do not test this code:

  /* FIXME This can/should be moved to genops ?? */
  if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
      /* We used to flush all line-buffered stream.  This really isn't
	 required by any standard.  My recollection is that
	 traditional Unix systems did this for stdout.  stderr better
	 not be line buffered.  So we do just that here
	 explicitly.  --drepper */
      _IO_acquire_lock (stdout);

      if ((stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
	  == (_IO_LINKED | _IO_LINE_BUF))
	_IO_OVERFLOW (stdout, EOF);

      _IO_release_lock (stdout);
    }

The inner comment does not match current POSIX.  POSIX requires that
input on an unbuffered stream triggers flush on any line-buffered
stream, not just standard output.  This is tricky to implement properly.
POSIX has some language around deadlock detection specifically for this
case.

I filed:

  No deadlock detection for flushing due to line buffering
  <https://sourceware.org/bugzilla/show_bug.cgi?id=32566>

  Line-buffered flushing on unrelated input streams is restricted to
  stdout
  <https://sourceware.org/bugzilla/show_bug.cgi?id=32567>

> diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c
> new file mode 100644
> index 0000000000..1e1c6adae7
> --- /dev/null
> +++ b/stdio-common/tst-setvbuf2.c

> +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 whilst 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.  */

We can use dup2 (or xdup2) to install any file descriptor we want before
calling setvbuf.  For example, we can use regular files and check using
FUSE if a single character is written immediately.

I think the previous approach using regular files and mmap would work,
too.  The only tricky part is that if we redirect standard output, the
regular test error reporting will not work (but the exit status will
still reflect the test outcome).

This applies to to the other tests as well.

> diff --git a/stdio-common/tst-setvbuf3.c b/stdio-common/tst-setvbuf3.c
> new file mode 100644
> index 0000000000..8a7e55f85a
> --- /dev/null
> +++ b/stdio-common/tst-setvbuf3.c
> +  /* 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");

We should test our implementation and what we document (or otherwise
expect to work), even if POSIX does not require a specific behavior.  So
if we implement this, it should be FAIL_EXIT1, not FAIL_UNSUPPORTED.
And you can trim the comments as well.

> +   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.  */

The file mapping approach is undefined according to POSIX.  It's
possible that you can make it defined by using msync with MS_INVALIDATE.
Typical Linux file systems do not need it because Linux has a shared
page cache, and any non-direct write is also reflected there.

It may be easier to test using file descriptors directly because with
mmap alone, it is not possible to detect incorrectly written trailing
null bytes.

Thanks,
Florian
  

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index e76e40e587..44e065f0f4 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -293,6 +293,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 \
diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c
new file mode 100644
index 0000000000..1e1c6adae7
--- /dev/null
+++ b/stdio-common/tst-setvbuf2.c
@@ -0,0 +1,71 @@ 
+/* 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 whilst 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..8a7e55f85a
--- /dev/null
+++ b/stdio-common/tst-setvbuf3.c
@@ -0,0 +1,104 @@ 
+/* 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 whilst 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..038aab6379
--- /dev/null
+++ b/stdio-common/tst-setvbuf4.c
@@ -0,0 +1,168 @@ 
+/* 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.  */
+
+/* Note - using small buffers here does trigger a bug in glibc's
+   implementation of setvbuf, eg:
+   
+     #define BIG_BUF_SIZE    128
+     #define SMALL_BUF_SIZE  12
+
+   The reason for this is explained in a post from Florian:
+
+     https://inbox.sourceware.org/libc-alpha/87jzdqj0qu.fsf@oldenburg.str.redhat.com/
+
+   For now we use larger buffers so that we can test the function with a more
+   reasonable buffer size.  */
+
+#define BIG_BUF_SIZE    256
+#define SMALL_BUF_SIZE  128
+
+_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..8f96ad5ab8
--- /dev/null
+++ b/stdio-common/tst-setvbuf5.c
@@ -0,0 +1,173 @@ 
+/* 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..2a0c47ce2c
--- /dev/null
+++ b/stdio-common/tst-setvbuf6.c
@@ -0,0 +1,137 @@ 
+/* 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>