Support concurrent calls to exit
And lock all stdio streams during exit Resolves: RHEL-65358
This commit is contained in:
parent
bd7cb93842
commit
19df5781cb
106
glibc-RHEL-65358-1.patch
Normal file
106
glibc-RHEL-65358-1.patch
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
commit 047703fbb88eb38fbe973f3abedb279382f181d0
|
||||||
|
Author: Florian Weimer <fweimer@redhat.com>
|
||||||
|
Date: Tue Jun 6 11:37:30 2023 +0200
|
||||||
|
|
||||||
|
support: Add delayed__exit (with two underscores)
|
||||||
|
|
||||||
|
It calls _exit instead of exit once the timeout expires.
|
||||||
|
|
||||||
|
Conflicts:
|
||||||
|
support/delayed_exit.c (fixup context)
|
||||||
|
support/xthread.h (fixup context)
|
||||||
|
|
||||||
|
diff --git a/support/delayed_exit.c b/support/delayed_exit.c
|
||||||
|
index 450860c5953257be..9242d4a1236e94ee 100644
|
||||||
|
--- a/support/delayed_exit.c
|
||||||
|
+++ b/support/delayed_exit.c
|
||||||
|
@@ -23,33 +23,58 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
+#include <support/support.h>
|
||||||
|
#include <time.h>
|
||||||
|
+#include <unistd.h>
|
||||||
|
+
|
||||||
|
+struct delayed_exit_request
|
||||||
|
+{
|
||||||
|
+ void (*exitfunc) (int);
|
||||||
|
+ int seconds;
|
||||||
|
+};
|
||||||
|
|
||||||
|
static void *
|
||||||
|
-delayed_exit_thread (void *seconds_as_ptr)
|
||||||
|
+delayed_exit_thread (void *closure)
|
||||||
|
{
|
||||||
|
- int seconds = (uintptr_t) seconds_as_ptr;
|
||||||
|
- struct timespec delay = { seconds, 0 };
|
||||||
|
+ struct delayed_exit_request *request = closure;
|
||||||
|
+ void (*exitfunc) (int) = request->exitfunc;
|
||||||
|
+ struct timespec delay = { request->seconds, 0 };
|
||||||
|
struct timespec remaining = { 0 };
|
||||||
|
+ free (request);
|
||||||
|
+
|
||||||
|
if (nanosleep (&delay, &remaining) != 0)
|
||||||
|
FAIL_EXIT1 ("nanosleep: %m");
|
||||||
|
- /* Exit the process sucessfully. */
|
||||||
|
- exit (0);
|
||||||
|
+ /* Exit the process successfully. */
|
||||||
|
+ exitfunc (0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
-void
|
||||||
|
-delayed_exit (int seconds)
|
||||||
|
+static void
|
||||||
|
+delayed_exit_1 (int seconds, void (*exitfunc) (int))
|
||||||
|
{
|
||||||
|
/* Create the new thread with all signals blocked. */
|
||||||
|
sigset_t all_blocked;
|
||||||
|
sigfillset (&all_blocked);
|
||||||
|
sigset_t old_set;
|
||||||
|
xpthread_sigmask (SIG_SETMASK, &all_blocked, &old_set);
|
||||||
|
+ struct delayed_exit_request *request = xmalloc (sizeof (*request));
|
||||||
|
+ request->seconds = seconds;
|
||||||
|
+ request->exitfunc = exitfunc;
|
||||||
|
/* Create a detached thread. */
|
||||||
|
- pthread_t thr = xpthread_create
|
||||||
|
- (NULL, delayed_exit_thread, (void *) (uintptr_t) seconds);
|
||||||
|
+ pthread_t thr = xpthread_create (NULL, delayed_exit_thread, request);
|
||||||
|
xpthread_detach (thr);
|
||||||
|
/* Restore the original signal mask. */
|
||||||
|
xpthread_sigmask (SIG_SETMASK, &old_set, NULL);
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+void
|
||||||
|
+delayed_exit (int seconds)
|
||||||
|
+{
|
||||||
|
+ delayed_exit_1 (seconds, exit);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void
|
||||||
|
+delayed__exit (int seconds)
|
||||||
|
+{
|
||||||
|
+ delayed_exit_1 (seconds, _exit);
|
||||||
|
+}
|
||||||
|
diff --git a/support/xthread.h b/support/xthread.h
|
||||||
|
index 1a39b1c0ddda9725..5c6b57e8829a4ee9 100644
|
||||||
|
--- a/support/xthread.h
|
||||||
|
+++ b/support/xthread.h
|
||||||
|
@@ -24,11 +24,14 @@
|
||||||
|
|
||||||
|
__BEGIN_DECLS
|
||||||
|
|
||||||
|
-/* Terminate the process (with exit status 0) after SECONDS have
|
||||||
|
- elapsed, from a helper thread. The process is terminated with the
|
||||||
|
- exit function, so atexit handlers are executed. */
|
||||||
|
+/* Terminate the process (with exit (0)) after SECONDS have elapsed,
|
||||||
|
+ from a helper thread. The process is terminated with the exit
|
||||||
|
+ function, so atexit handlers are executed. */
|
||||||
|
void delayed_exit (int seconds);
|
||||||
|
|
||||||
|
+/* Like delayed_exit, but use _exit (0). */
|
||||||
|
+void delayed__exit (int seconds);
|
||||||
|
+
|
||||||
|
/* Terminate the process (with exit status 1) if VALUE is not zero.
|
||||||
|
In that case, print a failure message to standard output mentioning
|
||||||
|
FUNCTION. The process is terminated with the exit function, so
|
23
glibc-RHEL-65358-2.patch
Normal file
23
glibc-RHEL-65358-2.patch
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
commit 7d421209287a07db5e926552ae5fbe9d8abb50dc
|
||||||
|
Author: Florian Weimer <fweimer@redhat.com>
|
||||||
|
Date: Tue Jun 6 11:39:06 2023 +0200
|
||||||
|
|
||||||
|
pthreads: Use _exit to terminate the tst-stdio1 test
|
||||||
|
|
||||||
|
Previously, the exit function was used, but this causes the test to
|
||||||
|
block (until the timeout) once exit is changed to lock stdio streams
|
||||||
|
during flush.
|
||||||
|
|
||||||
|
diff --git a/sysdeps/pthread/tst-stdio1.c b/sysdeps/pthread/tst-stdio1.c
|
||||||
|
index 80fb59c4e42ca550..a2cc71d67f0761f6 100644
|
||||||
|
--- a/sysdeps/pthread/tst-stdio1.c
|
||||||
|
+++ b/sysdeps/pthread/tst-stdio1.c
|
||||||
|
@@ -47,7 +47,7 @@ do_test (void)
|
||||||
|
_exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
- delayed_exit (1);
|
||||||
|
+ delayed__exit (1);
|
||||||
|
xpthread_join (th);
|
||||||
|
|
||||||
|
puts ("join returned");
|
131
glibc-RHEL-65358-3.patch
Normal file
131
glibc-RHEL-65358-3.patch
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
commit af130d27099651e0d27b2cf2cfb44dafd6fe8a26
|
||||||
|
Author: Andreas Schwab <schwab@suse.de>
|
||||||
|
Date: Tue Jan 30 10:16:00 2018 +0100
|
||||||
|
|
||||||
|
Always do locking when accessing streams (bug 15142, bug 14697)
|
||||||
|
|
||||||
|
Now that abort no longer calls fflush there is no reason to avoid locking
|
||||||
|
the stdio streams anywhere. This fixes a conformance issue and potential
|
||||||
|
heap corruption during exit.
|
||||||
|
|
||||||
|
diff --git a/libio/genops.c b/libio/genops.c
|
||||||
|
index b964c50657d7fbe9..a82c1b96767e14e0 100644
|
||||||
|
--- a/libio/genops.c
|
||||||
|
+++ b/libio/genops.c
|
||||||
|
@@ -683,7 +683,7 @@ _IO_adjust_column (unsigned start, const char *line, int count)
|
||||||
|
libc_hidden_def (_IO_adjust_column)
|
||||||
|
|
||||||
|
int
|
||||||
|
-_IO_flush_all_lockp (int do_lock)
|
||||||
|
+_IO_flush_all (void)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
FILE *fp;
|
||||||
|
@@ -696,8 +696,7 @@ _IO_flush_all_lockp (int do_lock)
|
||||||
|
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
|
||||||
|
{
|
||||||
|
run_fp = fp;
|
||||||
|
- if (do_lock)
|
||||||
|
- _IO_flockfile (fp);
|
||||||
|
+ _IO_flockfile (fp);
|
||||||
|
|
||||||
|
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|
||||||
|
|| (_IO_vtable_offset (fp) == 0
|
||||||
|
@@ -707,8 +706,7 @@ _IO_flush_all_lockp (int do_lock)
|
||||||
|
&& _IO_OVERFLOW (fp, EOF) == EOF)
|
||||||
|
result = EOF;
|
||||||
|
|
||||||
|
- if (do_lock)
|
||||||
|
- _IO_funlockfile (fp);
|
||||||
|
+ _IO_funlockfile (fp);
|
||||||
|
run_fp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -719,14 +717,6 @@ _IO_flush_all_lockp (int do_lock)
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-int
|
||||||
|
-_IO_flush_all (void)
|
||||||
|
-{
|
||||||
|
- /* We want locking. */
|
||||||
|
- return _IO_flush_all_lockp (1);
|
||||||
|
-}
|
||||||
|
libc_hidden_def (_IO_flush_all)
|
||||||
|
|
||||||
|
void
|
||||||
|
@@ -792,6 +782,9 @@ _IO_unbuffer_all (void)
|
||||||
|
{
|
||||||
|
int legacy = 0;
|
||||||
|
|
||||||
|
+ run_fp = fp;
|
||||||
|
+ _IO_flockfile (fp);
|
||||||
|
+
|
||||||
|
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
|
||||||
|
if (__glibc_unlikely (_IO_vtable_offset (fp) != 0))
|
||||||
|
legacy = 1;
|
||||||
|
@@ -807,18 +800,6 @@ _IO_unbuffer_all (void)
|
||||||
|
/* Iff stream is un-orientated, it wasn't used. */
|
||||||
|
&& (legacy || fp->_mode != 0))
|
||||||
|
{
|
||||||
|
-#ifdef _IO_MTSAFE_IO
|
||||||
|
- int cnt;
|
||||||
|
-#define MAXTRIES 2
|
||||||
|
- for (cnt = 0; cnt < MAXTRIES; ++cnt)
|
||||||
|
- if (fp->_lock == NULL || _IO_lock_trylock (*fp->_lock) == 0)
|
||||||
|
- break;
|
||||||
|
- else
|
||||||
|
- /* Give the other thread time to finish up its use of the
|
||||||
|
- stream. */
|
||||||
|
- __sched_yield ();
|
||||||
|
-#endif
|
||||||
|
-
|
||||||
|
if (! legacy && ! dealloc_buffers && !(fp->_flags & _IO_USER_BUF))
|
||||||
|
{
|
||||||
|
fp->_flags |= _IO_USER_BUF;
|
||||||
|
@@ -832,17 +813,15 @@ _IO_unbuffer_all (void)
|
||||||
|
|
||||||
|
if (! legacy && fp->_mode > 0)
|
||||||
|
_IO_wsetb (fp, NULL, NULL, 0);
|
||||||
|
-
|
||||||
|
-#ifdef _IO_MTSAFE_IO
|
||||||
|
- if (cnt < MAXTRIES && fp->_lock != NULL)
|
||||||
|
- _IO_lock_unlock (*fp->_lock);
|
||||||
|
-#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure that never again the wide char functions can be
|
||||||
|
used. */
|
||||||
|
if (! legacy)
|
||||||
|
fp->_mode = -1;
|
||||||
|
+
|
||||||
|
+ _IO_funlockfile (fp);
|
||||||
|
+ run_fp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _IO_MTSAFE_IO
|
||||||
|
@@ -868,9 +847,7 @@ libc_freeres_fn (buffer_free)
|
||||||
|
int
|
||||||
|
_IO_cleanup (void)
|
||||||
|
{
|
||||||
|
- /* We do *not* want locking. Some threads might use streams but
|
||||||
|
- that is their problem, we flush them underneath them. */
|
||||||
|
- int result = _IO_flush_all_lockp (0);
|
||||||
|
+ int result = _IO_flush_all ();
|
||||||
|
|
||||||
|
/* We currently don't have a reliable mechanism for making sure that
|
||||||
|
C++ static destructors are executed in the correct order.
|
||||||
|
diff --git a/libio/libioP.h b/libio/libioP.h
|
||||||
|
index 811e9c919bbc2ce1..fbe58fc10fb694d0 100644
|
||||||
|
--- a/libio/libioP.h
|
||||||
|
+++ b/libio/libioP.h
|
||||||
|
@@ -488,7 +488,6 @@ extern int _IO_new_do_write (FILE *, const char *, size_t);
|
||||||
|
extern int _IO_old_do_write (FILE *, const char *, size_t);
|
||||||
|
extern int _IO_wdo_write (FILE *, const wchar_t *, size_t);
|
||||||
|
libc_hidden_proto (_IO_wdo_write)
|
||||||
|
-extern int _IO_flush_all_lockp (int);
|
||||||
|
extern int _IO_flush_all (void);
|
||||||
|
libc_hidden_proto (_IO_flush_all)
|
||||||
|
extern int _IO_cleanup (void);
|
230
glibc-RHEL-65358-4.patch
Normal file
230
glibc-RHEL-65358-4.patch
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
commit f6ba993e0cda0ca5554fd47b00e6a87be5fdf05e
|
||||||
|
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||||||
|
Date: Thu Jul 25 15:41:44 2024 -0300
|
||||||
|
|
||||||
|
stdlib: Allow concurrent exit (BZ 31997)
|
||||||
|
|
||||||
|
Even if C/POSIX standard states that exit is not formally thread-unsafe,
|
||||||
|
calling it more than once is UB. The glibc already supports
|
||||||
|
it for the single-thread, and both elf/nodelete2.c and tst-rseq-disable.c
|
||||||
|
call exit from a DSO destructor (which is called by _dl_fini, registered
|
||||||
|
at program startup with __cxa_atexit).
|
||||||
|
|
||||||
|
However, there are still race issues when it is called more than once
|
||||||
|
concurrently by multiple threads. A recent Rust PR triggered this
|
||||||
|
issue [1], which resulted in an Austin Group ask for clarification [2].
|
||||||
|
Besides it, there is a discussion to make concurrent calling not UB [3],
|
||||||
|
wtih a defined semantic where any remaining callers block until the first
|
||||||
|
call to exit has finished (reentrant calls, leaving through longjmp, and
|
||||||
|
exceptions are still undefined).
|
||||||
|
|
||||||
|
For glibc, at least reentrant calls are required to be supported to avoid
|
||||||
|
changing the current behaviour. This requires locking using a recursive
|
||||||
|
lock, where any exit called by atexit() handlers resumes at the point of
|
||||||
|
the current handler (thus avoiding calling the current handle multiple
|
||||||
|
times).
|
||||||
|
|
||||||
|
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
|
||||||
|
|
||||||
|
[1] https://github.com/rust-lang/rust/issues/126600
|
||||||
|
[2] https://austingroupbugs.net/view.php?id=1845
|
||||||
|
[3] https://www.openwall.com/lists/libc-coord/2024/07/24/4
|
||||||
|
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||||
|
|
||||||
|
diff --git a/stdlib/Makefile b/stdlib/Makefile
|
||||||
|
index 603a330b1e8f1ba2..865d804ef2642cb5 100644
|
||||||
|
--- a/stdlib/Makefile
|
||||||
|
+++ b/stdlib/Makefile
|
||||||
|
@@ -93,6 +93,7 @@ tests := \
|
||||||
|
tst-bsearch \
|
||||||
|
tst-bz20544 \
|
||||||
|
tst-canon-bz26341 \
|
||||||
|
+ tst-concurrent-exit \
|
||||||
|
tst-cxa_atexit \
|
||||||
|
tst-environ \
|
||||||
|
tst-getenv-signal \
|
||||||
|
diff --git a/stdlib/exit.c b/stdlib/exit.c
|
||||||
|
index 546343f7d4b74773..7d536098623d47ff 100644
|
||||||
|
--- a/stdlib/exit.c
|
||||||
|
+++ b/stdlib/exit.c
|
||||||
|
@@ -140,9 +140,17 @@ __run_exit_handlers (int status, struct exit_function_list **listp,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
+/* The lock handles concurrent exit(), even though the C/POSIX standard states
|
||||||
|
+ that calling exit() more than once is UB. The recursive lock allows
|
||||||
|
+ atexit() handlers or destructors to call exit() itself. In this case, the
|
||||||
|
+ handler list execution will resume at the point of the current handler. */
|
||||||
|
+__libc_lock_define_initialized_recursive (static, __exit_lock)
|
||||||
|
+
|
||||||
|
void
|
||||||
|
exit (int status)
|
||||||
|
{
|
||||||
|
+ /* The exit should never return, so there is no need to unlock it. */
|
||||||
|
+ __libc_lock_lock_recursive (__exit_lock);
|
||||||
|
__run_exit_handlers (status, &__exit_funcs, true, true);
|
||||||
|
}
|
||||||
|
libc_hidden_def (exit)
|
||||||
|
diff --git a/stdlib/tst-concurrent-exit.c b/stdlib/tst-concurrent-exit.c
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000000000..1141130f87fde20f
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/stdlib/tst-concurrent-exit.c
|
||||||
|
@@ -0,0 +1,157 @@
|
||||||
|
+/* Check if exit can be called concurrently by multiple threads.
|
||||||
|
+ 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 <array_length.h>
|
||||||
|
+#include <stdlib.h>
|
||||||
|
+#include <support/check.h>
|
||||||
|
+#include <support/xthread.h>
|
||||||
|
+#include <stdio.h>
|
||||||
|
+#include <support/xunistd.h>
|
||||||
|
+#include <string.h>
|
||||||
|
+
|
||||||
|
+#define MAX_atexit 32
|
||||||
|
+
|
||||||
|
+static pthread_barrier_t barrier;
|
||||||
|
+
|
||||||
|
+static void *
|
||||||
|
+tf (void *closure)
|
||||||
|
+{
|
||||||
|
+ xpthread_barrier_wait (&barrier);
|
||||||
|
+ exit (0);
|
||||||
|
+
|
||||||
|
+ return NULL;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static const char expected[] = "00000000000000000000000003021121130211";
|
||||||
|
+static char crumbs[sizeof (expected)];
|
||||||
|
+static int next_slot = 0;
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+exit_with_flush (int code)
|
||||||
|
+{
|
||||||
|
+ fflush (stdout);
|
||||||
|
+ /* glibc allows recursive exit, the atexit handlers execution will be
|
||||||
|
+ resumed from the where the previous exit was interrupted. */
|
||||||
|
+ exit (code);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/* Take some time, so another thread potentially issue exit. */
|
||||||
|
+#define SETUP_NANOSLEEP \
|
||||||
|
+ if (nanosleep (&(struct timespec) { .tv_sec = 0, .tv_nsec = 1000L }, \
|
||||||
|
+ NULL) != 0) \
|
||||||
|
+ FAIL_EXIT1 ("nanosleep: %m")
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn0 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '0';
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn1 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '1';
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn2 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '2';
|
||||||
|
+ atexit (fn1);
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn3 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '3';
|
||||||
|
+ atexit (fn2);
|
||||||
|
+ atexit (fn0);
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn_final (void)
|
||||||
|
+{
|
||||||
|
+ TEST_COMPARE_STRING (crumbs, expected);
|
||||||
|
+ exit_with_flush (0);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+_Noreturn static void
|
||||||
|
+child (void)
|
||||||
|
+{
|
||||||
|
+ enum { nthreads = 8 };
|
||||||
|
+
|
||||||
|
+ xpthread_barrier_init (&barrier, NULL, nthreads + 1);
|
||||||
|
+
|
||||||
|
+ pthread_t thr[nthreads];
|
||||||
|
+ for (int i = 0; i < nthreads; i++)
|
||||||
|
+ thr[i] = xpthread_create (NULL, tf, NULL);
|
||||||
|
+
|
||||||
|
+ xpthread_barrier_wait (&barrier);
|
||||||
|
+
|
||||||
|
+ for (int i = 0; i < nthreads; i++)
|
||||||
|
+ {
|
||||||
|
+ pthread_join (thr[i], NULL);
|
||||||
|
+ /* It should not be reached, it means that thread did not exit for
|
||||||
|
+ some reason. */
|
||||||
|
+ support_record_failure ();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ exit (2);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int
|
||||||
|
+do_test (void)
|
||||||
|
+{
|
||||||
|
+ /* Register a large number of handler that will trigger a heap allocation
|
||||||
|
+ for the handle state. On exit, each block will be freed after the
|
||||||
|
+ handle is processed. */
|
||||||
|
+ int slots_remaining = MAX_atexit;
|
||||||
|
+
|
||||||
|
+ /* Register this first so it can verify expected order of the rest. */
|
||||||
|
+ atexit (fn_final); --slots_remaining;
|
||||||
|
+
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn2) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||||
|
+
|
||||||
|
+ while (slots_remaining > 0)
|
||||||
|
+ {
|
||||||
|
+ TEST_VERIFY_EXIT (atexit (fn0) == 0); --slots_remaining;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pid_t pid = xfork ();
|
||||||
|
+ if (pid != 0)
|
||||||
|
+ {
|
||||||
|
+ int status;
|
||||||
|
+ xwaitpid (pid, &status, 0);
|
||||||
|
+ TEST_VERIFY (WIFEXITED (status));
|
||||||
|
+ }
|
||||||
|
+ else
|
||||||
|
+ child ();
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#include <support/test-driver.c>
|
427
glibc-RHEL-65358-5.patch
Normal file
427
glibc-RHEL-65358-5.patch
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
commit c6af8a9a3ce137a9704825d173be22a2b2d9cb49
|
||||||
|
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||||||
|
Date: Mon Aug 5 11:27:35 2024 -0300
|
||||||
|
|
||||||
|
stdlib: Allow concurrent quick_exit (BZ 31997)
|
||||||
|
|
||||||
|
As for exit, also allows concurrent quick_exit to avoid race
|
||||||
|
conditions when it is called concurrently. Since it uses the same
|
||||||
|
internal function as exit, the __exit_lock lock is moved to
|
||||||
|
__run_exit_handlers. It also solved a potential concurrent when
|
||||||
|
calling exit and quick_exit concurrently.
|
||||||
|
|
||||||
|
The test case 'expected' is expanded to a value larger than the
|
||||||
|
minimum required by C/POSIX (32 entries) so at_quick_exit() will
|
||||||
|
require libc to allocate a new block. This makes the test mre likely to
|
||||||
|
trigger concurrent issues (through free() at __run_exit_handlers)
|
||||||
|
if quick_exit() interacts with the at_quick_exit list concurrently.
|
||||||
|
|
||||||
|
This is also the latest interpretation of the Austin Ticket [1].
|
||||||
|
|
||||||
|
Checked on x86_64-linux-gnu.
|
||||||
|
|
||||||
|
[1] https://austingroupbugs.net/view.php?id=1845
|
||||||
|
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
||||||
|
|
||||||
|
diff --git a/stdlib/Makefile b/stdlib/Makefile
|
||||||
|
index 865d804ef2642cb5..4cbf47d215353681 100644
|
||||||
|
--- a/stdlib/Makefile
|
||||||
|
+++ b/stdlib/Makefile
|
||||||
|
@@ -94,6 +94,7 @@ tests := \
|
||||||
|
tst-bz20544 \
|
||||||
|
tst-canon-bz26341 \
|
||||||
|
tst-concurrent-exit \
|
||||||
|
+ tst-concurrent-quick_exit \
|
||||||
|
tst-cxa_atexit \
|
||||||
|
tst-environ \
|
||||||
|
tst-getenv-signal \
|
||||||
|
diff --git a/stdlib/exit.c b/stdlib/exit.c
|
||||||
|
index 7d536098623d47ff..1719f88c7aca5397 100644
|
||||||
|
--- a/stdlib/exit.c
|
||||||
|
+++ b/stdlib/exit.c
|
||||||
|
@@ -30,6 +30,13 @@ DEFINE_HOOK (__libc_atexit, (void))
|
||||||
|
__exit_funcs_lock is declared. */
|
||||||
|
bool __exit_funcs_done = false;
|
||||||
|
|
||||||
|
+/* The lock handles concurrent exit() and quick_exit(), even though the
|
||||||
|
+ C/POSIX standard states that calling exit() more than once is UB. The
|
||||||
|
+ recursive lock allows atexit() handlers or destructors to call exit()
|
||||||
|
+ itself. In this case, the handler list execution will resume at the
|
||||||
|
+ point of the current handler. */
|
||||||
|
+__libc_lock_define_initialized_recursive (static, __exit_lock)
|
||||||
|
+
|
||||||
|
/* Call all functions registered with `atexit' and `on_exit',
|
||||||
|
in the reverse of the order in which they were registered
|
||||||
|
perform stdio cleanup, and terminate program execution with STATUS. */
|
||||||
|
@@ -38,6 +45,9 @@ attribute_hidden
|
||||||
|
__run_exit_handlers (int status, struct exit_function_list **listp,
|
||||||
|
bool run_list_atexit, bool run_dtors)
|
||||||
|
{
|
||||||
|
+ /* The exit should never return, so there is no need to unlock it. */
|
||||||
|
+ __libc_lock_lock_recursive (__exit_lock);
|
||||||
|
+
|
||||||
|
/* First, call the TLS destructors. */
|
||||||
|
#ifndef SHARED
|
||||||
|
if (&__call_tls_dtors != NULL)
|
||||||
|
@@ -140,17 +150,9 @@ __run_exit_handlers (int status, struct exit_function_list **listp,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-/* The lock handles concurrent exit(), even though the C/POSIX standard states
|
||||||
|
- that calling exit() more than once is UB. The recursive lock allows
|
||||||
|
- atexit() handlers or destructors to call exit() itself. In this case, the
|
||||||
|
- handler list execution will resume at the point of the current handler. */
|
||||||
|
-__libc_lock_define_initialized_recursive (static, __exit_lock)
|
||||||
|
-
|
||||||
|
void
|
||||||
|
exit (int status)
|
||||||
|
{
|
||||||
|
- /* The exit should never return, so there is no need to unlock it. */
|
||||||
|
- __libc_lock_lock_recursive (__exit_lock);
|
||||||
|
__run_exit_handlers (status, &__exit_funcs, true, true);
|
||||||
|
}
|
||||||
|
libc_hidden_def (exit)
|
||||||
|
diff --git a/stdlib/tst-concurrent-exit-skeleton.c b/stdlib/tst-concurrent-exit-skeleton.c
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000000000..cfd5140466e1a730
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/stdlib/tst-concurrent-exit-skeleton.c
|
||||||
|
@@ -0,0 +1,160 @@
|
||||||
|
+/* Check if exit/quick_exit can be called concurrently by multiple threads.
|
||||||
|
+ 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 <array_length.h>
|
||||||
|
+#include <stdlib.h>
|
||||||
|
+#include <support/check.h>
|
||||||
|
+#include <support/xthread.h>
|
||||||
|
+#include <stdio.h>
|
||||||
|
+#include <support/xunistd.h>
|
||||||
|
+#include <string.h>
|
||||||
|
+
|
||||||
|
+/* A value larger than the minimum required by C/POSIX (32), to trigger a
|
||||||
|
+ new block memory allocation. */
|
||||||
|
+#define MAX_atexit 64
|
||||||
|
+
|
||||||
|
+static pthread_barrier_t barrier;
|
||||||
|
+
|
||||||
|
+static void *
|
||||||
|
+tf (void *closure)
|
||||||
|
+{
|
||||||
|
+ xpthread_barrier_wait (&barrier);
|
||||||
|
+ EXIT (0);
|
||||||
|
+
|
||||||
|
+ return NULL;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static const char expected[] = "00000000000000000000000000000000000"
|
||||||
|
+ "00000000000000000000003021121130211";
|
||||||
|
+static char crumbs[sizeof (expected)];
|
||||||
|
+static int next_slot = 0;
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+exit_with_flush (int code)
|
||||||
|
+{
|
||||||
|
+ fflush (stdout);
|
||||||
|
+ /* glibc allows recursive EXIT, the ATEXIT handlers execution will be
|
||||||
|
+ resumed from the where the previous EXIT was interrupted. */
|
||||||
|
+ EXIT (code);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/* Take some time, so another thread potentially issue EXIT. */
|
||||||
|
+#define SETUP_NANOSLEEP \
|
||||||
|
+ if (nanosleep (&(struct timespec) { .tv_sec = 0, .tv_nsec = 1000L }, \
|
||||||
|
+ NULL) != 0) \
|
||||||
|
+ FAIL_EXIT1 ("nanosleep: %m")
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn0 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '0';
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn1 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '1';
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn2 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '2';
|
||||||
|
+ ATEXIT (fn1);
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn3 (void)
|
||||||
|
+{
|
||||||
|
+ crumbs[next_slot++] = '3';
|
||||||
|
+ ATEXIT (fn2);
|
||||||
|
+ ATEXIT (fn0);
|
||||||
|
+ SETUP_NANOSLEEP;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void
|
||||||
|
+fn_final (void)
|
||||||
|
+{
|
||||||
|
+ TEST_COMPARE_STRING (crumbs, expected);
|
||||||
|
+ exit_with_flush (0);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+_Noreturn static void
|
||||||
|
+child (void)
|
||||||
|
+{
|
||||||
|
+ enum { nthreads = 8 };
|
||||||
|
+
|
||||||
|
+ xpthread_barrier_init (&barrier, NULL, nthreads + 1);
|
||||||
|
+
|
||||||
|
+ pthread_t thr[nthreads];
|
||||||
|
+ for (int i = 0; i < nthreads; i++)
|
||||||
|
+ thr[i] = xpthread_create (NULL, tf, NULL);
|
||||||
|
+
|
||||||
|
+ xpthread_barrier_wait (&barrier);
|
||||||
|
+
|
||||||
|
+ for (int i = 0; i < nthreads; i++)
|
||||||
|
+ {
|
||||||
|
+ pthread_join (thr[i], NULL);
|
||||||
|
+ /* It should not be reached, it means that thread did not exit for
|
||||||
|
+ some reason. */
|
||||||
|
+ support_record_failure ();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ EXIT (2);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int
|
||||||
|
+do_test (void)
|
||||||
|
+{
|
||||||
|
+ /* Register a large number of handler that will trigger a heap allocation
|
||||||
|
+ for the handle state. On EXIT, each block will be freed after the
|
||||||
|
+ handle is processed. */
|
||||||
|
+ int slots_remaining = MAX_atexit;
|
||||||
|
+
|
||||||
|
+ /* Register this first so it can verify expected order of the rest. */
|
||||||
|
+ ATEXIT (fn_final); --slots_remaining;
|
||||||
|
+
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn1) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn3) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn1) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn2) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn1) == 0); --slots_remaining;
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn3) == 0); --slots_remaining;
|
||||||
|
+
|
||||||
|
+ while (slots_remaining > 0)
|
||||||
|
+ {
|
||||||
|
+ TEST_VERIFY_EXIT (ATEXIT (fn0) == 0); --slots_remaining;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pid_t pid = xfork ();
|
||||||
|
+ if (pid != 0)
|
||||||
|
+ {
|
||||||
|
+ int status;
|
||||||
|
+ xwaitpid (pid, &status, 0);
|
||||||
|
+ TEST_VERIFY (WIFEXITED (status));
|
||||||
|
+ }
|
||||||
|
+ else
|
||||||
|
+ child ();
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#include <support/test-driver.c>
|
||||||
|
diff --git a/stdlib/tst-concurrent-exit.c b/stdlib/tst-concurrent-exit.c
|
||||||
|
index 1141130f87fde20f..421c39d63126246d 100644
|
||||||
|
--- a/stdlib/tst-concurrent-exit.c
|
||||||
|
+++ b/stdlib/tst-concurrent-exit.c
|
||||||
|
@@ -16,142 +16,7 @@
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
-#include <array_length.h>
|
||||||
|
-#include <stdlib.h>
|
||||||
|
-#include <support/check.h>
|
||||||
|
-#include <support/xthread.h>
|
||||||
|
-#include <stdio.h>
|
||||||
|
-#include <support/xunistd.h>
|
||||||
|
-#include <string.h>
|
||||||
|
+#define EXIT(__r) exit (__r)
|
||||||
|
+#define ATEXIT(__f) atexit (__f)
|
||||||
|
|
||||||
|
-#define MAX_atexit 32
|
||||||
|
-
|
||||||
|
-static pthread_barrier_t barrier;
|
||||||
|
-
|
||||||
|
-static void *
|
||||||
|
-tf (void *closure)
|
||||||
|
-{
|
||||||
|
- xpthread_barrier_wait (&barrier);
|
||||||
|
- exit (0);
|
||||||
|
-
|
||||||
|
- return NULL;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-static const char expected[] = "00000000000000000000000003021121130211";
|
||||||
|
-static char crumbs[sizeof (expected)];
|
||||||
|
-static int next_slot = 0;
|
||||||
|
-
|
||||||
|
-static void
|
||||||
|
-exit_with_flush (int code)
|
||||||
|
-{
|
||||||
|
- fflush (stdout);
|
||||||
|
- /* glibc allows recursive exit, the atexit handlers execution will be
|
||||||
|
- resumed from the where the previous exit was interrupted. */
|
||||||
|
- exit (code);
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-/* Take some time, so another thread potentially issue exit. */
|
||||||
|
-#define SETUP_NANOSLEEP \
|
||||||
|
- if (nanosleep (&(struct timespec) { .tv_sec = 0, .tv_nsec = 1000L }, \
|
||||||
|
- NULL) != 0) \
|
||||||
|
- FAIL_EXIT1 ("nanosleep: %m")
|
||||||
|
-
|
||||||
|
-static void
|
||||||
|
-fn0 (void)
|
||||||
|
-{
|
||||||
|
- crumbs[next_slot++] = '0';
|
||||||
|
- SETUP_NANOSLEEP;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-static void
|
||||||
|
-fn1 (void)
|
||||||
|
-{
|
||||||
|
- crumbs[next_slot++] = '1';
|
||||||
|
- SETUP_NANOSLEEP;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-static void
|
||||||
|
-fn2 (void)
|
||||||
|
-{
|
||||||
|
- crumbs[next_slot++] = '2';
|
||||||
|
- atexit (fn1);
|
||||||
|
- SETUP_NANOSLEEP;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-static void
|
||||||
|
-fn3 (void)
|
||||||
|
-{
|
||||||
|
- crumbs[next_slot++] = '3';
|
||||||
|
- atexit (fn2);
|
||||||
|
- atexit (fn0);
|
||||||
|
- SETUP_NANOSLEEP;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-static void
|
||||||
|
-fn_final (void)
|
||||||
|
-{
|
||||||
|
- TEST_COMPARE_STRING (crumbs, expected);
|
||||||
|
- exit_with_flush (0);
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-_Noreturn static void
|
||||||
|
-child (void)
|
||||||
|
-{
|
||||||
|
- enum { nthreads = 8 };
|
||||||
|
-
|
||||||
|
- xpthread_barrier_init (&barrier, NULL, nthreads + 1);
|
||||||
|
-
|
||||||
|
- pthread_t thr[nthreads];
|
||||||
|
- for (int i = 0; i < nthreads; i++)
|
||||||
|
- thr[i] = xpthread_create (NULL, tf, NULL);
|
||||||
|
-
|
||||||
|
- xpthread_barrier_wait (&barrier);
|
||||||
|
-
|
||||||
|
- for (int i = 0; i < nthreads; i++)
|
||||||
|
- {
|
||||||
|
- pthread_join (thr[i], NULL);
|
||||||
|
- /* It should not be reached, it means that thread did not exit for
|
||||||
|
- some reason. */
|
||||||
|
- support_record_failure ();
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- exit (2);
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-static int
|
||||||
|
-do_test (void)
|
||||||
|
-{
|
||||||
|
- /* Register a large number of handler that will trigger a heap allocation
|
||||||
|
- for the handle state. On exit, each block will be freed after the
|
||||||
|
- handle is processed. */
|
||||||
|
- int slots_remaining = MAX_atexit;
|
||||||
|
-
|
||||||
|
- /* Register this first so it can verify expected order of the rest. */
|
||||||
|
- atexit (fn_final); --slots_remaining;
|
||||||
|
-
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn2) == 0); --slots_remaining;
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn1) == 0); --slots_remaining;
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn3) == 0); --slots_remaining;
|
||||||
|
-
|
||||||
|
- while (slots_remaining > 0)
|
||||||
|
- {
|
||||||
|
- TEST_VERIFY_EXIT (atexit (fn0) == 0); --slots_remaining;
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- pid_t pid = xfork ();
|
||||||
|
- if (pid != 0)
|
||||||
|
- {
|
||||||
|
- int status;
|
||||||
|
- xwaitpid (pid, &status, 0);
|
||||||
|
- TEST_VERIFY (WIFEXITED (status));
|
||||||
|
- }
|
||||||
|
- else
|
||||||
|
- child ();
|
||||||
|
-
|
||||||
|
- return 0;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-#include <support/test-driver.c>
|
||||||
|
+#include "tst-concurrent-exit-skeleton.c"
|
||||||
|
diff --git a/stdlib/tst-concurrent-quick_exit.c b/stdlib/tst-concurrent-quick_exit.c
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000000000000..3f321668d6b8d536
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/stdlib/tst-concurrent-quick_exit.c
|
||||||
|
@@ -0,0 +1,22 @@
|
||||||
|
+/* Check if quick_exit can be called concurrently by multiple threads.
|
||||||
|
+ 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/>. */
|
||||||
|
+
|
||||||
|
+#define EXIT(__r) quick_exit (__r)
|
||||||
|
+#define ATEXIT(__f) at_quick_exit (__f)
|
||||||
|
+
|
||||||
|
+#include "tst-concurrent-exit-skeleton.c"
|
11
glibc.spec
11
glibc.spec
@ -157,7 +157,7 @@ end \
|
|||||||
Summary: The GNU libc libraries
|
Summary: The GNU libc libraries
|
||||||
Name: glibc
|
Name: glibc
|
||||||
Version: %{glibcversion}
|
Version: %{glibcversion}
|
||||||
Release: 150%{?dist}
|
Release: 151%{?dist}
|
||||||
|
|
||||||
# In general, GPLv2+ is used by programs, LGPLv2+ is used for
|
# In general, GPLv2+ is used by programs, LGPLv2+ is used for
|
||||||
# libraries.
|
# libraries.
|
||||||
@ -1062,6 +1062,11 @@ Patch754: glibc-RHEL-56542-6.patch
|
|||||||
Patch755: glibc-RHEL-56542-7.patch
|
Patch755: glibc-RHEL-56542-7.patch
|
||||||
Patch756: glibc-RHEL-56542-8.patch
|
Patch756: glibc-RHEL-56542-8.patch
|
||||||
Patch757: glibc-RHEL-56542-9.patch
|
Patch757: glibc-RHEL-56542-9.patch
|
||||||
|
Patch758: glibc-RHEL-65358-1.patch
|
||||||
|
Patch759: glibc-RHEL-65358-2.patch
|
||||||
|
Patch760: glibc-RHEL-65358-3.patch
|
||||||
|
Patch761: glibc-RHEL-65358-4.patch
|
||||||
|
Patch762: glibc-RHEL-65358-5.patch
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Continued list of core "glibc" package information:
|
# Continued list of core "glibc" package information:
|
||||||
@ -3055,6 +3060,10 @@ update_gconv_modules_cache ()
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Fri Jan 10 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-151
|
||||||
|
- Lock all stdio streams during exit
|
||||||
|
- Support concurrent calls to exit (RHEL-65358)
|
||||||
|
|
||||||
* Fri Jan 10 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-150
|
* Fri Jan 10 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-150
|
||||||
- Backport test implementation to verify readdir behavior (RHEL-56542)
|
- Backport test implementation to verify readdir behavior (RHEL-56542)
|
||||||
- Backport the deallocation attributes commit for opendir/fdopendir (RHEL-56543)
|
- Backport the deallocation attributes commit for opendir/fdopendir (RHEL-56543)
|
||||||
|
Loading…
Reference in New Issue
Block a user