Backport fwrite tests and a fix for BZ 29459
Resolves: RHEL-55471
This commit is contained in:
parent
283b9330df
commit
e87166d350
94
glibc-RHEL-55471-1.patch
Normal file
94
glibc-RHEL-55471-1.patch
Normal file
@ -0,0 +1,94 @@
|
||||
commit 5d4ab106d4cf7d6e410d6fc3d460b090c9108682
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Thu Sep 5 15:34:29 2024 -0300
|
||||
|
||||
Add a new fwrite test for read-only streams
|
||||
|
||||
Ensure that fwrite() behaves correctly even when the stream is
|
||||
read-only.
|
||||
|
||||
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||
|
||||
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
||||
index 74e0edff73a9e468..4c2b820c641e8b55 100644
|
||||
--- a/stdio-common/Makefile
|
||||
+++ b/stdio-common/Makefile
|
||||
@@ -217,6 +217,7 @@ tests := \
|
||||
tst-freopen64-7 \
|
||||
tst-fseek \
|
||||
tst-fwrite \
|
||||
+ tst-fwrite-ro \
|
||||
tst-getline \
|
||||
tst-getline-enomem \
|
||||
tst-gets \
|
||||
diff --git a/stdio-common/tst-fwrite-ro.c b/stdio-common/tst-fwrite-ro.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..7013bee53cc494d0
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-ro.c
|
||||
@@ -0,0 +1,65 @@
|
||||
+/* Test fwrite on a read-only stream.
|
||||
+ 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 <support/check.h>
|
||||
+#include <support/temp_file.h>
|
||||
+#include <support/xstdio.h>
|
||||
+#include <support/xunistd.h>
|
||||
+
|
||||
+/* A small buffer size is enough to run this test. */
|
||||
+#define BUFSIZE 4
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ int fd;
|
||||
+ FILE *f;
|
||||
+ struct stat64 st;
|
||||
+
|
||||
+ /* Create a temporary file and open it in read-only mode. */
|
||||
+ fd = create_temp_file ("tst-fwrite-ro", NULL);
|
||||
+ TEST_VERIFY_EXIT (fd != -1);
|
||||
+ f = fdopen (fd, "r");
|
||||
+ TEST_VERIFY_EXIT (f != NULL);
|
||||
+
|
||||
+ /* Try to write to the temporary file with nmemb = 0, then check that
|
||||
+ fwrite returns 0. No errors are expected from this. */
|
||||
+ TEST_COMPARE (fwrite ("a", 1, 0, f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+
|
||||
+ /* Try to write to the temporary file with size = 0, then check that
|
||||
+ fwrite returns 0. No errors are expected from this. */
|
||||
+ TEST_COMPARE (fwrite ("a", 0, 1, f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+
|
||||
+ /* Try to write a single byte to the temporary file, then check that
|
||||
+ fwrite returns 0. Check if an error was reported. */
|
||||
+ TEST_COMPARE (fwrite ("a", 1, 1, f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 1);
|
||||
+ clearerr (f);
|
||||
+
|
||||
+ xfstat64 (fd, &st);
|
||||
+ TEST_COMPARE (st.st_size, 0);
|
||||
+
|
||||
+ xfclose (f);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
39
glibc-RHEL-55471-10.patch
Normal file
39
glibc-RHEL-55471-10.patch
Normal file
@ -0,0 +1,39 @@
|
||||
commit 4734d0f8adde573aeafe79ad0c71807833db1cae
|
||||
Author: Stefan Liebler <stli@linux.ibm.com>
|
||||
Date: Mon Feb 24 14:13:00 2025 +0100
|
||||
|
||||
Increase the amount of data tested in stdio-common/tst-fwrite-pipe.c
|
||||
|
||||
The number of iterations and the length of the string are not high
|
||||
enough on some systems causing the test to return false-positives.
|
||||
|
||||
Testcase stdio-common/tst-fwrite-bz29459.c was fixed in the same way in
|
||||
1b6f868625403d6b7683af840e87d2b18d5d7731
|
||||
(Increase the amount of data tested in stdio-common/tst-fwrite-bz29459.c, 2025-02-14)
|
||||
|
||||
Testcases stdio-common/tst-fwrite-bz29459.c and stdio-common/tst-fwrite-pipe.c
|
||||
were introcued in 596a61cf6b51ce2d58b8ca4e1d1f4fdfe1440dbc
|
||||
(libio: Start to return errors when flushing fwrite's buffer [BZ #29459], 2025-01-28)
|
||||
|
||||
diff --git a/stdio-common/tst-fwrite-pipe.c b/stdio-common/tst-fwrite-pipe.c
|
||||
index a6119125b25eeddb..ce1a92b384279600 100644
|
||||
--- a/stdio-common/tst-fwrite-pipe.c
|
||||
+++ b/stdio-common/tst-fwrite-pipe.c
|
||||
@@ -27,7 +27,7 @@
|
||||
/* Usually this test reproduces in a few iterations. However, keep a high
|
||||
number of iterations in order to avoid return false-positives due to an
|
||||
overwhelmed/slow system. */
|
||||
-#define ITERATIONS 5000
|
||||
+#define ITERATIONS 500000
|
||||
|
||||
#define BUFFERSIZE 20
|
||||
|
||||
@@ -71,7 +71,7 @@ do_test (void)
|
||||
{
|
||||
/* Ensure the string we send has a new line because we're dealing
|
||||
with a lined-buffered stream. */
|
||||
- const char *s = "hello\n";
|
||||
+ const char *s = "hello world\n";
|
||||
size_t len = strlen (s);
|
||||
int i;
|
||||
|
205
glibc-RHEL-55471-2.patch
Normal file
205
glibc-RHEL-55471-2.patch
Normal file
@ -0,0 +1,205 @@
|
||||
commit dccc9a5161264d2f98411c24ae22495ca3a09b60
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Thu Aug 29 14:12:41 2024 -0300
|
||||
|
||||
Add a new fwrite test for memory streams
|
||||
|
||||
Ensure that fwrite() behaves correctly when using memory streams.
|
||||
|
||||
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||
|
||||
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
||||
index 4c2b820c641e8b55..a483234085a6c612 100644
|
||||
--- a/stdio-common/Makefile
|
||||
+++ b/stdio-common/Makefile
|
||||
@@ -217,6 +217,7 @@ tests := \
|
||||
tst-freopen64-7 \
|
||||
tst-fseek \
|
||||
tst-fwrite \
|
||||
+ tst-fwrite-memstrm \
|
||||
tst-fwrite-ro \
|
||||
tst-getline \
|
||||
tst-getline-enomem \
|
||||
diff --git a/stdio-common/tst-fwrite-memstrm.c b/stdio-common/tst-fwrite-memstrm.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..7ee38314302ba794
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-memstrm.c
|
||||
@@ -0,0 +1,177 @@
|
||||
+/* Test fwrite on a memory stream.
|
||||
+ 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 <support/check.h>
|
||||
+#include <support/temp_file.h>
|
||||
+#include <support/xstdio.h>
|
||||
+#include <support/xunistd.h>
|
||||
+
|
||||
+void
|
||||
+test_ro (void)
|
||||
+{
|
||||
+ FILE *f;
|
||||
+ char *out;
|
||||
+
|
||||
+ /* Try to allocate a small buffer for this test. */
|
||||
+ out = malloc (2);
|
||||
+ TEST_VERIFY_EXIT (out != NULL);
|
||||
+
|
||||
+ /* Try to open the allocated buffer as a read-only stream. */
|
||||
+ f = fmemopen (out, 2, "r");
|
||||
+ TEST_VERIFY_EXIT (f != NULL);
|
||||
+
|
||||
+ /* Try to write to the temporary file with nmemb = 0, then check that
|
||||
+ fwrite returns 0. No errors are expected from this. */
|
||||
+ TEST_COMPARE (fwrite ("a", 1, 0, f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+
|
||||
+ /* Try to write to the temporary file with size = 0, then check that
|
||||
+ fwrite returns 0. No errors are expected from this. */
|
||||
+ TEST_COMPARE (fwrite ("a", 0, 1, f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+
|
||||
+ /* Try to write a single byte to the temporary file, then check that
|
||||
+ fwrite returns 0. Check if an error was reported. */
|
||||
+ TEST_COMPARE (fwrite ("a", 1, 1, f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 1);
|
||||
+
|
||||
+ clearerr (f);
|
||||
+ xfclose (f);
|
||||
+ free (out);
|
||||
+}
|
||||
+
|
||||
+/* Length of the output buffer in bytes. */
|
||||
+#define RWBUF_SIZE 16 * 1024
|
||||
+/* Maximum number of bytes to be written in output buffer. The rest will be
|
||||
+ used to check against overflow. */
|
||||
+#define RWBUF_SIZE_WRITABLE RWBUF_SIZE-2048
|
||||
+
|
||||
+/* Use the following byte to identify areas that should have not been
|
||||
+ modified. */
|
||||
+#define KNOWN_BYTE 0xaa
|
||||
+
|
||||
+void
|
||||
+test_one_rw (const char *in, size_t size, size_t nmemb,
|
||||
+ size_t expected_ret)
|
||||
+{
|
||||
+ FILE *f;
|
||||
+ char *out, *expected_out;
|
||||
+ /* Total number of bytes expected to be written. */
|
||||
+ size_t expected_bytes = size * nmemb;
|
||||
+
|
||||
+ printf ("Testing with size = %zd, nmemb = %zd\n", size, nmemb);
|
||||
+
|
||||
+ TEST_VERIFY_EXIT (expected_ret <= RWBUF_SIZE_WRITABLE);
|
||||
+ TEST_VERIFY_EXIT (expected_bytes <= RWBUF_SIZE_WRITABLE);
|
||||
+
|
||||
+ /* Try to allocate a buffer for this test and initialize it with
|
||||
+ known contents. */
|
||||
+ out = malloc (RWBUF_SIZE);
|
||||
+ TEST_VERIFY_EXIT (out != NULL);
|
||||
+ memset (out, KNOWN_BYTE, RWBUF_SIZE);
|
||||
+
|
||||
+ /* Try to allocate a buffer and fill it with the contents that are expected
|
||||
+ to be in memory after flushing/closing the memory stream. */
|
||||
+ expected_out = malloc (RWBUF_SIZE);
|
||||
+ TEST_VERIFY_EXIT (expected_out != NULL);
|
||||
+ if (expected_bytes > 0)
|
||||
+ {
|
||||
+ memcpy (expected_out, in, expected_bytes);
|
||||
+ expected_out[expected_bytes] = 0;
|
||||
+ memset (expected_out + expected_bytes + 1, KNOWN_BYTE,
|
||||
+ RWBUF_SIZE - expected_bytes - 1);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ /* No changes to the output are expected. */
|
||||
+ memset (expected_out, KNOWN_BYTE, RWBUF_SIZE);
|
||||
+ }
|
||||
+
|
||||
+ /* Try to open the allocated buffer as a read-write stream. */
|
||||
+ f = fmemopen (out, RWBUF_SIZE, "w");
|
||||
+ TEST_VERIFY_EXIT (f != NULL);
|
||||
+
|
||||
+ /* Try to write to the memory stream. Check if fwrite() returns the
|
||||
+ expected value. No errors are expected. */
|
||||
+ TEST_COMPARE (fwrite (in, size, nmemb, f), expected_ret);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+
|
||||
+ xfclose (f);
|
||||
+
|
||||
+ /* Ensure the output has the expected contents. */
|
||||
+ TEST_COMPARE (memcmp (out, expected_out, expected_bytes), 0);
|
||||
+
|
||||
+ free (expected_out);
|
||||
+ free (out);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+test_rw (void)
|
||||
+{
|
||||
+ char * in;
|
||||
+ int i, j;
|
||||
+ size_t size[] = {1, 8, 11, 16, 17, 0};
|
||||
+ size_t nmemb[] = {32, 83, 278, 709, 4097, RWBUF_SIZE / 2,
|
||||
+ RWBUF_SIZE_WRITABLE, 0};
|
||||
+ size_t n;
|
||||
+
|
||||
+ /* Try to write to the temporary file with nmemb = 0, then check that
|
||||
+ fwrite returns 0; */
|
||||
+ test_one_rw ("a", 1, 0, 0);
|
||||
+
|
||||
+ /* Try to write to the temporary file with size = 0, then check that
|
||||
+ fwrite returns 0; */
|
||||
+ test_one_rw ("a", 0, 1, 0);
|
||||
+
|
||||
+ /* Try to write a single byte to the temporary file, then check that
|
||||
+ fwrite returns 1; */
|
||||
+ test_one_rw ("a", 1, 2, 2);
|
||||
+
|
||||
+ in = malloc (RWBUF_SIZE);
|
||||
+ TEST_VERIFY_EXIT (in != NULL);
|
||||
+ for (i = 0; i < RWBUF_SIZE / 2; i++)
|
||||
+ in[i] = i % 0xff;
|
||||
+
|
||||
+ /* Test with all posibilities of size[] x nmemb[]. */
|
||||
+ for (i = 0; nmemb[i] != 0; i++)
|
||||
+ {
|
||||
+ for (j = 0; size[j] != 0; j++)
|
||||
+ {
|
||||
+ n = nmemb[i] / size[j];
|
||||
+ test_one_rw (in, size[j], n, n);
|
||||
+ }
|
||||
+ /* Run the test with a single item of maximum size. */
|
||||
+ test_one_rw (in, nmemb[i], 1, 1);
|
||||
+ }
|
||||
+
|
||||
+ free (in);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ test_ro ();
|
||||
+ test_rw ();
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
166
glibc-RHEL-55471-3.patch
Normal file
166
glibc-RHEL-55471-3.patch
Normal file
@ -0,0 +1,166 @@
|
||||
commit 97aa92263a151d12286d27d327edc35475fe521c
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Thu Sep 26 11:30:29 2024 -0300
|
||||
|
||||
Add a new fwrite test that exercises buffer overflow
|
||||
|
||||
Exercises fwrite's internal buffer when doing a file operation.
|
||||
The new test, exercises 2 overflow behaviors:
|
||||
|
||||
1. Call fwrite multiple times making usage of fwrite's internal buffer.
|
||||
The total number of bytes written is larger than fwrite's internal
|
||||
buffer, forcing an automatic flush.
|
||||
|
||||
2. Call fwrite a single time with an amount of data that is larger than
|
||||
fwrite's internal buffer.
|
||||
|
||||
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||
|
||||
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
||||
index a483234085a6c612..71f6ea12d103564c 100644
|
||||
--- a/stdio-common/Makefile
|
||||
+++ b/stdio-common/Makefile
|
||||
@@ -218,6 +218,7 @@ tests := \
|
||||
tst-fseek \
|
||||
tst-fwrite \
|
||||
tst-fwrite-memstrm \
|
||||
+ tst-fwrite-overflow \
|
||||
tst-fwrite-ro \
|
||||
tst-getline \
|
||||
tst-getline-enomem \
|
||||
diff --git a/stdio-common/tst-fwrite-overflow.c b/stdio-common/tst-fwrite-overflow.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..fe503fd5890a4812
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-overflow.c
|
||||
@@ -0,0 +1,130 @@
|
||||
+/* Test the overflow of fwrite's internal buffer.
|
||||
+ 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/>. */
|
||||
+
|
||||
+/* stdio.h provides BUFSIZ, which is the size of fwrite's internal buffer. */
|
||||
+#include <stdio.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <support/check.h>
|
||||
+#include <support/temp_file.h>
|
||||
+#include <support/support.h>
|
||||
+#include <support/xstdio.h>
|
||||
+#include <support/xunistd.h>
|
||||
+
|
||||
+/* Length of the buffers in bytes. */
|
||||
+#define RWBUF_SIZE (2 * BUFSIZ)
|
||||
+
|
||||
+void
|
||||
+test_one_rw (const char *in, size_t size, size_t nmemb, size_t blocks)
|
||||
+{
|
||||
+ int fd;
|
||||
+ FILE *f;
|
||||
+ char *out;
|
||||
+ size_t written, to_write;
|
||||
+ const size_t requested = size * nmemb;
|
||||
+
|
||||
+ printf ("Testing with size = %zd, nmemb = %zd, blocks = %zd\n",
|
||||
+ size, nmemb, blocks);
|
||||
+
|
||||
+ TEST_VERIFY_EXIT (requested <= RWBUF_SIZE);
|
||||
+ /* Ensure fwrite's internal buffer will overflow. */
|
||||
+ TEST_VERIFY_EXIT (requested > BUFSIZ);
|
||||
+
|
||||
+ /* Create a temporary file and open it for reading and writing. */
|
||||
+ fd = create_temp_file ("tst-fwrite-overflow", NULL);
|
||||
+ TEST_VERIFY_EXIT (fd != -1);
|
||||
+ f = fdopen (fd, "w+");
|
||||
+ TEST_VERIFY_EXIT (f != NULL);
|
||||
+
|
||||
+ /* Call fwrite() as many times as needed, until all data is written,
|
||||
+ limiting the amount of data written per call to block items. */
|
||||
+ for (written = 0; written < nmemb; written += to_write)
|
||||
+ {
|
||||
+ if (written + blocks <= nmemb)
|
||||
+ to_write = blocks;
|
||||
+ else
|
||||
+ to_write = nmemb - written;
|
||||
+ /* Check if fwrite() returns the expected value. No errors are
|
||||
+ expected. */
|
||||
+ TEST_COMPARE (fwrite (in + size * written, size, to_write, f),
|
||||
+ to_write);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+ }
|
||||
+ TEST_VERIFY_EXIT (written == nmemb);
|
||||
+
|
||||
+ /* Ensure all the data is flushed to file. */
|
||||
+ TEST_COMPARE (fflush (f), 0);
|
||||
+
|
||||
+ /* We have to check if the contents in the file are correct. Go back to
|
||||
+ the beginning of the file. */
|
||||
+ rewind (f);
|
||||
+ /* Try to allocate a buffer and save the contents of the generated file to
|
||||
+ it. */
|
||||
+ out = xmalloc (RWBUF_SIZE);
|
||||
+ TEST_COMPARE (fread (out, size, nmemb, f), nmemb);
|
||||
+
|
||||
+ /* Ensure the output has the expected contents. */
|
||||
+ TEST_COMPARE (memcmp (out, in, requested), 0);
|
||||
+
|
||||
+ xfclose (f);
|
||||
+ free (out);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ char * in;
|
||||
+ int i, j;
|
||||
+ size_t nmemb[] = {BUFSIZ + 1, RWBUF_SIZE, 0};
|
||||
+ /* Maximum number of items written for each fwrite call. */
|
||||
+ size_t block[] = {100, 1024, 2047, 0};
|
||||
+ /* The largest block must fit entirely in fwrite's buffer. */
|
||||
+ _Static_assert (2047 < BUFSIZ,
|
||||
+ "a block must fit in fwrite's internal buffer");
|
||||
+
|
||||
+ in = xmalloc (RWBUF_SIZE);
|
||||
+ for (i = 0; i < RWBUF_SIZE; i++)
|
||||
+ in[i] = i % 0xff;
|
||||
+
|
||||
+ for (i = 0; nmemb[i] != 0; i++)
|
||||
+ for (j = 0; block[j] != 0; j++)
|
||||
+ {
|
||||
+ /* Run a test with an array of nmemb bytes. Write at most block
|
||||
+ items per fwrite call. */
|
||||
+ test_one_rw (in, 1, nmemb[i], block[j]);
|
||||
+ /* Run a test that overflows fwrite's internal buffer in a single call
|
||||
+ by writting a single item of nmemb bytes.
|
||||
+ This call should not use the buffer and should be written directly
|
||||
+ to the file. */
|
||||
+ test_one_rw (in, nmemb[i], 1, nmemb[i]);
|
||||
+ }
|
||||
+
|
||||
+ for (j = 0; block[j] != 0; j++)
|
||||
+ {
|
||||
+ /* Run a test with size=2 and the minimum nmemb value that still
|
||||
+ overflows the buffer. Write at most block items per fwrite call. */
|
||||
+ test_one_rw (in, 2, BUFSIZ / 2 + 1, block[j]);
|
||||
+ /* Likewise, but size=3. */
|
||||
+ test_one_rw (in, 3, BUFSIZ / 3 + 1, block[j]);
|
||||
+ }
|
||||
+
|
||||
+ free (in);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
445
glibc-RHEL-55471-4.patch
Normal file
445
glibc-RHEL-55471-4.patch
Normal file
@ -0,0 +1,445 @@
|
||||
commit 596a61cf6b51ce2d58b8ca4e1d1f4fdfe1440dbc
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Tue Jan 28 15:37:44 2025 -0300
|
||||
|
||||
libio: Start to return errors when flushing fwrite's buffer [BZ #29459]
|
||||
|
||||
When an error happens, fwrite is expected to return a value that is less
|
||||
than nmemb. If this error happens while flushing its internal buffer,
|
||||
fwrite is in a complex scenario: all the data might have been written to
|
||||
the buffer, indicating a successful copy, but the buffer is expected to
|
||||
be flushed and it was not.
|
||||
|
||||
POSIX.1-2024 states the following about errors on fwrite:
|
||||
|
||||
If an error occurs, the resulting value of the file-position indicator
|
||||
for the stream is unspecified.
|
||||
|
||||
The fwrite() function shall return the number of elements successfully
|
||||
written, which may be less than nitems if a write error is encountered.
|
||||
|
||||
With that in mind, this commit modifies _IO_new_file_write in order to
|
||||
return the total number of bytes written via the file pointer. It also
|
||||
modifies fwrite in order to use the new information and return the
|
||||
correct number of bytes written even when sputn returns EOF.
|
||||
|
||||
Add 2 tests:
|
||||
|
||||
1. tst-fwrite-bz29459: This test is based on the reproducer attached to
|
||||
bug 29459. In order to work, it requires to pipe stdout to another
|
||||
process making it hard to reuse test-driver.c. This code is more
|
||||
specific to the issue reported.
|
||||
2. tst-fwrite-pipe: Recreates the issue by creating a pipe that is shared
|
||||
with a child process. Reuses test-driver.c. Evaluates a more generic
|
||||
scenario.
|
||||
|
||||
Co-authored-by: Florian Weimer <fweimer@redhat.com>
|
||||
Reviewed-by: DJ Delorie <dj@redhat.com>
|
||||
|
||||
Conflicts:
|
||||
libio/bits/types/struct_FILE.h
|
||||
(Downstream is missing commit 2a99e2398d9d717c034e915f7846a49e623f5450)
|
||||
|
||||
diff --git a/libio/bits/types/struct_FILE.h b/libio/bits/types/struct_FILE.h
|
||||
index f7f756a701ce0e93..7292334a28ad3f79 100644
|
||||
--- a/libio/bits/types/struct_FILE.h
|
||||
+++ b/libio/bits/types/struct_FILE.h
|
||||
@@ -102,8 +102,15 @@ struct _IO_FILE_complete
|
||||
void *_freeres_buf;
|
||||
size_t __pad5;
|
||||
int _mode;
|
||||
+#ifdef __LP64__
|
||||
+ int _unused3;
|
||||
+#endif
|
||||
+ __uint64_t _total_written;
|
||||
+#ifndef __LP64__
|
||||
+ int _unused3;
|
||||
+#endif
|
||||
/* Make sure we don't get into trouble again. */
|
||||
- char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
|
||||
+ char _unused2[12 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
|
||||
};
|
||||
|
||||
/* These macros are used by bits/stdio.h and internal headers. */
|
||||
diff --git a/libio/fileops.c b/libio/fileops.c
|
||||
index f43ad59c5a5bca7d..b2354d42b420b80c 100644
|
||||
--- a/libio/fileops.c
|
||||
+++ b/libio/fileops.c
|
||||
@@ -114,6 +114,7 @@ _IO_new_file_init_internal (struct _IO_FILE_plus *fp)
|
||||
|
||||
_IO_link_in (fp);
|
||||
fp->file._fileno = -1;
|
||||
+ fp->file._total_written = 0;
|
||||
}
|
||||
|
||||
/* External version of _IO_new_file_init_internal which switches off
|
||||
@@ -1185,6 +1186,7 @@ _IO_new_file_write (FILE *f, const void *data, ssize_t n)
|
||||
f->_flags |= _IO_ERR_SEEN;
|
||||
break;
|
||||
}
|
||||
+ f->_total_written += count;
|
||||
to_do -= count;
|
||||
data = (void *) ((char *) data + count);
|
||||
}
|
||||
diff --git a/libio/iofwrite.c b/libio/iofwrite.c
|
||||
index 71b609c526b79071..5c648302c8fd9224 100644
|
||||
--- a/libio/iofwrite.c
|
||||
+++ b/libio/iofwrite.c
|
||||
@@ -36,13 +36,42 @@ _IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp)
|
||||
return 0;
|
||||
_IO_acquire_lock (fp);
|
||||
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
|
||||
- written = _IO_sputn (fp, (const char *) buf, request);
|
||||
+ {
|
||||
+ /* Compute actually written bytes plus pending buffer
|
||||
+ contents. */
|
||||
+ uint64_t original_total_written
|
||||
+ = fp->_total_written + (fp->_IO_write_ptr - fp->_IO_write_base);
|
||||
+ written = _IO_sputn (fp, (const char *) buf, request);
|
||||
+ if (written == EOF)
|
||||
+ {
|
||||
+ /* An error happened and we need to find the appropriate return
|
||||
+ value. There 3 possible scenarios:
|
||||
+ 1. If the number of bytes written is between 0..[buffer content],
|
||||
+ we need to return 0 because none of the bytes from this
|
||||
+ request have been written;
|
||||
+ 2. If the number of bytes written is between
|
||||
+ [buffer content]+1..request-1, that means we managed to write
|
||||
+ data requested in this fwrite call;
|
||||
+ 3. We might have written all the requested data and got an error
|
||||
+ anyway. We can't return success, which means we still have to
|
||||
+ return less than request. */
|
||||
+ if (fp->_total_written > original_total_written)
|
||||
+ {
|
||||
+ written = fp->_total_written - original_total_written;
|
||||
+ /* If everything was reported as written and somehow an
|
||||
+ error occurred afterwards, avoid reporting success. */
|
||||
+ if (written == request)
|
||||
+ --written;
|
||||
+ }
|
||||
+ else
|
||||
+ /* Only already-pending buffer contents was written. */
|
||||
+ written = 0;
|
||||
+ }
|
||||
+ }
|
||||
_IO_release_lock (fp);
|
||||
/* We have written all of the input in case the return value indicates
|
||||
- this or EOF is returned. The latter is a special case where we
|
||||
- simply did not manage to flush the buffer. But the data is in the
|
||||
- buffer and therefore written as far as fwrite is concerned. */
|
||||
- if (written == request || written == EOF)
|
||||
+ this. */
|
||||
+ if (written == request)
|
||||
return count;
|
||||
else
|
||||
return written / size;
|
||||
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
||||
index 71f6ea12d103564c..b4a1e62f4a388d0a 100644
|
||||
--- a/stdio-common/Makefile
|
||||
+++ b/stdio-common/Makefile
|
||||
@@ -219,6 +219,7 @@ tests := \
|
||||
tst-fwrite \
|
||||
tst-fwrite-memstrm \
|
||||
tst-fwrite-overflow \
|
||||
+ tst-fwrite-pipe \
|
||||
tst-fwrite-ro \
|
||||
tst-getline \
|
||||
tst-getline-enomem \
|
||||
@@ -276,6 +277,7 @@ endif
|
||||
|
||||
test-srcs = \
|
||||
$(xprintf-srcs) \
|
||||
+ tst-fwrite-bz29459 \
|
||||
tst-printf \
|
||||
tst-printfsz-islongdouble \
|
||||
tst-unbputc \
|
||||
@@ -284,6 +286,7 @@ test-srcs = \
|
||||
ifeq ($(run-built-tests),yes)
|
||||
tests-special += \
|
||||
$(foreach f,$(xprintf-stems),$(objpfx)$(f).out) \
|
||||
+ $(objpfx)tst-fwrite-bz29459.out \
|
||||
$(objpfx)tst-printf.out \
|
||||
$(objpfx)tst-printfsz-islongdouble.out \
|
||||
$(objpfx)tst-setvbuf1-cmp.out \
|
||||
@@ -436,6 +439,10 @@ tst-freopen64-6-ENV = \
|
||||
MALLOC_TRACE=$(objpfx)tst-freopen64-6.mtrace \
|
||||
LD_PRELOAD=$(common-objpfx)malloc/libc_malloc_debug.so
|
||||
|
||||
+$(objpfx)tst-fwrite-bz29459.out: tst-fwrite-bz29459.sh $(objpfx)tst-fwrite-bz29459
|
||||
+ $(SHELL) $< $(common-objpfx) '$(test-program-prefix)'; \
|
||||
+ $(evaluate-test)
|
||||
+
|
||||
$(objpfx)tst-unbputc.out: tst-unbputc.sh $(objpfx)tst-unbputc
|
||||
$(SHELL) $< $(common-objpfx) '$(test-program-prefix)'; \
|
||||
$(evaluate-test)
|
||||
diff --git a/stdio-common/tst-fwrite-bz29459.c b/stdio-common/tst-fwrite-bz29459.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..0640faac0c3823ef
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-bz29459.c
|
||||
@@ -0,0 +1,89 @@
|
||||
+/* Test fwrite against bug 29459.
|
||||
+ Copyright (C) 2025 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/>. */
|
||||
+
|
||||
+/* This test is based on the code attached to bug 29459.
|
||||
+ It depends on stdout being redirected to a specific process via a script
|
||||
+ with the same name. Because of this, we cannot use the features from
|
||||
+ test_driver.c. */
|
||||
+
|
||||
+#include <stdio.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <errno.h>
|
||||
+#include <unistd.h>
|
||||
+#include <support/check.h>
|
||||
+#include <support/xsignal.h>
|
||||
+
|
||||
+/* Usually this test reproduces in a few iterations. However, keep a high
|
||||
+ number of iterations in order to avoid return false-positives due to an
|
||||
+ overwhelmed/slow system. */
|
||||
+#define ITERATIONS 5000
|
||||
+
|
||||
+/* The goal of this test is to use fwrite () on a redirected and closed
|
||||
+ stdout. A script will guarantee that stdout is redirected to another
|
||||
+ process that closes it during the execution. The process reading from
|
||||
+ the pipe must read at least the first line in order to guarantee that
|
||||
+ flag _IO_CURRENTLY_PUTTING is set in the write end of the pipe, triggering
|
||||
+ important parts of the code that flushes lines from fwrite's internal
|
||||
+ buffer. The underlying write () returns EPIPE, which fwrite () must
|
||||
+ propagate. */
|
||||
+
|
||||
+int
|
||||
+main (void)
|
||||
+{
|
||||
+ int i;
|
||||
+ size_t rc;
|
||||
+ /* Ensure the string we send has a new line because we're dealing
|
||||
+ with a lined-buffered stream. */
|
||||
+ const char *s = "hello\n";
|
||||
+ const size_t len = strlen(s);
|
||||
+
|
||||
+ /* Ensure that fwrite buffers the output before writing to stdout. */
|
||||
+ setlinebuf(stdout);
|
||||
+ /* Ignore SIGPIPE in order to catch the EPIPE returned by the
|
||||
+ underlying call to write(). */
|
||||
+ xsignal(SIGPIPE, SIG_IGN);
|
||||
+
|
||||
+ for (i = 1; i <= ITERATIONS; i++)
|
||||
+ {
|
||||
+ /* Keep writing to stdout. The test succeeds if fwrite () returns an
|
||||
+ error. */
|
||||
+ if ((rc = fwrite(s, 1, len, stdout)) < len)
|
||||
+ {
|
||||
+ /* An error happened. Check if ferror () does return an error
|
||||
+ and that it is indeed EPIPE. */
|
||||
+ TEST_COMPARE (ferror (stdout), 1);
|
||||
+ TEST_COMPARE (errno, EPIPE);
|
||||
+ fprintf(stderr, "Success: i=%d. fwrite returned %zu < %zu "
|
||||
+ "and errno=EPIPE\n",
|
||||
+ i, rc, len);
|
||||
+ /* The test succeeded! */
|
||||
+ return 0;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ /* fwrite () was able to write all the contents. Check if no errors
|
||||
+ have been reported and try again. */
|
||||
+ TEST_COMPARE (ferror (stdout), 0);
|
||||
+ TEST_COMPARE (errno, 0);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ fprintf(stderr, "Error: fwrite did not return an error\n");
|
||||
+ return 1;
|
||||
+}
|
||||
diff --git a/stdio-common/tst-fwrite-bz29459.sh b/stdio-common/tst-fwrite-bz29459.sh
|
||||
new file mode 100755
|
||||
index 0000000000000000..164313532b91cb56
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-bz29459.sh
|
||||
@@ -0,0 +1,34 @@
|
||||
+#!/bin/sh
|
||||
+# Test fwrite for bug 29459.
|
||||
+# Copyright (C) 2025 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/>.
|
||||
+
|
||||
+set -e
|
||||
+
|
||||
+common_objpfx=$1; shift
|
||||
+test_program_prefix=$1; shift
|
||||
+
|
||||
+status=0
|
||||
+
|
||||
+${test_program_prefix} \
|
||||
+ ${common_objpfx}stdio-common/tst-fwrite-bz29459 \
|
||||
+ 2> ${common_objpfx}stdio-common/tst-fwrite-bz29459.out \
|
||||
+ | head -n1 > /dev/null
|
||||
+
|
||||
+grep -q Success ${common_objpfx}stdio-common/tst-fwrite-bz29459.out || status=1
|
||||
+
|
||||
+exit $status
|
||||
diff --git a/stdio-common/tst-fwrite-pipe.c b/stdio-common/tst-fwrite-pipe.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..a6119125b25eeddb
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-pipe.c
|
||||
@@ -0,0 +1,130 @@
|
||||
+/* Test if fwrite returns EPIPE.
|
||||
+ Copyright (C) 2025 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 <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <errno.h>
|
||||
+#include <support/check.h>
|
||||
+#include <support/xstdio.h>
|
||||
+#include <support/xsignal.h>
|
||||
+#include <support/xunistd.h>
|
||||
+
|
||||
+/* Usually this test reproduces in a few iterations. However, keep a high
|
||||
+ number of iterations in order to avoid return false-positives due to an
|
||||
+ overwhelmed/slow system. */
|
||||
+#define ITERATIONS 5000
|
||||
+
|
||||
+#define BUFFERSIZE 20
|
||||
+
|
||||
+/* When the underlying write () fails with EPIPE, fwrite () is expected to
|
||||
+ return an error by returning < nmemb and keeping errno=EPIPE. */
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ int fd[2];
|
||||
+ pid_t p;
|
||||
+ FILE *f;
|
||||
+ size_t written;
|
||||
+ int ret = 1; /* Return failure by default. */
|
||||
+
|
||||
+ /* Try to create a pipe. */
|
||||
+ xpipe (fd);
|
||||
+
|
||||
+ p = xfork ();
|
||||
+ if (p == 0)
|
||||
+ {
|
||||
+ char b[BUFFERSIZE];
|
||||
+ size_t bytes;
|
||||
+
|
||||
+ /* Read at least the first line from the pipe before closing it.
|
||||
+ This is important because it guarantees the file stream will have
|
||||
+ flag _IO_CURRENTLY_PUTTING set, which triggers important parts of
|
||||
+ the code that flushes lines from fwrite's internal buffer. */
|
||||
+ do {
|
||||
+ bytes = read (fd[0], b, BUFFERSIZE);
|
||||
+ } while(bytes > 0 && memrchr (b, '\n', bytes) == NULL);
|
||||
+
|
||||
+ /* Child closes both ends of the pipe in order to trigger an EPIPE
|
||||
+ error on the parent. */
|
||||
+ xclose (fd[0]);
|
||||
+ xclose (fd[1]);
|
||||
+
|
||||
+ return 0;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ /* Ensure the string we send has a new line because we're dealing
|
||||
+ with a lined-buffered stream. */
|
||||
+ const char *s = "hello\n";
|
||||
+ size_t len = strlen (s);
|
||||
+ int i;
|
||||
+
|
||||
+ /* Parent only writes to pipe.
|
||||
+ Close the unused read end of the pipe. */
|
||||
+ xclose (fd[0]);
|
||||
+
|
||||
+ /* Ignore SIGPIPE in order to catch the EPIPE returned by the
|
||||
+ underlying call to write(). */
|
||||
+ xsignal(SIGPIPE, SIG_IGN);
|
||||
+
|
||||
+ /* Create a file stream associated with the write end of the pipe. */
|
||||
+ f = fdopen (fd[1], "w");
|
||||
+ TEST_VERIFY_EXIT (f != NULL);
|
||||
+ /* Ensure that fwrite buffers the output before writing to the pipe. */
|
||||
+ setlinebuf (f);
|
||||
+
|
||||
+ /* Ensure errno is not set before starting. */
|
||||
+ errno = 0;
|
||||
+ for (i = 1; i <= ITERATIONS; i++)
|
||||
+ {
|
||||
+ /* Try to write to the pipe. The first calls are expected to
|
||||
+ suceeded until the child process closes the read end.
|
||||
+ After that, fwrite () is expected to fail and errno should be
|
||||
+ set to EPIPE. */
|
||||
+ written = fwrite (s, 1, len, f);
|
||||
+
|
||||
+ if (written == len)
|
||||
+ {
|
||||
+ TEST_VERIFY_EXIT (ferror (f) == 0);
|
||||
+ TEST_VERIFY_EXIT (errno == 0);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ /* An error happened. Check if ferror () does return an error
|
||||
+ and that it is indeed EPIPE. */
|
||||
+ TEST_COMPARE (ferror (f), 1);
|
||||
+ TEST_COMPARE (errno, EPIPE);
|
||||
+ /* The test succeeded! Clear the error from the file stream and
|
||||
+ return success. */
|
||||
+ clearerr (f);
|
||||
+ ret = 0;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ xfclose (f);
|
||||
+ }
|
||||
+
|
||||
+ if (ret)
|
||||
+ FAIL_RET ("fwrite should have returned an error, but it didn't.\n");
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
262
glibc-RHEL-55471-5.patch
Normal file
262
glibc-RHEL-55471-5.patch
Normal file
@ -0,0 +1,262 @@
|
||||
commit 1515f74fd81035a79861cd9fa12053fa9450ec65
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Tue Jan 28 15:37:44 2025 -0300
|
||||
|
||||
libio: Add a new fwrite test that evaluates partial writes
|
||||
|
||||
Test if the file-position is correctly updated when fwrite tries to
|
||||
flush its internal cache but is not able to completely write all items.
|
||||
|
||||
Reviewed-by: DJ Delorie <dj@redhat.com>
|
||||
|
||||
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
|
||||
index b4a1e62f4a388d0a..acf7059ba50d2ca9 100644
|
||||
--- a/stdio-common/Makefile
|
||||
+++ b/stdio-common/Makefile
|
||||
@@ -220,6 +220,7 @@ tests := \
|
||||
tst-fwrite-memstrm \
|
||||
tst-fwrite-overflow \
|
||||
tst-fwrite-pipe \
|
||||
+ tst-fwrite-pos \
|
||||
tst-fwrite-ro \
|
||||
tst-getline \
|
||||
tst-getline-enomem \
|
||||
diff --git a/stdio-common/tst-fwrite-pos.c b/stdio-common/tst-fwrite-pos.c
|
||||
new file mode 100644
|
||||
index 0000000000000000..3923490d5923b4b4
|
||||
--- /dev/null
|
||||
+++ b/stdio-common/tst-fwrite-pos.c
|
||||
@@ -0,0 +1,233 @@
|
||||
+/* Test if fwrite returns consistent values on partial writes.
|
||||
+ Copyright (C) 2025 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 <errno.h>
|
||||
+/* stdio.h provides BUFSIZ, which is the size of fwrite's internal buffer. */
|
||||
+#include <stdio.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <support/check.h>
|
||||
+#include <support/fuse.h>
|
||||
+#include <support/support.h>
|
||||
+#include <support/xstdio.h>
|
||||
+#include <support/temp_file.h>
|
||||
+
|
||||
+/* Length of the buffer in bytes. */
|
||||
+#define INBUF_SIZE (BUFSIZ)
|
||||
+
|
||||
+/* Amount of bytes written to fwrite's internal cache that trigger a
|
||||
+ flush. */
|
||||
+#define CACHE_THRESHOLD (BUFSIZ / 2)
|
||||
+
|
||||
+#define ITERATIONS 1000
|
||||
+
|
||||
+/* Maximum number of bytes written during a partial write. */
|
||||
+#define PARTIAL_BYTES 4
|
||||
+
|
||||
+#define EXPECT_EVENT(opcode, state, expected_state) \
|
||||
+ { \
|
||||
+ if (state != expected_state) \
|
||||
+ { \
|
||||
+ char *s = support_fuse_opcode (opcode); \
|
||||
+ FAIL ("unexpected event %s at state %d", s, state); \
|
||||
+ free (s); \
|
||||
+ } \
|
||||
+ }
|
||||
+
|
||||
+/* The goal of this test is to check that file position of a file stream is
|
||||
+ correctly updated when write () returns a partial write.
|
||||
+ The file system simulates pseudorandom partial writes while the test is
|
||||
+ running.
|
||||
+ Meanwhile the main thread calls fwrite () with a large object first and
|
||||
+ small objects later. The usage of a large enough object ensures that
|
||||
+ fwrite's internal cache is full enough, without triggering a write to file.
|
||||
+ Subsequent calls to fwrite are guaranteed to trigger a write to file. */
|
||||
+
|
||||
+static void
|
||||
+fuse_thread (struct support_fuse *f, void *closure)
|
||||
+{
|
||||
+ struct fuse_in_header *inh;
|
||||
+ int state = 0;
|
||||
+ while ((inh = support_fuse_next (f)) != NULL)
|
||||
+ {
|
||||
+ {
|
||||
+ char *opcode = support_fuse_opcode (inh->opcode);
|
||||
+ printf ("info: (T) event %s(%llu) len=%u nodeid=%llu\n",
|
||||
+ opcode, (unsigned long long int) inh->unique, inh->len,
|
||||
+ (unsigned long long int) inh->nodeid);
|
||||
+ free (opcode);
|
||||
+ }
|
||||
+
|
||||
+ /* Handle mountpoint and basic directory operation for the root (1). */
|
||||
+ if (support_fuse_handle_mountpoint (f)
|
||||
+ || (inh->nodeid == 1 && support_fuse_handle_directory (f)))
|
||||
+ continue;
|
||||
+
|
||||
+ switch (inh->opcode)
|
||||
+ {
|
||||
+ case FUSE_LOOKUP:
|
||||
+ EXPECT_EVENT (inh->nodeid, state, 0);
|
||||
+ state++;
|
||||
+ support_fuse_reply_error (f, ENOENT);
|
||||
+ break;
|
||||
+ case FUSE_CREATE:
|
||||
+ EXPECT_EVENT (inh->nodeid, state, 1);
|
||||
+ state++;
|
||||
+ struct fuse_entry_out *entry;
|
||||
+ struct fuse_open_out *open;
|
||||
+ support_fuse_prepare_create (f, 2, &entry, &open);
|
||||
+ entry->attr.mode = S_IFREG | 0600;
|
||||
+ support_fuse_reply_prepared (f);
|
||||
+ break;
|
||||
+ case FUSE_GETXATTR:
|
||||
+ /* We don't need to support extended attributes in this test. */
|
||||
+ support_fuse_reply_error (f, ENOSYS);
|
||||
+ break;
|
||||
+ case FUSE_GETATTR:
|
||||
+ /* Happens after open. */
|
||||
+ if (inh->nodeid == 2)
|
||||
+ {
|
||||
+ struct fuse_attr_out *out = support_fuse_prepare_attr (f);
|
||||
+ out->attr.mode = S_IFREG | 0600;
|
||||
+ out->attr.size = 0;
|
||||
+ support_fuse_reply_prepared (f);
|
||||
+ }
|
||||
+ else
|
||||
+ support_fuse_reply_error (f, ENOENT);
|
||||
+ break;
|
||||
+ case FUSE_WRITE:
|
||||
+ if (inh->nodeid == 2)
|
||||
+ {
|
||||
+ struct fuse_write_out out;
|
||||
+ if (state > 1 && state < ITERATIONS + 2)
|
||||
+ {
|
||||
+ /* The 2nd and subsequent calls to fwrite () trigger a
|
||||
+ flush of fwrite's internal cache. Simulate a partial
|
||||
+ write of up to PARTIAL_BYTES bytes. */
|
||||
+ out.padding = 0;
|
||||
+ out.size = 1 + rand () % PARTIAL_BYTES,
|
||||
+ state++;
|
||||
+ support_fuse_reply (f, &out, sizeof (out));
|
||||
+ }
|
||||
+ else if (state >= ITERATIONS + 2)
|
||||
+ {
|
||||
+ /* This request is expected to come from fflush (). Copy
|
||||
+ all the data successfully. This may be executed more
|
||||
+ than once. */
|
||||
+ struct fuse_write_in *p = support_fuse_cast (WRITE, inh);
|
||||
+ out.padding = 0;
|
||||
+ out.size = p->size,
|
||||
+ state++;
|
||||
+ support_fuse_reply (f, &out, sizeof (out));
|
||||
+ }
|
||||
+ else
|
||||
+ support_fuse_reply_error (f, EIO);
|
||||
+ }
|
||||
+ else
|
||||
+ support_fuse_reply_error (f, EIO);
|
||||
+ break;
|
||||
+ case FUSE_FLUSH:
|
||||
+ case FUSE_RELEASE:
|
||||
+ TEST_COMPARE (inh->nodeid, 2);
|
||||
+ support_fuse_reply_empty (f);
|
||||
+ break;
|
||||
+ default:
|
||||
+ FAIL ("unexpected event %s", support_fuse_opcode (inh->opcode));
|
||||
+ support_fuse_reply_error (f, EIO);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+do_test (void)
|
||||
+{
|
||||
+ char *in;
|
||||
+ int i;
|
||||
+ size_t written;
|
||||
+
|
||||
+ _Static_assert (CACHE_THRESHOLD <= INBUF_SIZE,
|
||||
+ "the input buffer must be larger than the cache threshold");
|
||||
+ /* Avoid filling up fwrite's cache. */
|
||||
+ _Static_assert (CACHE_THRESHOLD - 1 + PARTIAL_BYTES * ITERATIONS <= BUFSIZ,
|
||||
+ "fwrite's cache must fit all data written");
|
||||
+
|
||||
+ support_fuse_init ();
|
||||
+ struct support_fuse *fs = support_fuse_mount (fuse_thread, NULL);
|
||||
+
|
||||
+ /* Create and open a temporary file in the fuse mount point. */
|
||||
+ char *fname = xasprintf ("%s/%sXXXXXX", support_fuse_mountpoint (fs),
|
||||
+ "tst-fwrite-fuse");
|
||||
+ int fd = mkstemp (fname);
|
||||
+ TEST_VERIFY_EXIT (fd != -1);
|
||||
+ FILE *f = fdopen (fd, "w");
|
||||
+ TEST_VERIFY_EXIT (f != NULL);
|
||||
+
|
||||
+ /* Allocate an input array that will be written to the temporary file. */
|
||||
+ in = xmalloc (INBUF_SIZE);
|
||||
+ for (i = 0; i < INBUF_SIZE; i++)
|
||||
+ in[i] = i % 0xff;
|
||||
+
|
||||
+ /* Ensure the file position indicator is at the beginning of the stream. */
|
||||
+ TEST_COMPARE (ftell (f), 0);
|
||||
+
|
||||
+ /* Try to fill as most data to the cache of the file stream as possible
|
||||
+ with a single large object.
|
||||
+ All data is expected to be written to the cache.
|
||||
+ No errors are expected from this. */
|
||||
+ TEST_COMPARE (fwrite (in, CACHE_THRESHOLD - 1, 1, f), 1);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+ written = CACHE_THRESHOLD - 1;
|
||||
+
|
||||
+ /* Ensure the file position indicator advanced correctly. */
|
||||
+ TEST_COMPARE (ftell (f), written);
|
||||
+
|
||||
+ for (i = 0; i < ITERATIONS; i++)
|
||||
+ {
|
||||
+ /* Write an extra object of size PARTIAL_BYTES that triggers a write to
|
||||
+ disk. Our FS will write at most PARTIAL_BYTES bytes to the file
|
||||
+ instead of all the data. By writting PARTIAL_BYTES, we guarantee
|
||||
+ the amount of data in the cache will never decrease below
|
||||
+ CACHE_THRESHOLD.
|
||||
+ No errors are expected. */
|
||||
+ TEST_COMPARE (fwrite (in, PARTIAL_BYTES, 1, f), 1);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+ written += PARTIAL_BYTES;
|
||||
+
|
||||
+ /* Ensure the file position indicator advanced correctly. */
|
||||
+ TEST_COMPARE (ftell (f), written);
|
||||
+ }
|
||||
+
|
||||
+ /* Flush the rest of the data. */
|
||||
+ TEST_COMPARE (fflush (f), 0);
|
||||
+ TEST_COMPARE (ferror (f), 0);
|
||||
+
|
||||
+ /* Ensure the file position indicator was not modified. */
|
||||
+ TEST_COMPARE (ftell (f), written);
|
||||
+
|
||||
+ /* In case an unexpected error happened, clear it before exiting. */
|
||||
+ if (ferror (f))
|
||||
+ clearerr (f);
|
||||
+
|
||||
+ xfclose (f);
|
||||
+ free (fname);
|
||||
+ free (in);
|
||||
+ support_fuse_unmount (fs);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+#include <support/test-driver.c>
|
39
glibc-RHEL-55471-6.patch
Normal file
39
glibc-RHEL-55471-6.patch
Normal file
@ -0,0 +1,39 @@
|
||||
commit cdb0800022110bc68a033944f09e501be5bd72d7
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Thu Jan 30 15:51:01 2025 -0300
|
||||
|
||||
libio: Replace __LP64__ with __WORDSIZE
|
||||
|
||||
__LP64__ is a GCC extension and shouldn't be used in an installed
|
||||
header.
|
||||
|
||||
Fixes: 596a61cf6b (libio: Start to return errors when flushing fwrite's buffer [BZ #29459], 2025-01-28)
|
||||
Reported-by: Florian Weimer <fweimer@redhat.com>
|
||||
Reviewed-by: Arjun Shankar <arjun@redhat.com>
|
||||
|
||||
diff --git a/libio/bits/types/struct_FILE.h b/libio/bits/types/struct_FILE.h
|
||||
index 7292334a28ad3f79..59f316f8a03d9498 100644
|
||||
--- a/libio/bits/types/struct_FILE.h
|
||||
+++ b/libio/bits/types/struct_FILE.h
|
||||
@@ -32,6 +32,7 @@
|
||||
#endif
|
||||
|
||||
#include <bits/types.h>
|
||||
+#include <bits/wordsize.h>
|
||||
|
||||
struct _IO_FILE;
|
||||
struct _IO_marker;
|
||||
@@ -102,11 +103,11 @@ struct _IO_FILE_complete
|
||||
void *_freeres_buf;
|
||||
size_t __pad5;
|
||||
int _mode;
|
||||
-#ifdef __LP64__
|
||||
+#if __WORDSIZE == 64
|
||||
int _unused3;
|
||||
#endif
|
||||
__uint64_t _total_written;
|
||||
-#ifndef __LP64__
|
||||
+#if __WORDSIZE == 32
|
||||
int _unused3;
|
||||
#endif
|
||||
/* Make sure we don't get into trouble again. */
|
37
glibc-RHEL-55471-7.patch
Normal file
37
glibc-RHEL-55471-7.patch
Normal file
@ -0,0 +1,37 @@
|
||||
commit 88f7ef881d1b9507aa934104c338b958c37821d7
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Fri Jan 31 10:26:22 2025 -0300
|
||||
|
||||
libio: Initialize _total_written for all kinds of streams
|
||||
|
||||
Move the initialization code to a general place instead of keeping it
|
||||
specific to file-backed streams.
|
||||
|
||||
Fixes: 596a61cf6b (libio: Start to return errors when flushing fwrite's buffer [BZ #29459], 2025-01-28)
|
||||
Reported-by: Florian Weimer <fweimer@redhat.com>
|
||||
Reviewed-by: Arjun Shankar <arjun@redhat.com>
|
||||
|
||||
diff --git a/libio/fileops.c b/libio/fileops.c
|
||||
index b2354d42b420b80c..d40748e0fc548fff 100644
|
||||
--- a/libio/fileops.c
|
||||
+++ b/libio/fileops.c
|
||||
@@ -114,7 +114,6 @@ _IO_new_file_init_internal (struct _IO_FILE_plus *fp)
|
||||
|
||||
_IO_link_in (fp);
|
||||
fp->file._fileno = -1;
|
||||
- fp->file._total_written = 0;
|
||||
}
|
||||
|
||||
/* External version of _IO_new_file_init_internal which switches off
|
||||
diff --git a/libio/genops.c b/libio/genops.c
|
||||
index a82c1b96767e14e0..cf6985938255e70d 100644
|
||||
--- a/libio/genops.c
|
||||
+++ b/libio/genops.c
|
||||
@@ -586,6 +586,7 @@ _IO_no_init (FILE *fp, int flags, int orientation,
|
||||
stream. */
|
||||
fp->_wide_data = (struct _IO_wide_data *) -1L;
|
||||
fp->_freeres_list = NULL;
|
||||
+ fp->_total_written = 0;
|
||||
}
|
||||
|
||||
int
|
34
glibc-RHEL-55471-8.patch
Normal file
34
glibc-RHEL-55471-8.patch
Normal file
@ -0,0 +1,34 @@
|
||||
commit 1b6f868625403d6b7683af840e87d2b18d5d7731
|
||||
Author: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
|
||||
Date: Wed Feb 5 17:20:34 2025 -0300
|
||||
|
||||
Increase the amount of data tested in stdio-common/tst-fwrite-bz29459.c
|
||||
|
||||
The number of iterations and the length of the string are not high
|
||||
enough on some systems causing the test to return false-positives.
|
||||
|
||||
Fixes: 596a61cf6b (libio: Start to return errors when flushing fwrite's buffer [BZ #29459], 2025-01-28)
|
||||
Reported-by: Florian Weimer <fweimer@redhat.com>
|
||||
|
||||
diff --git a/stdio-common/tst-fwrite-bz29459.c b/stdio-common/tst-fwrite-bz29459.c
|
||||
index 0640faac0c3823ef..4fcc4c89e21d754d 100644
|
||||
--- a/stdio-common/tst-fwrite-bz29459.c
|
||||
+++ b/stdio-common/tst-fwrite-bz29459.c
|
||||
@@ -32,7 +32,7 @@
|
||||
/* Usually this test reproduces in a few iterations. However, keep a high
|
||||
number of iterations in order to avoid return false-positives due to an
|
||||
overwhelmed/slow system. */
|
||||
-#define ITERATIONS 5000
|
||||
+#define ITERATIONS 500000
|
||||
|
||||
/* The goal of this test is to use fwrite () on a redirected and closed
|
||||
stdout. A script will guarantee that stdout is redirected to another
|
||||
@@ -50,7 +50,7 @@ main (void)
|
||||
size_t rc;
|
||||
/* Ensure the string we send has a new line because we're dealing
|
||||
with a lined-buffered stream. */
|
||||
- const char *s = "hello\n";
|
||||
+ const char *s = "hello world\n";
|
||||
const size_t len = strlen(s);
|
||||
|
||||
/* Ensure that fwrite buffers the output before writing to stdout. */
|
35
glibc-RHEL-55471-9.patch
Normal file
35
glibc-RHEL-55471-9.patch
Normal file
@ -0,0 +1,35 @@
|
||||
Downstream-only patch to restore the extern ABI for functions
|
||||
like fprintf that use the FILE * type. Rebuilds of applications
|
||||
receive ABI change reports because of this installed header change
|
||||
(indirect subtype change in libabigail terms), and given that
|
||||
this part of struct _IO_FILE is strictly internal, there is no
|
||||
need to expose this change to installed headers.
|
||||
|
||||
diff --git a/libio/bits/types/struct_FILE.h b/libio/bits/types/struct_FILE.h
|
||||
index 59f316f8a03d9498..ab64e4e43d663333 100644
|
||||
--- a/libio/bits/types/struct_FILE.h
|
||||
+++ b/libio/bits/types/struct_FILE.h
|
||||
@@ -103,15 +103,19 @@ struct _IO_FILE_complete
|
||||
void *_freeres_buf;
|
||||
size_t __pad5;
|
||||
int _mode;
|
||||
-#if __WORDSIZE == 64
|
||||
+#ifdef _LIBC
|
||||
+# if __WORDSIZE == 64
|
||||
int _unused3;
|
||||
-#endif
|
||||
+# endif
|
||||
__uint64_t _total_written;
|
||||
-#if __WORDSIZE == 32
|
||||
+# if __WORDSIZE == 32
|
||||
int _unused3;
|
||||
-#endif
|
||||
+# endif
|
||||
/* Make sure we don't get into trouble again. */
|
||||
char _unused2[12 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
|
||||
+#else
|
||||
+ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
|
||||
+#endif
|
||||
};
|
||||
|
||||
/* These macros are used by bits/stdio.h and internal headers. */
|
15
glibc.spec
15
glibc.spec
@ -157,7 +157,7 @@ end \
|
||||
Summary: The GNU libc libraries
|
||||
Name: glibc
|
||||
Version: %{glibcversion}
|
||||
Release: 174%{?dist}
|
||||
Release: 175%{?dist}
|
||||
|
||||
# In general, GPLv2+ is used by programs, LGPLv2+ is used for
|
||||
# libraries.
|
||||
@ -1130,6 +1130,16 @@ Patch822: glibc-RHEL-65280-4.patch
|
||||
Patch823: glibc-RHEL-65280-5.patch
|
||||
Patch824: glibc-RHEL-65280-6.patch
|
||||
Patch825: glibc-RHEL-65280-7.patch
|
||||
Patch826: glibc-RHEL-55471-1.patch
|
||||
Patch827: glibc-RHEL-55471-2.patch
|
||||
Patch828: glibc-RHEL-55471-3.patch
|
||||
Patch829: glibc-RHEL-55471-4.patch
|
||||
Patch830: glibc-RHEL-55471-5.patch
|
||||
Patch831: glibc-RHEL-55471-6.patch
|
||||
Patch832: glibc-RHEL-55471-7.patch
|
||||
Patch833: glibc-RHEL-55471-8.patch
|
||||
Patch834: glibc-RHEL-55471-9.patch
|
||||
Patch835: glibc-RHEL-55471-10.patch
|
||||
|
||||
##############################################################################
|
||||
# Continued list of core "glibc" package information:
|
||||
@ -3123,6 +3133,9 @@ update_gconv_modules_cache ()
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Mon Mar 10 2025 Tulio Magno Quites Machado Filho <tuliom@redhat.com> - 2.34-175
|
||||
- Backport fwrite tests and a fix for BZ 29459 (RHEL-55471)
|
||||
|
||||
* Fri Mar 07 2025 Arjun Shankar <arjun@redhat.com> - 2.34-174
|
||||
- nptl: Keep __rseq_size consistent (RHEL-65280)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user