diff mbox series

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

Message ID 877c99lwuj.fsf@redhat.com
State New
Headers show
Series stdio-common: Add more tests of the setvbuf() function | expand

Commit Message

Nick Clifton Nov. 11, 2024, 1:32 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,
---
 stdio-common/Makefile       |   5 ++
 stdio-common/tst-setvbuf2.c |  70 +++++++++++++++
 stdio-common/tst-setvbuf3.c | 101 +++++++++++++++++++++
 stdio-common/tst-setvbuf4.c | 167 +++++++++++++++++++++++++++++++++++
 stdio-common/tst-setvbuf5.c | 171 ++++++++++++++++++++++++++++++++++++
 stdio-common/tst-setvbuf6.c | 136 ++++++++++++++++++++++++++++
 6 files changed, 650 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
diff mbox series

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index e76e40e587..dcfe8fd6f1 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -292,6 +292,11 @@  tests := \
   tst-scanf-nan \
   tst-scanf-round \
   tst-scanf-to_inpunct \
+  tst-setvbuf2 \
+  tst-setvbuf3 \
+  tst-setvbuf4 \
+  tst-setvbuf5 \
+  tst-setvbuf6 \
   tst-setvbuf1 \
   tst-sprintf \
   tst-sprintf-errno \
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..5d2611ed0d
--- /dev/null
+++ b/stdio-common/tst-setvbuf4.c
@@ -0,0 +1,167 @@ 
+/* 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..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>