You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
768 lines
24 KiB
768 lines
24 KiB
2 years ago
|
This patch is a RHEL-8.7 backport of the following upstream commit:
|
||
|
|
||
|
commit 52a103e237329b9f88a28513fe7506ffc3bd8ced
|
||
|
Author: Arjun Shankar <arjun@redhat.com>
|
||
|
Date: Tue May 24 17:57:36 2022 +0200
|
||
|
|
||
|
Fix deadlock when pthread_atfork handler calls pthread_atfork or dlclose
|
||
|
|
||
|
In multi-threaded programs, registering via pthread_atfork,
|
||
|
de-registering implicitly via dlclose, or running pthread_atfork
|
||
|
handlers during fork was protected by an internal lock. This meant
|
||
|
that a pthread_atfork handler attempting to register another handler or
|
||
|
dlclose a dynamically loaded library would lead to a deadlock.
|
||
|
|
||
|
This commit fixes the deadlock in the following way:
|
||
|
|
||
|
During the execution of handlers at fork time, the atfork lock is
|
||
|
released prior to the execution of each handler and taken again upon its
|
||
|
return. Any handler registrations or de-registrations that occurred
|
||
|
during the execution of the handler are accounted for before proceeding
|
||
|
with further handler execution.
|
||
|
|
||
|
If a handler that hasn't been executed yet gets de-registered by another
|
||
|
handler during fork, it will not be executed. If a handler gets
|
||
|
registered by another handler during fork, it will not be executed
|
||
|
during that particular fork.
|
||
|
|
||
|
The possibility that handlers may now be registered or deregistered
|
||
|
during handler execution means that identifying the next handler to be
|
||
|
run after a given handler may register/de-register others requires some
|
||
|
bookkeeping. The fork_handler struct has an additional field, 'id',
|
||
|
which is assigned sequentially during registration. Thus, handlers are
|
||
|
executed in ascending order of 'id' during 'prepare', and descending
|
||
|
order of 'id' during parent/child handler execution after the fork.
|
||
|
|
||
|
Two tests are included:
|
||
|
|
||
|
* tst-atfork3: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||
|
This test exercises calling dlclose from prepare, parent, and child
|
||
|
handlers.
|
||
|
|
||
|
* tst-atfork4: This test exercises calling pthread_atfork and dlclose
|
||
|
from the prepare handler.
|
||
|
|
||
|
[BZ #24595, BZ #27054]
|
||
|
|
||
|
Co-authored-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||
|
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
||
|
|
||
|
diff --git a/nptl/Makefile b/nptl/Makefile
|
||
|
index 70a3be23ecfcd9c9..76c914e23e8873f2 100644
|
||
|
--- a/nptl/Makefile
|
||
|
+++ b/nptl/Makefile
|
||
|
@@ -382,8 +382,17 @@ tests += tst-cancelx2 tst-cancelx3 tst-cancelx4 tst-cancelx5 \
|
||
|
tst-cancelx16 tst-cancelx17 tst-cancelx18 tst-cancelx20 tst-cancelx21 \
|
||
|
tst-cleanupx0 tst-cleanupx1 tst-cleanupx2 tst-cleanupx3 tst-cleanupx4
|
||
|
ifeq ($(build-shared),yes)
|
||
|
-tests += tst-atfork2 tst-tls4 tst-_res1 tst-fini1 tst-compat-forwarder \
|
||
|
- tst-audit-threads
|
||
|
+tests += \
|
||
|
+ tst-atfork2 \
|
||
|
+ tst-tls4 \
|
||
|
+ tst-_res1 \
|
||
|
+ tst-fini1 \
|
||
|
+ tst-compat-forwarder \
|
||
|
+ tst-audit-threads \
|
||
|
+ tst-atfork3 \
|
||
|
+ tst-atfork4 \
|
||
|
+# tests
|
||
|
+
|
||
|
tests-internal += tst-tls3 tst-tls3-malloc tst-tls5 tst-stackguard1
|
||
|
tests-nolibpthread += tst-fini1
|
||
|
ifeq ($(have-z-execstack),yes)
|
||
|
@@ -391,18 +400,39 @@ tests += tst-execstack
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
-modules-names = tst-atfork2mod tst-tls3mod tst-tls4moda tst-tls4modb \
|
||
|
- tst-tls5mod tst-tls5moda tst-tls5modb tst-tls5modc \
|
||
|
- tst-tls5modd tst-tls5mode tst-tls5modf tst-stack4mod \
|
||
|
- tst-_res1mod1 tst-_res1mod2 tst-execstack-mod tst-fini1mod \
|
||
|
- tst-join7mod tst-compat-forwarder-mod tst-audit-threads-mod1 \
|
||
|
- tst-audit-threads-mod2
|
||
|
+modules-names = \
|
||
|
+ tst-atfork2mod \
|
||
|
+ tst-tls3mod \
|
||
|
+ tst-tls4moda \
|
||
|
+ tst-tls4modb \
|
||
|
+ tst-tls5mod \
|
||
|
+ tst-tls5moda \
|
||
|
+ tst-tls5modb \
|
||
|
+ tst-tls5modc \
|
||
|
+ tst-tls5modd \
|
||
|
+ tst-tls5mode \
|
||
|
+ tst-tls5modf \
|
||
|
+ tst-stack4mod \
|
||
|
+ tst-_res1mod1 \
|
||
|
+ tst-_res1mod2 \
|
||
|
+ tst-execstack-mod \
|
||
|
+ tst-fini1mod \
|
||
|
+ tst-join7mod \
|
||
|
+ tst-compat-forwarder-mod \
|
||
|
+ tst-audit-threads-mod1 \
|
||
|
+ tst-audit-threads-mod2 \
|
||
|
+ tst-atfork3mod \
|
||
|
+ tst-atfork4mod \
|
||
|
+# module-names
|
||
|
+
|
||
|
extra-test-objs += $(addsuffix .os,$(strip $(modules-names))) \
|
||
|
tst-cleanup4aux.o tst-cleanupx4aux.o
|
||
|
test-extras += tst-cleanup4aux tst-cleanupx4aux
|
||
|
test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names)))
|
||
|
|
||
|
tst-atfork2mod.so-no-z-defs = yes
|
||
|
+tst-atfork3mod.so-no-z-defs = yes
|
||
|
+tst-atfork4mod.so-no-z-defs = yes
|
||
|
tst-tls3mod.so-no-z-defs = yes
|
||
|
tst-tls5mod.so-no-z-defs = yes
|
||
|
tst-tls5moda.so-no-z-defs = yes
|
||
|
@@ -541,6 +571,14 @@ LDFLAGS-tst-atfork2 = -rdynamic
|
||
|
tst-atfork2-ENV = MALLOC_TRACE=$(objpfx)tst-atfork2.mtrace
|
||
|
$(objpfx)tst-atfork2mod.so: $(shared-thread-library)
|
||
|
|
||
|
+$(objpfx)tst-atfork3: $(libdl) $(shared-thread-library)
|
||
|
+LDFLAGS-tst-atfork3 = -rdynamic
|
||
|
+$(objpfx)tst-atfork3mod.so: $(shared-thread-library)
|
||
|
+
|
||
|
+$(objpfx)tst-atfork4: $(libdl) $(shared-thread-library)
|
||
|
+LDFLAGS-tst-atfork4 = -rdynamic
|
||
|
+$(objpfx)tst-atfork4mod.so: $(shared-thread-library)
|
||
|
+
|
||
|
tst-stack3-ENV = MALLOC_TRACE=$(objpfx)tst-stack3.mtrace
|
||
|
$(objpfx)tst-stack3-mem.out: $(objpfx)tst-stack3.out
|
||
|
$(common-objpfx)malloc/mtrace $(objpfx)tst-stack3.mtrace > $@; \
|
||
|
@@ -640,6 +678,8 @@ $(objpfx)../libc.so: $(common-objpfx)libc.so ;
|
||
|
$(addprefix $(objpfx),$(tests-static) $(xtests-static)): $(objpfx)libpthread.a
|
||
|
|
||
|
$(objpfx)tst-atfork2.out: $(objpfx)tst-atfork2mod.so
|
||
|
+$(objpfx)tst-atfork3.out: $(objpfx)tst-atfork3mod.so
|
||
|
+$(objpfx)tst-atfork4.out: $(objpfx)tst-atfork4mod.so
|
||
|
else
|
||
|
$(addprefix $(objpfx),$(tests) $(test-srcs)): $(objpfx)libpthread.a
|
||
|
endif
|
||
|
diff --git a/nptl/register-atfork.c b/nptl/register-atfork.c
|
||
|
index 9edb7d4bbb49fbed..4c1e20ae8cab005f 100644
|
||
|
--- a/nptl/register-atfork.c
|
||
|
+++ b/nptl/register-atfork.c
|
||
|
@@ -21,6 +21,8 @@
|
||
|
#include <string.h>
|
||
|
#include <fork.h>
|
||
|
#include <atomic.h>
|
||
|
+#include <intprops.h>
|
||
|
+#include <stdio.h>
|
||
|
|
||
|
#define DYNARRAY_ELEMENT struct fork_handler
|
||
|
#define DYNARRAY_STRUCT fork_handler_list
|
||
|
@@ -29,7 +31,7 @@
|
||
|
#include <malloc/dynarray-skeleton.c>
|
||
|
|
||
|
static struct fork_handler_list fork_handlers;
|
||
|
-static bool fork_handler_init = false;
|
||
|
+static uint64_t fork_handler_counter;
|
||
|
|
||
|
static int atfork_lock = LLL_LOCK_INITIALIZER;
|
||
|
|
||
|
@@ -39,11 +41,8 @@ __register_atfork (void (*prepare) (void), void (*parent) (void),
|
||
|
{
|
||
|
lll_lock (atfork_lock, LLL_PRIVATE);
|
||
|
|
||
|
- if (!fork_handler_init)
|
||
|
- {
|
||
|
- fork_handler_list_init (&fork_handlers);
|
||
|
- fork_handler_init = true;
|
||
|
- }
|
||
|
+ if (fork_handler_counter == 0)
|
||
|
+ fork_handler_list_init (&fork_handlers);
|
||
|
|
||
|
struct fork_handler *newp = fork_handler_list_emplace (&fork_handlers);
|
||
|
if (newp != NULL)
|
||
|
@@ -52,6 +51,13 @@ __register_atfork (void (*prepare) (void), void (*parent) (void),
|
||
|
newp->parent_handler = parent;
|
||
|
newp->child_handler = child;
|
||
|
newp->dso_handle = dso_handle;
|
||
|
+
|
||
|
+ /* IDs assigned to handlers start at 1 and increment with handler
|
||
|
+ registration. Un-registering a handlers discards the corresponding
|
||
|
+ ID. It is not reused in future registrations. */
|
||
|
+ if (INT_ADD_OVERFLOW (fork_handler_counter, 1))
|
||
|
+ __libc_fatal ("fork handler counter overflow");
|
||
|
+ newp->id = ++fork_handler_counter;
|
||
|
}
|
||
|
|
||
|
/* Release the lock. */
|
||
|
@@ -106,37 +112,111 @@ __unregister_atfork (void *dso_handle)
|
||
|
lll_unlock (atfork_lock, LLL_PRIVATE);
|
||
|
}
|
||
|
|
||
|
-void
|
||
|
-__run_fork_handlers (enum __run_fork_handler_type who, _Bool do_locking)
|
||
|
+uint64_t
|
||
|
+__run_prefork_handlers (_Bool do_locking)
|
||
|
{
|
||
|
- struct fork_handler *runp;
|
||
|
+ uint64_t lastrun;
|
||
|
|
||
|
- if (who == atfork_run_prepare)
|
||
|
+ if (do_locking)
|
||
|
+ lll_lock (atfork_lock, LLL_PRIVATE);
|
||
|
+
|
||
|
+ /* We run prepare handlers from last to first. After fork, only
|
||
|
+ handlers up to the last handler found here (pre-fork) will be run.
|
||
|
+ Handlers registered during __run_prefork_handlers or
|
||
|
+ __run_postfork_handlers will be positioned after this last handler, and
|
||
|
+ since their prepare handlers won't be run now, their parent/child
|
||
|
+ handlers should also be ignored. */
|
||
|
+ lastrun = fork_handler_counter;
|
||
|
+
|
||
|
+ size_t sl = fork_handler_list_size (&fork_handlers);
|
||
|
+ for (size_t i = sl; i > 0;)
|
||
|
{
|
||
|
- if (do_locking)
|
||
|
- lll_lock (atfork_lock, LLL_PRIVATE);
|
||
|
- size_t sl = fork_handler_list_size (&fork_handlers);
|
||
|
- for (size_t i = sl; i > 0; i--)
|
||
|
- {
|
||
|
- runp = fork_handler_list_at (&fork_handlers, i - 1);
|
||
|
- if (runp->prepare_handler != NULL)
|
||
|
- runp->prepare_handler ();
|
||
|
- }
|
||
|
+ struct fork_handler *runp
|
||
|
+ = fork_handler_list_at (&fork_handlers, i - 1);
|
||
|
+
|
||
|
+ uint64_t id = runp->id;
|
||
|
+
|
||
|
+ if (runp->prepare_handler != NULL)
|
||
|
+ {
|
||
|
+ if (do_locking)
|
||
|
+ lll_unlock (atfork_lock, LLL_PRIVATE);
|
||
|
+
|
||
|
+ runp->prepare_handler ();
|
||
|
+
|
||
|
+ if (do_locking)
|
||
|
+ lll_lock (atfork_lock, LLL_PRIVATE);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* We unlocked, ran the handler, and locked again. In the
|
||
|
+ meanwhile, one or more deregistrations could have occurred leading
|
||
|
+ to the current (just run) handler being moved up the list or even
|
||
|
+ removed from the list itself. Since handler IDs are guaranteed to
|
||
|
+ to be in increasing order, the next handler has to have: */
|
||
|
+
|
||
|
+ /* A. An earlier position than the current one has. */
|
||
|
+ i--;
|
||
|
+
|
||
|
+ /* B. A lower ID than the current one does. The code below skips
|
||
|
+ any newly added handlers with higher IDs. */
|
||
|
+ while (i > 0
|
||
|
+ && fork_handler_list_at (&fork_handlers, i - 1)->id >= id)
|
||
|
+ i--;
|
||
|
}
|
||
|
- else
|
||
|
+
|
||
|
+ return lastrun;
|
||
|
+}
|
||
|
+
|
||
|
+void
|
||
|
+__run_postfork_handlers (enum __run_fork_handler_type who, _Bool do_locking,
|
||
|
+ uint64_t lastrun)
|
||
|
+{
|
||
|
+ size_t sl = fork_handler_list_size (&fork_handlers);
|
||
|
+ for (size_t i = 0; i < sl;)
|
||
|
{
|
||
|
- size_t sl = fork_handler_list_size (&fork_handlers);
|
||
|
- for (size_t i = 0; i < sl; i++)
|
||
|
- {
|
||
|
- runp = fork_handler_list_at (&fork_handlers, i);
|
||
|
- if (who == atfork_run_child && runp->child_handler)
|
||
|
- runp->child_handler ();
|
||
|
- else if (who == atfork_run_parent && runp->parent_handler)
|
||
|
- runp->parent_handler ();
|
||
|
- }
|
||
|
+ struct fork_handler *runp = fork_handler_list_at (&fork_handlers, i);
|
||
|
+ uint64_t id = runp->id;
|
||
|
+
|
||
|
+ /* prepare handlers were not run for handlers with ID > LASTRUN.
|
||
|
+ Thus, parent/child handlers will also not be run. */
|
||
|
+ if (id > lastrun)
|
||
|
+ break;
|
||
|
+
|
||
|
if (do_locking)
|
||
|
- lll_unlock (atfork_lock, LLL_PRIVATE);
|
||
|
+ lll_unlock (atfork_lock, LLL_PRIVATE);
|
||
|
+
|
||
|
+ if (who == atfork_run_child && runp->child_handler)
|
||
|
+ runp->child_handler ();
|
||
|
+ else if (who == atfork_run_parent && runp->parent_handler)
|
||
|
+ runp->parent_handler ();
|
||
|
+
|
||
|
+ if (do_locking)
|
||
|
+ lll_lock (atfork_lock, LLL_PRIVATE);
|
||
|
+
|
||
|
+ /* We unlocked, ran the handler, and locked again. In the meanwhile,
|
||
|
+ one or more [de]registrations could have occurred. Due to this,
|
||
|
+ the list size must be updated. */
|
||
|
+ sl = fork_handler_list_size (&fork_handlers);
|
||
|
+
|
||
|
+ /* The just-run handler could also have moved up the list. */
|
||
|
+
|
||
|
+ if (sl > i && fork_handler_list_at (&fork_handlers, i)->id == id)
|
||
|
+ /* The position of the recently run handler hasn't changed. The
|
||
|
+ next handler to be run is an easy increment away. */
|
||
|
+ i++;
|
||
|
+ else
|
||
|
+ {
|
||
|
+ /* The next handler to be run is the first handler in the list
|
||
|
+ to have an ID higher than the current one. */
|
||
|
+ for (i = 0; i < sl; i++)
|
||
|
+ {
|
||
|
+ if (fork_handler_list_at (&fork_handlers, i)->id > id)
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
}
|
||
|
+
|
||
|
+ if (do_locking)
|
||
|
+ lll_unlock (atfork_lock, LLL_PRIVATE);
|
||
|
}
|
||
|
|
||
|
|
||
|
diff --git a/nptl/tst-atfork3.c b/nptl/tst-atfork3.c
|
||
|
new file mode 100644
|
||
|
index 0000000000000000..bb2250e432ab79ad
|
||
|
--- /dev/null
|
||
|
+++ b/nptl/tst-atfork3.c
|
||
|
@@ -0,0 +1,118 @@
|
||
|
+/* Check if pthread_atfork handler can call dlclose (BZ#24595).
|
||
|
+ Copyright (C) 2022 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
|
||
|
+ <http://www.gnu.org/licenses/>. */
|
||
|
+
|
||
|
+#include <stdio.h>
|
||
|
+#include <pthread.h>
|
||
|
+#include <unistd.h>
|
||
|
+#include <stdlib.h>
|
||
|
+#include <stdbool.h>
|
||
|
+
|
||
|
+#include <support/check.h>
|
||
|
+#include <support/xthread.h>
|
||
|
+#include <support/capture_subprocess.h>
|
||
|
+#include <support/xdlfcn.h>
|
||
|
+
|
||
|
+/* Check if pthread_atfork handlers do not deadlock when calling a function
|
||
|
+ that might alter the internal fork handle list, such as dlclose.
|
||
|
+
|
||
|
+ The test registers a callback set with pthread_atfork(), dlopen() a shared
|
||
|
+ library (nptl/tst-atfork3mod.c), calls an exported symbol from the library
|
||
|
+ (which in turn also registers atfork handlers), and calls fork to trigger
|
||
|
+ the callbacks. */
|
||
|
+
|
||
|
+static void *handler;
|
||
|
+static bool run_dlclose_prepare;
|
||
|
+static bool run_dlclose_parent;
|
||
|
+static bool run_dlclose_child;
|
||
|
+
|
||
|
+static void
|
||
|
+prepare (void)
|
||
|
+{
|
||
|
+ if (run_dlclose_prepare)
|
||
|
+ xdlclose (handler);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+parent (void)
|
||
|
+{
|
||
|
+ if (run_dlclose_parent)
|
||
|
+ xdlclose (handler);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+child (void)
|
||
|
+{
|
||
|
+ if (run_dlclose_child)
|
||
|
+ xdlclose (handler);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+proc_func (void *closure)
|
||
|
+{
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+do_test_generic (bool dlclose_prepare, bool dlclose_parent, bool dlclose_child)
|
||
|
+{
|
||
|
+ run_dlclose_prepare = dlclose_prepare;
|
||
|
+ run_dlclose_parent = dlclose_parent;
|
||
|
+ run_dlclose_child = dlclose_child;
|
||
|
+
|
||
|
+ handler = xdlopen ("tst-atfork3mod.so", RTLD_NOW);
|
||
|
+
|
||
|
+ int (*atfork3mod_func)(void);
|
||
|
+ atfork3mod_func = xdlsym (handler, "atfork3mod_func");
|
||
|
+
|
||
|
+ atfork3mod_func ();
|
||
|
+
|
||
|
+ struct support_capture_subprocess proc
|
||
|
+ = support_capture_subprocess (proc_func, NULL);
|
||
|
+ support_capture_subprocess_check (&proc, "tst-atfork3", 0, sc_allow_none);
|
||
|
+
|
||
|
+ handler = atfork3mod_func = NULL;
|
||
|
+
|
||
|
+ support_capture_subprocess_free (&proc);
|
||
|
+}
|
||
|
+
|
||
|
+static void *
|
||
|
+thread_func (void *closure)
|
||
|
+{
|
||
|
+ return NULL;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+do_test (void)
|
||
|
+{
|
||
|
+ {
|
||
|
+ /* Make the process acts as multithread. */
|
||
|
+ pthread_attr_t attr;
|
||
|
+ xpthread_attr_init (&attr);
|
||
|
+ xpthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
|
||
|
+ xpthread_create (&attr, thread_func, NULL);
|
||
|
+ }
|
||
|
+
|
||
|
+ TEST_COMPARE (pthread_atfork (prepare, parent, child), 0);
|
||
|
+
|
||
|
+ do_test_generic (true /* prepare */, false /* parent */, false /* child */);
|
||
|
+ do_test_generic (false /* prepare */, true /* parent */, false /* child */);
|
||
|
+ do_test_generic (false /* prepare */, false /* parent */, true /* child */);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+#include <support/test-driver.c>
|
||
|
diff --git a/nptl/tst-atfork3mod.c b/nptl/tst-atfork3mod.c
|
||
|
new file mode 100644
|
||
|
index 0000000000000000..6d0658cb9efdecbc
|
||
|
--- /dev/null
|
||
|
+++ b/nptl/tst-atfork3mod.c
|
||
|
@@ -0,0 +1,44 @@
|
||
|
+/* Copyright (C) 2022 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
|
||
|
+ <http://www.gnu.org/licenses/>. */
|
||
|
+
|
||
|
+#include <unistd.h>
|
||
|
+#include <stdlib.h>
|
||
|
+#include <pthread.h>
|
||
|
+
|
||
|
+#include <support/check.h>
|
||
|
+
|
||
|
+static void
|
||
|
+mod_prepare (void)
|
||
|
+{
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+mod_parent (void)
|
||
|
+{
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+mod_child (void)
|
||
|
+{
|
||
|
+}
|
||
|
+
|
||
|
+int atfork3mod_func (void)
|
||
|
+{
|
||
|
+ TEST_COMPARE (pthread_atfork (mod_prepare, mod_parent, mod_child), 0);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
diff --git a/nptl/tst-atfork4.c b/nptl/tst-atfork4.c
|
||
|
new file mode 100644
|
||
|
index 0000000000000000..52dc87e73b846ab9
|
||
|
--- /dev/null
|
||
|
+++ b/nptl/tst-atfork4.c
|
||
|
@@ -0,0 +1,128 @@
|
||
|
+/* pthread_atfork supports handlers that call pthread_atfork or dlclose.
|
||
|
+ Copyright (C) 2022 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 <support/xdlfcn.h>
|
||
|
+#include <stdio.h>
|
||
|
+#include <support/xthread.h>
|
||
|
+#include <sys/types.h>
|
||
|
+#include <sys/wait.h>
|
||
|
+#include <support/xunistd.h>
|
||
|
+#include <support/check.h>
|
||
|
+#include <stdlib.h>
|
||
|
+
|
||
|
+static void *
|
||
|
+thread_func (void *x)
|
||
|
+{
|
||
|
+ return NULL;
|
||
|
+}
|
||
|
+
|
||
|
+static unsigned int second_atfork_handler_runcount = 0;
|
||
|
+
|
||
|
+static void
|
||
|
+second_atfork_handler (void)
|
||
|
+{
|
||
|
+ second_atfork_handler_runcount++;
|
||
|
+}
|
||
|
+
|
||
|
+static void *h = NULL;
|
||
|
+
|
||
|
+static unsigned int atfork_handler_runcount = 0;
|
||
|
+
|
||
|
+static void
|
||
|
+prepare (void)
|
||
|
+{
|
||
|
+ /* These atfork handlers are registered while atfork handlers are being
|
||
|
+ executed and thus will not be executed during the corresponding
|
||
|
+ fork. */
|
||
|
+ TEST_VERIFY_EXIT (pthread_atfork (second_atfork_handler,
|
||
|
+ second_atfork_handler,
|
||
|
+ second_atfork_handler) == 0);
|
||
|
+
|
||
|
+ /* This will de-register the atfork handlers registered by the dlopen'd
|
||
|
+ library and so they will not be executed. */
|
||
|
+ if (h != NULL)
|
||
|
+ {
|
||
|
+ xdlclose (h);
|
||
|
+ h = NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ atfork_handler_runcount++;
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+after (void)
|
||
|
+{
|
||
|
+ atfork_handler_runcount++;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+do_test (void)
|
||
|
+{
|
||
|
+ /* Make sure __libc_single_threaded is 0. */
|
||
|
+ pthread_attr_t attr;
|
||
|
+ xpthread_attr_init (&attr);
|
||
|
+ xpthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
|
||
|
+ xpthread_create (&attr, thread_func, NULL);
|
||
|
+
|
||
|
+ void (*reg_atfork_handlers) (void);
|
||
|
+
|
||
|
+ h = xdlopen ("tst-atfork4mod.so", RTLD_LAZY);
|
||
|
+
|
||
|
+ reg_atfork_handlers = xdlsym (h, "reg_atfork_handlers");
|
||
|
+
|
||
|
+ reg_atfork_handlers ();
|
||
|
+
|
||
|
+ /* We register our atfork handlers *after* loading the module so that our
|
||
|
+ prepare handler is called first at fork, where we then dlclose the
|
||
|
+ module before its prepare handler has a chance to be called. */
|
||
|
+ TEST_VERIFY_EXIT (pthread_atfork (prepare, after, after) == 0);
|
||
|
+
|
||
|
+ pid_t pid = xfork ();
|
||
|
+
|
||
|
+ /* Both the parent and the child processes should observe this. */
|
||
|
+ TEST_VERIFY_EXIT (atfork_handler_runcount == 2);
|
||
|
+ TEST_VERIFY_EXIT (second_atfork_handler_runcount == 0);
|
||
|
+
|
||
|
+ if (pid > 0)
|
||
|
+ {
|
||
|
+ int childstat;
|
||
|
+
|
||
|
+ xwaitpid (-1, &childstat, 0);
|
||
|
+ TEST_VERIFY_EXIT (WIFEXITED (childstat)
|
||
|
+ && WEXITSTATUS (childstat) == 0);
|
||
|
+
|
||
|
+ /* This time, the second set of atfork handlers should also be called
|
||
|
+ since the handlers are already in place before fork is called. */
|
||
|
+
|
||
|
+ pid = xfork ();
|
||
|
+
|
||
|
+ TEST_VERIFY_EXIT (atfork_handler_runcount == 4);
|
||
|
+ TEST_VERIFY_EXIT (second_atfork_handler_runcount == 2);
|
||
|
+
|
||
|
+ if (pid > 0)
|
||
|
+ {
|
||
|
+ xwaitpid (-1, &childstat, 0);
|
||
|
+ TEST_VERIFY_EXIT (WIFEXITED (childstat)
|
||
|
+ && WEXITSTATUS (childstat) == 0);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+#include <support/test-driver.c>
|
||
|
diff --git a/nptl/tst-atfork4mod.c b/nptl/tst-atfork4mod.c
|
||
|
new file mode 100644
|
||
|
index 0000000000000000..e111efeb185916e0
|
||
|
--- /dev/null
|
||
|
+++ b/nptl/tst-atfork4mod.c
|
||
|
@@ -0,0 +1,48 @@
|
||
|
+/* pthread_atfork supports handlers that call pthread_atfork or dlclose.
|
||
|
+ Copyright (C) 2022 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 <pthread.h>
|
||
|
+#include <stdlib.h>
|
||
|
+
|
||
|
+/* This dynamically loaded library simply registers its atfork handlers when
|
||
|
+ asked to. The atfork handlers should never be executed because the
|
||
|
+ library is unloaded before fork is called by the test program. */
|
||
|
+
|
||
|
+static void
|
||
|
+prepare (void)
|
||
|
+{
|
||
|
+ abort ();
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+parent (void)
|
||
|
+{
|
||
|
+ abort ();
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+child (void)
|
||
|
+{
|
||
|
+ abort ();
|
||
|
+}
|
||
|
+
|
||
|
+void
|
||
|
+reg_atfork_handlers (void)
|
||
|
+{
|
||
|
+ pthread_atfork (prepare, parent, child);
|
||
|
+}
|
||
|
diff --git a/sysdeps/nptl/fork.c b/sysdeps/nptl/fork.c
|
||
|
index b4d20fa652f4ba3b..1324b813136764fc 100644
|
||
|
--- a/sysdeps/nptl/fork.c
|
||
|
+++ b/sysdeps/nptl/fork.c
|
||
|
@@ -54,8 +54,9 @@ __libc_fork (void)
|
||
|
signal handlers. POSIX requires that fork is async-signal-safe,
|
||
|
but our current fork implementation is not. */
|
||
|
bool multiple_threads = THREAD_GETMEM (THREAD_SELF, header.multiple_threads);
|
||
|
+ uint64_t lastrun;
|
||
|
|
||
|
- __run_fork_handlers (atfork_run_prepare, multiple_threads);
|
||
|
+ lastrun = __run_prefork_handlers (multiple_threads);
|
||
|
|
||
|
/* If we are not running multiple threads, we do not have to
|
||
|
preserve lock state. If fork runs from a signal handler, only
|
||
|
@@ -129,7 +130,7 @@ __libc_fork (void)
|
||
|
__rtld_lock_initialize (GL(dl_load_tls_lock));
|
||
|
|
||
|
/* Run the handlers registered for the child. */
|
||
|
- __run_fork_handlers (atfork_run_child, multiple_threads);
|
||
|
+ __run_postfork_handlers (atfork_run_child, multiple_threads, lastrun);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
@@ -144,7 +145,7 @@ __libc_fork (void)
|
||
|
}
|
||
|
|
||
|
/* Run the handlers registered for the parent. */
|
||
|
- __run_fork_handlers (atfork_run_parent, multiple_threads);
|
||
|
+ __run_postfork_handlers (atfork_run_parent, multiple_threads, lastrun);
|
||
|
}
|
||
|
|
||
|
return pid;
|
||
|
diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h
|
||
|
index bef2b7a8a6af8635..222c4f618970a455 100644
|
||
|
--- a/sysdeps/nptl/fork.h
|
||
|
+++ b/sysdeps/nptl/fork.h
|
||
|
@@ -31,6 +31,7 @@ struct fork_handler
|
||
|
void (*parent_handler) (void);
|
||
|
void (*child_handler) (void);
|
||
|
void *dso_handle;
|
||
|
+ uint64_t id;
|
||
|
};
|
||
|
|
||
|
/* Function to call to unregister fork handlers. */
|
||
|
@@ -44,19 +45,18 @@ enum __run_fork_handler_type
|
||
|
atfork_run_parent
|
||
|
};
|
||
|
|
||
|
-/* Run the atfork handlers and lock/unlock the internal lock depending
|
||
|
- of the WHO argument:
|
||
|
+/* Run the atfork prepare handlers in the reverse order of registration and
|
||
|
+ return the ID of the last registered handler. If DO_LOCKING is true, the
|
||
|
+ internal lock is held locked upon return. */
|
||
|
+extern uint64_t __run_prefork_handlers (_Bool do_locking) attribute_hidden;
|
||
|
|
||
|
- - atfork_run_prepare: run all the PREPARE_HANDLER in reverse order of
|
||
|
- insertion and locks the internal lock.
|
||
|
- - atfork_run_child: run all the CHILD_HANDLER and unlocks the internal
|
||
|
- lock.
|
||
|
- - atfork_run_parent: run all the PARENT_HANDLER and unlocks the internal
|
||
|
- lock.
|
||
|
-
|
||
|
- Perform locking only if DO_LOCKING. */
|
||
|
-extern void __run_fork_handlers (enum __run_fork_handler_type who,
|
||
|
- _Bool do_locking) attribute_hidden;
|
||
|
+/* Given a handler type (parent or child), run all the atfork handlers in
|
||
|
+ the order of registration up to and including the handler with id equal
|
||
|
+ to LASTRUN. If DO_LOCKING is true, the internal lock is unlocked prior
|
||
|
+ to return. */
|
||
|
+extern void __run_postfork_handlers (enum __run_fork_handler_type who,
|
||
|
+ _Bool do_locking,
|
||
|
+ uint64_t lastrun) attribute_hidden;
|
||
|
|
||
|
/* C library side function to register new fork handlers. */
|
||
|
extern int __register_atfork (void (*__prepare) (void),
|