diff mbox series

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

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

Commit Message

Nick Clifton Sept. 27, 2024, 4:13 p.m. UTC

diff mbox series

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index 89f263894c..ec6b1a078e 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -261,6 +261,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 \
@@ -283,6 +288,12 @@  tests := \
   xbug \
   # tests
 
+# The tst-setvbuf4 test examines the use of full buffering on non-terminal
+# based files.  Currently it fails because somewhere inside glibc the buffering
+# is disabled and writes to the file appear there immediately.  This needs
+# investigating.
+test-xfail-tst-setvbuf4 = yes
+
 ifeq ($(run-built-tests),yes)
 ifeq (yes,$(build-shared))
 ifneq ($(PERL),no)
@@ -323,6 +334,8 @@  tests-special += \
   $(objpfx)tst-printf.out \
   $(objpfx)tst-printfsz-islongdouble.out \
   $(objpfx)tst-setvbuf1-cmp.out \
+  $(objpfx)tst-setvbuf2-diff.out \
+  $(objpfx)tst-setvbuf3-diff.out \
   $(objpfx)tst-unbputc.out \
   # tests-special
 
@@ -620,5 +633,21 @@  $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out
 	cmp $^ > $@; \
 	$(evaluate-test)
 
+$(objpfx)tst-setvbuf2.out: /dev/null $(objpfx)tst-setvbuf2
+	$(test-program-cmd) > $@ 2>&1; \
+	$(evaluate-test)
+
+$(objpfx)tst-setvbuf2-diff.out: tst-setvbuf2.expect $(objpfx)tst-setvbuf2.out
+	diff $^ > $@; \
+	$(evaluate-test)
+
+$(objpfx)tst-setvbuf3.out: /dev/null $(objpfx)tst-setvbuf3
+	$(test-program-cmd) > $@ 2>&1; \
+	$(evaluate-test)
+
+$(objpfx)tst-setvbuf3-diff.out: tst-setvbuf3.expect $(objpfx)tst-setvbuf3.out
+	diff $^ > $@; \
+	$(evaluate-test)
+
 $(objpfx)tst-printf-round: $(libm)
 $(objpfx)tst-scanf-round: $(libm)
diff --git a/stdio-common/tst-setvbuf2.c b/stdio-common/tst-setvbuf2.c
new file mode 100644
index 0000000000..0d14d21388
--- /dev/null
+++ b/stdio-common/tst-setvbuf2.c
@@ -0,0 +1,98 @@ 
+/* 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 (stderr, NULL, _IONBF, 0) == 0);
+  TEST_VERIFY_EXIT (setvbuf (stdout, NULL, _IONBF, 0) == 0);
+
+  /* This test relies upon stdout and stderr being attached to a terminal.
+     FIXME: Actually we need to be sure that they are attached to the *same*
+     terminal, but there is no easy way to detect this.
+     Tested here, rather than before calling setvbuf, since it is still valid
+     to call setvbuf even if the streams are not attached to a terminal.  It
+     just means that the buffering might not work as expected.  */
+  if (! isatty (1) || isatty (2))
+    {
+      printf ("info: tst-setvbuf2.c: this test only works when attached to a terminal\n");
+      return 0;
+    }
+
+  /* Emit some text to both streams with newlines in the middle.  */
+  fprintf (stderr, "setvbuf2: Output #1 ");
+  printf ("setvbuf2: Output #2 ");
+  fprintf (stderr, "<stderr>.\nAny day now ");
+  fprintf (stdout, "<stdout>.\nWe should discover ");
+
+  /* Flush the output buffers.  */
+  fflush (stderr);
+  fflush (stdout);
+  
+  /* Assuming that test is attached to a terminal and the shell mixes stdout
+     and stderr output, then...
+
+     If the streams are unbuffered then the output should look like this:
+
+       setvbuf2: Output #1 setvbuf2: Output #2 <stderr>.
+       Any day now <stdout>.
+       We should discover 
+
+     However if the streams are line buffered then it should look like this:
+
+       setvbuf2: Output #1 <stderr>.
+       setvbuf2: Output #2 <stdout>.
+       Any day now We should discover
+
+    And if they are fully buffered then it should look like this:
+
+       setvbuf2: Output #1 <stderr>.
+       Any day now 
+       setvbuf2: Output #2 <stdout>.
+       We should discover 
+    
+    This is checked by comparing the output to the tst-setvbuf2.expect file.  */
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf2.expect b/stdio-common/tst-setvbuf2.expect
new file mode 100644
index 0000000000..2edc14a158
--- /dev/null
+++ b/stdio-common/tst-setvbuf2.expect
@@ -0,0 +1,3 @@ 
+setvbuf2: Output #1 setvbuf2: Output #2 <stderr>.
+Any day now <stdout>.
+We should discover
diff --git a/stdio-common/tst-setvbuf3.c b/stdio-common/tst-setvbuf3.c
new file mode 100644
index 0000000000..8e4317f954
--- /dev/null
+++ b/stdio-common/tst-setvbuf3.c
@@ -0,0 +1,124 @@ 
+/* 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)
+{
+  /* 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.  */
+
+  /* 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.
+
+    For the purposes of this test we assume that this restriction is not
+    enforced.  */
+
+  static char local_buf [LOCAL_BUF_SIZE];
+
+  /* Use a library allocated line buffer for stderr.  */
+  TEST_VERIFY_EXIT (setvbuf (stderr, NULL, _IOLBF, LOCAL_BUF_SIZE) == 0);
+
+  /* Use a program allocated line buffer for stdout.  */
+  TEST_VERIFY_EXIT (setvbuf (stdout, local_buf, _IOLBF, sizeof local_buf) == 0);
+
+  /* This test relies upon stdout and stderr being attached to a terminal.
+     FIXME: Actually we need to be sure that they are attached to the *same*
+     terminal, but there is no easy way to detect this.
+     Tested here, rather than before calling setvbuf, since it is still valid
+     to call setvbuf even if the streams are not attached to a terminal.  It
+     just means that the buffering might not work as expected.  */
+  if (! isatty (1) || isatty (2))
+    {
+      printf ("info: tst-setvbuf3.c: this test only works when attached to a terminal\n");
+      return 0;
+    }
+  
+  /* Emit some text to both streams with newlines in the middle.  */
+  fprintf (stderr, "setvbuf3: Output #1 ");
+  printf ("setvbuf3: Output #2 ");
+  fprintf (stderr, "<stderr>.\nAny day now ");
+  printf ("<stdout>.\nWe should discover ");
+
+  /* Flush the output buffers.  */
+  fflush (stderr);
+  fflush (stdout);
+  
+  /* Assuming that test is attached to a terminal and the shell mixes stdout
+     and stderr output, then...
+
+    If the streams are unbuffered then the output should look like this:
+
+       setvbuf3: Output #1 setvbuf3: Output #2 <stderr>.
+       Any day now <stdout>.
+       We should discover
+
+     However if the streams a line buffered then it should look like this:
+
+       setvbuf3: Output #1 <stderr>.
+       setvbuf3: Output #2 <stdout>.
+       Any day now We should discover
+
+    And if they are fully buffered then it should look like this:
+
+       setvbuf3: Output #1 <stderr>.
+       Any day now setvbuf3: Output #2 <stdout>.
+       We should discover
+    
+    This is checked by comparing the output to the tst-setvbuf3.expect file.  */
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf3.expect b/stdio-common/tst-setvbuf3.expect
new file mode 100644
index 0000000000..80241ab0c9
--- /dev/null
+++ b/stdio-common/tst-setvbuf3.expect
@@ -0,0 +1,3 @@ 
+setvbuf3: Output #1 <stderr>.
+setvbuf3: Output #2 <stdout>.
+Any day now We should discover
diff --git a/stdio-common/tst-setvbuf4.c b/stdio-common/tst-setvbuf4.c
new file mode 100644
index 0000000000..f918432ab6
--- /dev/null
+++ b/stdio-common/tst-setvbuf4.c
@@ -0,0 +1,154 @@ 
+/* Test using setvbuf to set full buffered mode on a non-terminal file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+/* Check the behaviour of enabling full buffering on a non-terminal stream.
+
+   Note - the POSIX standard indicates that setting the buffering on a file
+   might not produce the expected results.  It states:
+
+     If BUF is not a null pointer, the array it points to *may*
+     be used instead of a buffer allocated by setvbuf() and the
+     argument SIZE specifies the size of the array; otherwise,
+     SIZE *may* determine the size of a buffer allocated by the
+     setvbuf() function.
+
+  So whilst we can set the buffering mode, we cannot be certain of the size of
+  the buffer that will be used, or where that buffer will be held.  For now we
+  proceed with the assumption that if the calls to setvbuf succeed then the
+  buffers are the size we expect.  But we do not test to see if the buffer we
+  have supplied are the ones actually being used.
+
+  Note - because of the POSIX rules on the interactions of multiple handles
+  on the same stream (see section 2.5.1 "Interaction of File Descriptors
+  and Standard I/O Streams" in the POSIX specification) we cannot just open a
+  file twice, once for reading and once for writing and then check that writes
+  to the file do not happen until the buffer is full.  Nor can we open a
+  single stream for both reading and writing and test that way because any
+  time we reposition the file pointer (ie by calling fseek) the buffer is
+  flushed.
+
+  In theory we could use fmemopen() to create a memory backed stream and
+  then check the buffering behaviour that way.  But it turns out the glibc's
+  implementation does not support buffering, so that does not work.
+
+  Another alternative is open_memstream() - which does use glibc's default
+  I/O code.  But it turns out that the function is not suitable for this test
+  as it specifically does not support having the memory buffer examined after
+  a write has completed but before a flush has been performed.
+  
+  So we resort to opening an ordinary file and using mmap to provide us with
+  a memory page that we can examine.  */
+
+#define BIG_BUF_SIZE    128
+#define SMALL_BUF_SIZE  12
+
+_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE,
+		"test assumes that its small buffer is shorter than its large buffer");
+_Static_assert (SMALL_BUF_SIZE > 1,
+		"test assumes that its small buffer is more than one byte long");
+
+static char file_buf [SMALL_BUF_SIZE];
+static char small_buf [SMALL_BUF_SIZE];
+static char big_buf [BIG_BUF_SIZE];
+
+static int
+do_test (void)
+{
+  FILE * file;
+  char * mem_page;
+  size_t page_size;
+  int    fd;
+  int    val;
+
+  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
+  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
+
+  /* Create a temporay file.  */
+  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf4", NULL)) != -1);
+
+  /* Create a stream attached to the file.  */
+  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);
+  
+  /* Set full buffering on the file, using our (small) file buffer.
+
+     Note - this has to be done now, right after opening the file.  The POSIX
+     standard states:
+
+        The setvbuf() function may be used after the stream
+	pointed to by stream is associated with an open file
+	but before any other operation (other than an unsuccessful
+	call to setvbuf( )) is performed on the stream.  */
+  TEST_VERIFY_EXIT (setvbuf (file, file_buf, _IOFBF, sizeof file_buf) == 0);
+
+  /* Map the file into memory.
+     FIXME: Using xmmap would simplify this statement, but it would be
+     inconsistent with how we test all of the other library function calls.  */
+  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) != MAP_FAILED);
+  
+  /* The page backing the file has not been initialised yet, so attempts
+     to read from it will produce a SIGBUS error.  We fix that by setting
+     the file to the size of the page.  */
+  TEST_VERIFY (ftruncate (fd, page_size) == 0);
+  
+  /* Create our test data using a value that should not
+     be the same as the contents of the memory page.  */
+  val = mem_page[0] + 1;
+  memset (small_buf, val, sizeof small_buf);
+  
+  /* Write one byte (which is less than our file buffer size) to the file.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1);
+
+  /* Check that the byte has not made it into the file.  */
+  TEST_VERIFY (mem_page[0] != val);
+  
+  /* Try reading the byte.  This would fail, except for the
+     fact that we call fseek() first, which flushes the buffer.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, 1, file) == 1);
+  TEST_VERIFY (big_buf[0] == small_buf[0]);
+
+  /* Write a whole buffer full.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, sizeof small_buf, file) == sizeof small_buf);
+
+  /* Check that the in-memory buffer now contains these bytes.  */
+  TEST_VERIFY (memcmp (small_buf, mem_page, sizeof small_buf) == 0);
+
+  /* Try reading lots of data.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, sizeof big_buf, file) == sizeof big_buf);
+
+  /* Verify that what we have read the expected bytes.  */
+  TEST_VERIFY (memcmp (big_buf, small_buf, sizeof small_buf) == 0);
+  
+  fclose (file);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf5.c b/stdio-common/tst-setvbuf5.c
new file mode 100644
index 0000000000..4a32e19ebb
--- /dev/null
+++ b/stdio-common/tst-setvbuf5.c
@@ -0,0 +1,171 @@ 
+/* Test using setvbuf to set line buffered mode on a non-terminal file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+/* Check the behaviour of enabling line buffering mode on a non-terminal stream.
+
+   Note - the POSIX standard indicates that setting the buffering on a file
+   might not produce the expected results.  It states:
+
+     If BUF is not a null pointer, the array it points to *may*
+     be used instead of a buffer allocated by setvbuf( ) and the
+     argument SIZE specifies the size of the array; otherwise,
+     SIZE *may* determine the size of a buffer allocated by the
+     setvbuf( ) function.
+
+  So whilst we can set the buffering mode, we cannot be certain of the size of
+  the buffer that will be used, or where that buffer will be held.  For now we
+  proceed with the assumption that if the calls to setvbuf succeed then the
+  buffers are the size we expect.  But we do not test to see if the buffer we
+  have supplied are the ones actually being used.
+
+  Note - the POSIX standard also indicates that line buffering mode might not
+  be supported:
+  
+    Applications should note that many implementations only
+    provide line buffering on input from terminal devices.
+
+  So the test first tries to write less than a line of characters.  If this
+  shows up in the memory buffer, then we know that line buffering is not
+  supported and the test exits.  Otherwise it continues and tests that
+  writing a full line does cause the buffer to be flushed to memory.
+  
+  Note - because of the POSIX rules on the interactions of multiple handles
+  on the same stream (see section 2.5.1 "Interaction of File Descriptors
+  and Standard I/O Streams" in the POSIX specification) we cannot just open a
+  file twice, once for reading and once for writing and then check that writes
+  to the file do not happen until the buffer is full.  Nor can we open a
+  single stream for both reading and writing and test that way because any
+  time we reposition the file pointer (ie by calling fseek) the buffer is
+  flushed.
+
+  In theory we could use fmemopen() to create a memory backed stream and
+  then check the buffering behaviour that way.  But it turns out the glibc's
+  implementation does not support buffering, so that does not work.
+
+  Another alternative is open_memstream() - which does use glibc's default
+  I/O code.  But it turns out that the function is not suitable for this test
+  as it specifically does not support having the memory buffer examined after
+  a write has completed but before a flush has been performed.
+  
+  So we resort to opening an ordinary file and using mmap to provide us with
+  a memory page that we can examine.  */
+
+#define BIG_BUF_SIZE    128
+#define SMALL_BUF_SIZE  12
+
+_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE,
+		"test assumes that its small buffer is shorter than its large buffer");
+_Static_assert (SMALL_BUF_SIZE > 1,
+		"test assumes that its small buffer is more than one byte long");
+
+static char line_buf [SMALL_BUF_SIZE];
+static char in_buf [BIG_BUF_SIZE];
+
+const char string_without_newline[] = "test";
+
+static int
+do_test (void)
+{
+  FILE * file;
+  char * mem_page;
+  size_t page_size;
+  int    fd;
+  int    len;
+
+  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
+  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
+
+  /* Create a temporay file.  */
+  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf5", NULL)) != -1);
+
+  /* Create a stream attached to the file.  */
+  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);
+  
+  /* Set line buffering on the file, using our (small) file buffer.
+
+     Note - this has to be done now, right after opening the file.  The POSIX
+     standard states:
+
+        The setvbuf() function may be used after the stream
+	pointed to by stream is associated with an open file
+	but before any other operation (other than an unsuccessful
+	call to setvbuf( )) is performed on the stream.  */
+  TEST_VERIFY_EXIT (setvbuf (file, line_buf, _IOLBF, sizeof line_buf) == 0);
+
+  /* Map the file into memory.
+     FIXME: Using xmmap would simplify this statement, but it would be
+     inconsistent with how we test all of the other library function calls.  */
+  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) != MAP_FAILED);
+  
+  /* The page backing the file has not been initialised yet, so attempts
+     to read from it will produce a SIGBUS error.  We fix that by setting
+     the file to the size of the page.  */
+  TEST_VERIFY (ftruncate (fd, page_size) == 0);
+
+  /* Write one byte to the file.  */
+  TEST_VERIFY (fwrite (string_without_newline, 1, 1, file) == 1);
+
+  /* Check that the byte has not made it into the file.  */
+  if (mem_page[0] == string_without_newline[0])
+    {
+      printf ("info: tst-setvbuf5.c: line buffering is not supported on non-terminal I/O streams\n");
+      printf ("info: tst-setvbuf5.c: this is allowed by the POSIX standard\n");
+      close (fd);
+      return 0;
+    }
+  
+  /* Try reading the byte.  This would fail, except for the
+     fact that we call fseek() first, which flushes the buffer.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (in_buf, 1, 1, file) == 1);
+  TEST_VERIFY (in_buf[0] == string_without_newline[0]);
+
+  /* Write one and half lines to the file.  */
+  len = strlen (string_without_newline);
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fprintf (file, "%s\n%s", string_without_newline,
+			string_without_newline) == len * 2 + 1);
+
+  /* Check that the in-memory buffer now contains the first line.  */	       
+  TEST_VERIFY (strncmp (mem_page, string_without_newline, len) == 0);
+
+  /* And that it does not contain the second line.  */
+  TEST_VERIFY (strncmp (mem_page + len + 1, string_without_newline, len) != 0);
+
+  /* Read back what we have written.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (in_buf, 1, len * 2 + 1, file) == len * 2 + 1);
+
+  /* Check that we read in the second, not-newline-terminated string.  */
+  TEST_VERIFY (strncmp (in_buf + len + 1, string_without_newline, len) == 0);
+
+  fclose (file);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdio-common/tst-setvbuf6.c b/stdio-common/tst-setvbuf6.c
new file mode 100644
index 0000000000..10be391e3b
--- /dev/null
+++ b/stdio-common/tst-setvbuf6.c
@@ -0,0 +1,136 @@ 
+/* Test using setvbuf to set unbuffered mode on a non-terminal file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+
+/* Check the behaviour of setting unbuffered mode on a non-terminal stream.
+
+  Note - because of the POSIX rules on the interactions of multiple handles
+  on the same stream (see section 2.5.1 "Interaction of File Descriptors
+  and Standard I/O Streams" in the POSIX specification) we cannot just open a
+  file twice, once for reading and once for writing and then check that writes
+  to the file happen immediately.  Nor can we open a single stream for both
+  reading and writing and test that way because any time we reposition the
+  file pointer (ie by calling fseek) the buffer is flushed.
+
+  In theory we can use fmemopen() to create a memory backed stream and
+  then check the buffering behaviour that way.  But it turns out the glibc's
+  implementation does not support buffering, which would not matter for this
+  test except that we want to be sure that buffering is not enabled because
+  of our actions, rather than the library's internal code.
+
+  Another alternative is open_memstream() - which does use glibc's default
+  I/O code.  But it turns out that the function is not suitable for this test
+  as it specifically does not support having the memory buffer examined after
+  a write has completed but before a flush has been performed.
+  
+  So we resort to opening an ordinary file and using mmap to provide us with
+  a memory page that we can examine.  */
+  
+#define BIG_BUF_SIZE    128
+#define SMALL_BUF_SIZE  12
+
+_Static_assert (SMALL_BUF_SIZE < BIG_BUF_SIZE,
+		"test assumes that its small buffer is shorter than its large buffer");
+
+static char small_buf [SMALL_BUF_SIZE];
+static char big_buf [BIG_BUF_SIZE];
+
+static int
+do_test (void)
+{
+  FILE * file;
+  char * mem_page;
+  size_t page_size;
+  int    fd;
+  int    val;
+
+  TEST_VERIFY_EXIT ((page_size = sysconf (_SC_PAGE_SIZE)) != 0);
+  TEST_VERIFY_EXIT (page_size > SMALL_BUF_SIZE);
+
+  /* Create a temporay file.  */
+  TEST_VERIFY_EXIT ((fd = create_temp_file ("tst-setvbuf6", NULL)) != -1);
+
+  /* Create a stream attached to the file.  */
+  TEST_VERIFY_EXIT ((file = fdopen (fd, "r+")) != NULL);
+  
+  /* Disable buffering on the file.
+
+     Note - this has to be done now, right after opening the file.  The POSIX
+     standard states:
+
+        The setvbuf() function may be used after the stream
+	pointed to by stream is associated with an open file
+	but before any other operation (other than an unsuccessful
+	call to setvbuf( )) is performed on the stream.  */
+  TEST_VERIFY_EXIT (setvbuf (file, NULL, _IONBF, 0) == 0);
+
+  /* Map the file into memory.
+     FIXME: Using xmmap would simplify this statement, but it would be
+     inconsistent with how we test all of the other library function calls.  */
+  TEST_VERIFY_EXIT ((mem_page = mmap (NULL, page_size, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) != MAP_FAILED);
+  
+  /* The page backing the file has not been initialised yet, so attempts
+     to read from it will produce a SIGBUS error.  We fix that by setting
+     the file to the size of the page.  */
+  TEST_VERIFY (ftruncate (fd, page_size) == 0);
+  
+  /* Create our test data using a value that should not
+     be the same as the contents of the memory page.  */
+  val = mem_page[0] + 1;
+  memset (small_buf, val, sizeof small_buf);
+  
+  /* Write one byte to the file.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, 1, file) == 1);
+
+  /* Check that the byte has made it into the file.  */
+  TEST_VERIFY (mem_page[0] == val);
+  
+  /* Try reading the byte.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, 1, file) == 1);
+  TEST_VERIFY (big_buf[0] == small_buf[0]);
+
+  /* Write a whole buffer full.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fwrite (small_buf, 1, sizeof small_buf, file) == sizeof small_buf);
+
+  /* Check that the in-memory buffer now contains these bytes.  */
+  TEST_VERIFY (memcmp (small_buf, mem_page, sizeof small_buf) == 0);
+
+  /* Try reading lots of data.  */
+  TEST_VERIFY (fseek (file, 0L, SEEK_SET) == 0);
+  TEST_VERIFY (fread (big_buf, 1, sizeof big_buf, file) == sizeof big_buf);
+
+  /* Verify that what we have read the expected bytes.  */
+  TEST_VERIFY (memcmp (big_buf, small_buf, sizeof small_buf) == 0);
+  
+  fclose (file);
+  return 0;
+}
+
+#include <support/test-driver.c>