diff --git a/glibc-RHEL-55471-1.patch b/glibc-RHEL-55471-1.patch new file mode 100644 index 0000000..d38927c --- /dev/null +++ b/glibc-RHEL-55471-1.patch @@ -0,0 +1,94 @@ +commit 5d4ab106d4cf7d6e410d6fc3d460b090c9108682 +Author: Tulio Magno Quites Machado Filho +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 + +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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* 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 diff --git a/glibc-RHEL-55471-10.patch b/glibc-RHEL-55471-10.patch new file mode 100644 index 0000000..112aae2 --- /dev/null +++ b/glibc-RHEL-55471-10.patch @@ -0,0 +1,39 @@ +commit 4734d0f8adde573aeafe79ad0c71807833db1cae +Author: Stefan Liebler +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; + diff --git a/glibc-RHEL-55471-2.patch b/glibc-RHEL-55471-2.patch new file mode 100644 index 0000000..e8483c1 --- /dev/null +++ b/glibc-RHEL-55471-2.patch @@ -0,0 +1,205 @@ +commit dccc9a5161264d2f98411c24ae22495ca3a09b60 +Author: Tulio Magno Quites Machado Filho +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 + +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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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 diff --git a/glibc-RHEL-55471-3.patch b/glibc-RHEL-55471-3.patch new file mode 100644 index 0000000..4c4c609 --- /dev/null +++ b/glibc-RHEL-55471-3.patch @@ -0,0 +1,166 @@ +commit 97aa92263a151d12286d27d327edc35475fe521c +Author: Tulio Magno Quites Machado Filho +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 + +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 ++ . */ ++ ++/* stdio.h provides BUFSIZ, which is the size of fwrite's internal buffer. */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* 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 diff --git a/glibc-RHEL-55471-4.patch b/glibc-RHEL-55471-4.patch new file mode 100644 index 0000000..f988bf6 --- /dev/null +++ b/glibc-RHEL-55471-4.patch @@ -0,0 +1,445 @@ +commit 596a61cf6b51ce2d58b8ca4e1d1f4fdfe1440dbc +Author: Tulio Magno Quites Machado Filho +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 + Reviewed-by: DJ Delorie + +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 ++ . */ ++ ++/* 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* 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 ++# . ++ ++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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* 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 diff --git a/glibc-RHEL-55471-5.patch b/glibc-RHEL-55471-5.patch new file mode 100644 index 0000000..9e1b43c --- /dev/null +++ b/glibc-RHEL-55471-5.patch @@ -0,0 +1,262 @@ +commit 1515f74fd81035a79861cd9fa12053fa9450ec65 +Author: Tulio Magno Quites Machado Filho +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 + +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 ++ . */ ++ ++#include ++/* stdio.h provides BUFSIZ, which is the size of fwrite's internal buffer. */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* 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 diff --git a/glibc-RHEL-55471-6.patch b/glibc-RHEL-55471-6.patch new file mode 100644 index 0000000..f118179 --- /dev/null +++ b/glibc-RHEL-55471-6.patch @@ -0,0 +1,39 @@ +commit cdb0800022110bc68a033944f09e501be5bd72d7 +Author: Tulio Magno Quites Machado Filho +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 + Reviewed-by: Arjun Shankar + +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 ++#include + + 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. */ diff --git a/glibc-RHEL-55471-7.patch b/glibc-RHEL-55471-7.patch new file mode 100644 index 0000000..987ba1f --- /dev/null +++ b/glibc-RHEL-55471-7.patch @@ -0,0 +1,37 @@ +commit 88f7ef881d1b9507aa934104c338b958c37821d7 +Author: Tulio Magno Quites Machado Filho +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 + Reviewed-by: Arjun Shankar + +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 diff --git a/glibc-RHEL-55471-8.patch b/glibc-RHEL-55471-8.patch new file mode 100644 index 0000000..5224677 --- /dev/null +++ b/glibc-RHEL-55471-8.patch @@ -0,0 +1,34 @@ +commit 1b6f868625403d6b7683af840e87d2b18d5d7731 +Author: Tulio Magno Quites Machado Filho +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 + +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. */ diff --git a/glibc-RHEL-55471-9.patch b/glibc-RHEL-55471-9.patch new file mode 100644 index 0000000..46eaf5e --- /dev/null +++ b/glibc-RHEL-55471-9.patch @@ -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. */ diff --git a/glibc.spec b/glibc.spec index 0b9be48..0bc6036 100644 --- a/glibc.spec +++ b/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 - 2.34-175 +- Backport fwrite tests and a fix for BZ 29459 (RHEL-55471) + * Fri Mar 07 2025 Arjun Shankar - 2.34-174 - nptl: Keep __rseq_size consistent (RHEL-65280)