From patchwork Thu May 23 13:30:48 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella X-Patchwork-Id: 164999 Delivered-To: patch@linaro.org Received: by 2002:a92:9e1a:0:0:0:0:0 with SMTP id q26csp2191292ili; Thu, 23 May 2019 06:31:11 -0700 (PDT) X-Google-Smtp-Source: APXvYqyif/TtQtEE8rHaeRhC+kS9eis9LrkitjOv8AgqqOfdo3My81MaGhGpF490yXg9js4iFeT+ X-Received: by 2002:a63:ed03:: with SMTP id d3mr97028077pgi.7.1558618271632; Thu, 23 May 2019 06:31:11 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1558618271; cv=none; d=google.com; s=arc-20160816; b=YNRBUWmFGM2ft9gh0N37/YncNW5vzHtOtZFJHvyBJvrszWrWCiX8I7+aBXF/ywvX/k Q4EPoodn+LIgaAPVZD7KMpDw3xTJg9Wdgypu0AOJOXt9PUrxrlgWgAZhFCZkdAuSieDq sF3xGjshAj3FCpVJFpFIXjfQ+LOWQn0DuOYmHwGV1xLPQTETOWClx5aQeMKaBG+dYDzB fDUIwZYW7mkdBdOUCimP0EeUtlWeYh3qhn2nnMBYAJWpimWc4mKwqpxIlewR6ZEf6evp UjPDkg5SEmjpSCgQYIsl8Rf8vKA1qzNDERNRF5C1Mvgnjy+UWU/nrFknecK6zDiPQVKn CJ/Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=message-id:date:subject:to:from:dkim-signature:delivered-to:sender :list-help:list-post:list-archive:list-subscribe:list-unsubscribe :list-id:precedence:mailing-list:dkim-signature:domainkey-signature; bh=JlhaVVzvLTJW6kxGxJupu2Cnpjy3b+qctvRu+aTs/f4=; b=DZOAsf/6LHvzQsIizUon0X2ICz9eG2tVvQqWZil2AAC9W+EERtZx7JGSbwWPaImGqU XfuA8Z/ZEFYqRoX0CPejCtDNDaC3ietBjNDHaVNF/bT9eOEP+ZAdoaLMyOi1l9EtQkuA qHxyv3z4RAdZwmL7Hn873O4RDZXYTAl70uNdxp8pKEkTHDaCuKEsvwEV7VpL7cUrb+Ek 7P1UMbigtfdDzmKU8Yq2xvOXbMKL5slNXPP0ebJLo9O8CAdCML7RZam+8MiOZsLsQY6v Jr3WptVR6FqPsXyo9Zx6wsbFIxd7tkqWjNQRaJXr36ACHDghCQhJEQAUBtH27LYlH+lT OyTw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@sourceware.org header.s=default header.b=mMUrYEGp; dkim=pass header.i=@linaro.org header.s=google header.b=fx6W5xWy; spf=pass (google.com: domain of libc-alpha-return-102213-patch=linaro.org@sourceware.org designates 209.132.180.131 as permitted sender) smtp.mailfrom="libc-alpha-return-102213-patch=linaro.org@sourceware.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from sourceware.org (server1.sourceware.org. [209.132.180.131]) by mx.google.com with ESMTPS id k19si30326728pgh.143.2019.05.23.06.31.11 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 23 May 2019 06:31:11 -0700 (PDT) Received-SPF: pass (google.com: domain of libc-alpha-return-102213-patch=linaro.org@sourceware.org designates 209.132.180.131 as permitted sender) client-ip=209.132.180.131; Authentication-Results: mx.google.com; dkim=pass header.i=@sourceware.org header.s=default header.b=mMUrYEGp; dkim=pass header.i=@linaro.org header.s=google header.b=fx6W5xWy; spf=pass (google.com: domain of libc-alpha-return-102213-patch=linaro.org@sourceware.org designates 209.132.180.131 as permitted sender) smtp.mailfrom="libc-alpha-return-102213-patch=linaro.org@sourceware.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:from:to:subject:date:message-id; q=dns; s= default; b=Lntzbiok0jFyI7voHCxe5rVR/yozF8GiSEHwNcr4BUR4znLpSoYJa BdFYCYOvll/eLBC6l3HzMocBGxaf370kPTA/ZTKbtwq7PTXWu66SlWEErfBsCXPj WabZVM9fRvI7JwYJIygjeQYSP6dSs6LcAWjDRheF7BjqECFQPwkKnE= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:from:to:subject:date:message-id; s=default; bh=mDiWwVhOMkZTVN4N811TL6OEWb0=; b=mMUrYEGpJZQIYC+WsLV0jhggwXkg Wasn8ho0dwGsOs6fHLILIuP9mKUrCNV4dv5BcXlsFaspKHkOEvDHfPPS5W+S6Wpf FutYFPFe64JAeJuhmgKu7d9T/BFSgew7GgyUy4iWMcVi2w2er2yAbJ9Wr8JI6Jwg zEMsPB4vu20n9so= Received: (qmail 39937 invoked by alias); 23 May 2019 13:31:02 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 39929 invoked by uid 89); 23 May 2019 13:31:02 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-20.9 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_PASS autolearn=ham version=3.3.1 spammy=issuing, 2003, shifting, so X-HELO: mail-ua1-f67.google.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:subject:date:message-id; bh=JlhaVVzvLTJW6kxGxJupu2Cnpjy3b+qctvRu+aTs/f4=; b=fx6W5xWyTGExB4ic/N9YZAuCZWjN7igkjJNQlK69cJR6k+t+S6dwkMGH7M0F5w+SSm VEcMJXHiCpgXd4tpA0l3zStdcI6LnW+J0eOXVNmn51K2DGKEm73hvHMT1KmzfTDq8wJz 0KpXeBXsjblZ2b5VITL4RtCeM6VEZz07zQM0OXAJY7yzeakx6AmIzopvDVO+GYKVIXSa m0xRGBL6lRJQMNWQVJkxEKqgqFVoTTYt7p4GtdmMSaRyM9F2yEehIU+YSW78Te6wy97o 03Sc2gdyeNiE/mtHccQTKxTAK/3un1d+U+a1Hf5K2JsLuyzeobt4+Bt44s9tjn44SOuR 2Xdw== Return-Path: From: Adhemerval Zanella To: libc-alpha@sourceware.org Subject: [PATCH] nptl: Fix deadlock on atfork handler which calls dlclose (BZ#24595) Date: Thu, 23 May 2019 10:30:48 -0300 Message-Id: <20190523133048.14922-1-adhemerval.zanella@linaro.org> Some real-world cases rely upon that atfork handlers can call functions that might change the atfork handlers, such as dlclose. Since 27761a10 (Refactor atfork handlers), all internal atfork handlers access is protected with a simple lock, not allowing reentrancy. This leads to deadlocks for the aforementioned scenario. Although this specific usage is far from portable (as comment #2 in the bug report), glibc did allow it. This patch fixes by using a recursive lock along with a double-linked list to hold the atfork handlers. It allows atfork handlers to remove elements through dlclose without invaliding the forward or backward walking when issuing the atfork handlers. Checked on x86_64-linux-gnu, i686-linux-gnu, powerpc64le-linux-gnu, powerpc-linux-gnu, and aarch64-linux-gnu. * nptl/Makefile [build-shared == yes] (tests): Add tst-atfork3. (modules-names): Add tst-atfork3mod. (tst-atfork3mod.so-no-z-defs, $(objpfx)tst-atfork3:, LDFLAGS-tst-atfork3, $(objpfx)tst-atfork3mod.so, $(objpfx)tst-atfork3.out): New rules. * nptl/register-atfork.c: (__register_atfork, __unregister_atfork, __run_fork_handlers, libc_freeres_fn): Remove usage of dynarray in favor of a double linked list. (atfork_lock): Change to a recursive lock. (fork_handlers_push_back, fork_handlers_remove, fork_handlers_remove_if, fork_handlers_remove_all): New functions. (fork_handler_list_find): Remove function. * nptl/tst-atfork3.c, nptl/tst-atfork3mod.c: New files. * sysdeps/nptl/fork.h (fork_handler): Add next and prev members. --- nptl/Makefile | 10 ++- nptl/register-atfork.c | 194 +++++++++++++++++++++++------------------ nptl/tst-atfork3.c | 118 +++++++++++++++++++++++++ nptl/tst-atfork3mod.c | 45 ++++++++++ sysdeps/nptl/fork.h | 2 + 5 files changed, 279 insertions(+), 90 deletions(-) create mode 100644 nptl/tst-atfork3.c create mode 100644 nptl/tst-atfork3mod.c -- 2.17.1 diff --git a/nptl/Makefile b/nptl/Makefile index de312b3477..a47a112dc5 100644 --- a/nptl/Makefile +++ b/nptl/Makefile @@ -391,7 +391,7 @@ tests += tst-cancelx2 tst-cancelx3 tst-cancelx4 tst-cancelx5 \ tst-oncex3 tst-oncex4 ifeq ($(build-shared),yes) tests += tst-atfork2 tst-tls4 tst-_res1 tst-fini1 tst-compat-forwarder \ - tst-audit-threads + tst-audit-threads tst-atfork3 tests-internal += tst-tls3 tst-tls3-malloc tst-tls5 tst-stackguard1 tests-nolibpthread += tst-fini1 ifeq ($(have-z-execstack),yes) @@ -404,13 +404,14 @@ modules-names = tst-atfork2mod tst-tls3mod tst-tls4moda tst-tls4modb \ 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-audit-threads-mod2 tst-atfork3mod 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-tls3mod.so-no-z-defs = yes tst-tls5mod.so-no-z-defs = yes tst-tls5moda.so-no-z-defs = yes @@ -547,9 +548,12 @@ tst-cancelx7-ARGS = $(tst-cancel7-ARGS) tst-umask1-ARGS = $(objpfx)tst-umask1.temp $(objpfx)tst-atfork2: $(libdl) $(shared-thread-library) +$(objpfx)tst-atfork3: $(libdl) $(shared-thread-library) LDFLAGS-tst-atfork2 = -rdynamic +LDFLAGS-tst-atfork3 = -rdynamic tst-atfork2-ENV = MALLOC_TRACE=$(objpfx)tst-atfork2.mtrace $(objpfx)tst-atfork2mod.so: $(shared-thread-library) +$(objpfx)tst-atfork3mod.so: $(shared-thread-library) tst-stack3-ENV = MALLOC_TRACE=$(objpfx)tst-stack3.mtrace $(objpfx)tst-stack3-mem.out: $(objpfx)tst-stack3.out @@ -649,7 +653,7 @@ $(addprefix $(objpfx), $(tests-reverse)): \ $(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 else $(addprefix $(objpfx),$(tests) $(test-srcs)): $(objpfx)libpthread.a endif diff --git a/nptl/register-atfork.c b/nptl/register-atfork.c index 80a1becb5f..09a61fc210 100644 --- a/nptl/register-atfork.c +++ b/nptl/register-atfork.c @@ -20,131 +20,151 @@ #include #include #include -#include +#include -#define DYNARRAY_ELEMENT struct fork_handler -#define DYNARRAY_STRUCT fork_handler_list -#define DYNARRAY_PREFIX fork_handler_list_ -#define DYNARRAY_INITIAL_SIZE 48 -#include +/* The codes uses a recursive mutex plus a linked-list to allow an atfork + handler to modify the fork handler list. A real usercase is when callback + calls dlclose to unload a specific library, where the library itself has an + atfork handler and thus is removal will trigger __unregister_atfork. -static struct fork_handler_list fork_handlers; -static bool fork_handler_init = false; + On __run_fork_handlers, if the callback triggers __unregister_atfork + the fork_handlers_remove_if removes the elements to make sure the + walking can progress (either fowards or backwards). */ -static int atfork_lock = LLL_LOCK_INITIALIZER; +struct fork_handler_list_t +{ + struct fork_handler *first; + struct fork_handler *last; +}; +static struct fork_handler_list_t fork_handlers = { NULL, NULL }; +__libc_lock_define_initialized_recursive (static, atfork_lock); + +/* Add the elements ENTRY on the end of FORK_HANDLE_LIST. */ +static void +fork_handlers_push_back (struct fork_handler *entry) +{ + entry->next = NULL; + entry->prev = fork_handlers.last; + if (entry->prev != NULL) + entry->prev->next = entry; + if (fork_handlers.first == NULL) + fork_handlers.first = entry; + fork_handlers.last = entry; +} -int -__register_atfork (void (*prepare) (void), void (*parent) (void), - void (*child) (void), void *dso_handle) +/* Remove the element ENTRY from the list FORK_HANDLE_LIST, updating + its internal state. */ +static void +fork_handlers_remove (struct fork_handler *entry) { - lll_lock (atfork_lock, LLL_PRIVATE); + if (entry->prev != NULL) + entry->prev->next = entry->next; + else + fork_handlers.first = entry->next; - if (!fork_handler_init) - { - fork_handler_list_init (&fork_handlers); - fork_handler_init = true; - } + if (entry->next != NULL) + entry->next->prev = entry->prev; + else + fork_handlers.last = entry->prev; - struct fork_handler *newp = fork_handler_list_emplace (&fork_handlers); - if (newp != NULL) + free (entry); +} + +/* Remove all the elements that has its internal dso_handle equal to + DSO_HANDLE. */ +static void +fork_handlers_remove_if (void *dso_handle) +{ + struct fork_handler *it = fork_handlers.first; + while (it != NULL) { - newp->prepare_handler = prepare; - newp->parent_handler = parent; - newp->child_handler = child; - newp->dso_handle = dso_handle; + if (it->dso_handle == dso_handle) + { + struct fork_handler *entry = it; + it = it->next; + fork_handlers_remove (entry); + } + else + it = it->next; } - - /* Release the lock. */ - lll_unlock (atfork_lock, LLL_PRIVATE); - - return newp == NULL ? ENOMEM : 0; } -libc_hidden_def (__register_atfork) -static struct fork_handler * -fork_handler_list_find (struct fork_handler_list *fork_handlers, - void *dso_handle) +/* Remove all elements from FORK_HANDLE_LIST. */ +static void +fork_handlers_remove_all (void) { - for (size_t i = 0; i < fork_handler_list_size (fork_handlers); i++) + struct fork_handler *it = fork_handlers.first; + while (it != NULL) { - struct fork_handler *elem = fork_handler_list_at (fork_handlers, i); - if (elem->dso_handle == dso_handle) - return elem; + struct fork_handler *entry = it; + it = it->next; + fork_handlers_remove (entry); } - return NULL; } -void -__unregister_atfork (void *dso_handle) +int +__register_atfork (void (*prepare) (void), void (*parent) (void), + void (*child) (void), void *dso_handle) { - lll_lock (atfork_lock, LLL_PRIVATE); - - struct fork_handler *first = fork_handler_list_find (&fork_handlers, - dso_handle); - /* Removing is done by shifting the elements in the way the elements - that are not to be removed appear in the beginning in dynarray. - This avoid the quadradic run-time if a naive strategy to remove and - shift one element at time. */ - if (first != NULL) - { - struct fork_handler *new_end = first; - first++; - for (; first != fork_handler_list_end (&fork_handlers); ++first) - { - if (first->dso_handle != dso_handle) - { - *new_end = *first; - ++new_end; - } - } + struct fork_handler *entry = malloc (sizeof (struct fork_handler)); + if (entry == NULL) + return ENOMEM; - ptrdiff_t removed = first - new_end; - for (size_t i = 0; i < removed; i++) - fork_handler_list_remove_last (&fork_handlers); - } + entry->prepare_handler = prepare; + entry->parent_handler = parent; + entry->child_handler = child; + entry->dso_handle = dso_handle; + + __libc_lock_lock_recursive (atfork_lock); + fork_handlers_push_back (entry); + __libc_lock_unlock_recursive (atfork_lock); - lll_unlock (atfork_lock, LLL_PRIVATE); + return 0; } +libc_hidden_def (__register_atfork) void -__run_fork_handlers (enum __run_fork_handler_type who, _Bool do_locking) +__unregister_atfork (void *dso_handle) { - struct fork_handler *runp; + __libc_lock_lock_recursive (atfork_lock); + fork_handlers_remove_if (dso_handle); + __libc_lock_unlock_recursive (atfork_lock); +} +void +__run_fork_handlers (enum __run_fork_handler_type who, _Bool do_locking) +{ if (who == atfork_run_prepare) { 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--) + __libc_lock_lock_recursive (atfork_lock); + + for (struct fork_handler *it = fork_handlers.last; it != NULL; + it = it->prev) { - runp = fork_handler_list_at (&fork_handlers, i - 1); - if (runp->prepare_handler != NULL) - runp->prepare_handler (); + if (it->prepare_handler != NULL) + it->prepare_handler (); } } else { - size_t sl = fork_handler_list_size (&fork_handlers); - for (size_t i = 0; i < sl; i++) + for (struct fork_handler *it = fork_handlers.first; it != NULL; + it = it->next) { - 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 (); - } + if (who == atfork_run_child && it->child_handler != NULL) + it->child_handler (); + else if (who == atfork_run_parent && it->parent_handler != NULL) + it->parent_handler (); + } if (do_locking) - lll_unlock (atfork_lock, LLL_PRIVATE); + __libc_lock_unlock_recursive (atfork_lock); } } libc_freeres_fn (free_mem) { - lll_lock (atfork_lock, LLL_PRIVATE); - - fork_handler_list_free (&fork_handlers); - - lll_unlock (atfork_lock, LLL_PRIVATE); + __libc_lock_lock_recursive (atfork_lock); + fork_handlers_remove_all (); + __libc_lock_unlock_recursive (atfork_lock); } diff --git a/nptl/tst-atfork3.c b/nptl/tst-atfork3.c new file mode 100644 index 0000000000..1435ecc86a --- /dev/null +++ b/nptl/tst-atfork3.c @@ -0,0 +1,118 @@ +/* Check if pthread_atfork handler can call dlclose (BZ#24595) + Copyright (C) 2019 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 +#include +#include + +/* 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) + TEST_COMPARE (dlclose (handler), 0); +} + +static void +parent (void) +{ + if (run_dlclose_parent) + TEST_COMPARE (dlclose (handler), 0); +} + +static void +child (void) +{ + if (run_dlclose_child) + TEST_COMPARE (dlclose (handler), 0); +} + +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 = dlopen ("tst-atfork3mod.so", RTLD_NOW); + TEST_VERIFY (handler != NULL); + + int (*atfork3mod_func)(void); + atfork3mod_func = dlsym (handler, "atfork3mod_func"); + TEST_VERIFY (atfork3mod_func != NULL); + + 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; +} + +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 diff --git a/nptl/tst-atfork3mod.c b/nptl/tst-atfork3mod.c new file mode 100644 index 0000000000..7449962e57 --- /dev/null +++ b/nptl/tst-atfork3mod.c @@ -0,0 +1,45 @@ +/* Copyright (C) 2003-2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper , 2003. + + 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 + +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/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h index 99ed76034b..c742252f8c 100644 --- a/sysdeps/nptl/fork.h +++ b/sysdeps/nptl/fork.h @@ -31,6 +31,8 @@ struct fork_handler void (*parent_handler) (void); void (*child_handler) (void); void *dso_handle; + struct fork_handler *next; + struct fork_handler *prev; }; /* Function to call to unregister fork handlers. */