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

Message ID 87wmgyc5t6.fsf@redhat.com
State Failed CI
Headers

Commit Message

Nick Clifton Nov. 20, 2024, 2:56 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

Carlos O'Donell Nov. 25, 2024, 2:48 p.m. UTC | #1
On 11/20/24 9:56 AM, Nick Clifton wrote:
> This patch series extends the current test of the setvbuf() function
> in order to cover more use cases. In particular it checks that:

Just a note here that your subject is incorrect in this message, it should be 1/1.

The use of git send-email to send numbered patches with cover letters should make
this easier and avoid mistakes.

I'm following up with DJ to determine why this message didn't trigger pre-commit,
but I expect it's because of the incorrect numbering and the CI system ignoring
this as a cover letter number.
  

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>