Reduce code duplication in atfork
[kopensolaris-gnu/glibc.git] / nptl / sysdeps / unix / sysv / linux / unregister-atfork.c
index 470f80d..d9a7436 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2002 Free Software Foundation, Inc.
+/* Copyright (C) 2002, 2003, 2005, 2007 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
 
 
 #include <errno.h>
 #include <stdlib.h>
-#include "fork.h"
+#include <fork.h>
+#include <atomic.h>
+#include <pthreadP.h>
 
 
 void
 __unregister_atfork (dso_handle)
      void *dso_handle;
 {
-  /* Get the lock to not conflict with running forks.  */
-  lll_lock (__fork_lock);
+  /* Check whether there is any entry in the list which we have to
+     remove.  It is likely that this is not the case so don't bother
+     getting the lock.
 
-  list_t *runp;
-  list_t *prevp;
+     We do not worry about other threads adding entries for this DSO
+     right this moment.  If this happens this is a race and we can do
+     whatever we please.  The program will crash anyway seen.  */
+  struct fork_handler *runp = __fork_handlers;
+  struct fork_handler *lastp = NULL;
 
-  list_for_each_prev_safe (runp, prevp, &__fork_prepare_list)
-    if (list_entry (runp, struct fork_handler, list)->dso_handle == dso_handle)
-      list_del (runp);
+  while (runp != NULL)
+    if (runp->dso_handle == dso_handle)
+      break;
+    else
+      {
+       lastp = runp;
+       runp = runp->next;
+      }
 
-  list_for_each_prev_safe (runp, prevp, &__fork_parent_list)
-    if (list_entry (runp, struct fork_handler, list)->dso_handle == dso_handle)
-      list_del (runp);
+  if (runp == NULL)
+    /* Nothing to do.  */
+    return;
 
-  list_for_each_prev_safe (runp, prevp, &__fork_child_list)
-    if (list_entry (runp, struct fork_handler, list)->dso_handle == dso_handle)
-      list_del (runp);
+  /* Get the lock to not conflict with additions or deletions.  Note
+     that there couldn't have been another thread deleting something.
+     The __unregister_atfork function is only called from the
+     dlclose() code which itself serializes the operations.  */
+  lll_lock (__fork_lock, LLL_PRIVATE);
+
+  /* We have to create a new list with all the entries we don't remove.  */
+  struct deleted_handler
+  {
+    struct fork_handler *handler;
+    struct deleted_handler *next;
+  } *deleted = NULL;
+
+  /* Remove the entries for the DSO which is unloaded from the list.
+     It's a single linked list so readers are.  */
+  do
+    {
+      if (runp->dso_handle == dso_handle)
+       {
+         if (lastp == NULL)
+           __fork_handlers = runp->next;
+         else
+           lastp->next = runp->next;
+
+         /* We cannot overwrite the ->next element now.  Put the deleted
+            entries in a separate list.  */
+         struct deleted_handler *newp = alloca (sizeof (*newp));
+         newp->handler = runp;
+         newp->next = deleted;
+         deleted = newp;
+       }
+      else
+       lastp = runp;
+
+      runp = runp->next;
+    }
+  while (runp != NULL);
 
   /* Release the lock.  */
-  lll_unlock (__fork_lock);
+  lll_unlock (__fork_lock, LLL_PRIVATE);
+
+  /* Walk the list of all entries which have to be deleted.  */
+  while (deleted != NULL)
+    {
+      /* We need to be informed by possible current users.  */
+      deleted->handler->need_signal = 1;
+      /* Make sure this gets written out first.  */
+      atomic_write_barrier ();
+
+      /* Decrement the reference counter.  If it does not reach zero
+        wait for the last user.  */
+      atomic_decrement (&deleted->handler->refcntr);
+      unsigned int val;
+      while ((val = deleted->handler->refcntr) != 0)
+        ; // TODO
+#if 0 // TODO
+       lll_futex_wait (&deleted->handler->refcntr, val, LLL_PRIVATE);
+#endif
+
+      deleted = deleted->next;
+    }
 }