summary refs log tree commit diff
path: root/hurd/hurdsig.c
diff options
context:
space:
mode:
Diffstat (limited to 'hurd/hurdsig.c')
-rw-r--r--hurd/hurdsig.c280
1 files changed, 217 insertions, 63 deletions
diff --git a/hurd/hurdsig.c b/hurd/hurdsig.c
index 5ac8c2a203..13a81ed189 100644
--- a/hurd/hurdsig.c
+++ b/hurd/hurdsig.c
@@ -46,9 +46,6 @@ mach_port_t _hurd_msgport;
 /* Thread listening on it.  */
 thread_t _hurd_msgport_thread;
 
-/* Thread which receives task-global signals.  */
-thread_t _hurd_sigthread;
-
 /* These are set up by _hurdsig_init.  */
 unsigned long int __hurd_sigthread_stack_base;
 unsigned long int __hurd_sigthread_stack_end;
@@ -56,6 +53,9 @@ unsigned long int __hurd_sigthread_stack_end;
 /* Linked-list of per-thread signal state.  */
 struct hurd_sigstate *_hurd_sigstates;
 
+/* Sigstate for the task-global signals.  */
+struct hurd_sigstate *_hurd_global_sigstate;
+
 /* Timeout for RPC's after interrupt_operation. */
 mach_msg_timeout_t _hurd_interrupted_rpc_timeout = 60000;
 
@@ -84,7 +84,7 @@ _hurd_thread_sigstate (thread_t thread)
     {
       ss = malloc (sizeof (*ss));
       if (ss == NULL)
-	__libc_fatal ("hurd: Can't allocate thread sigstate\n");
+	__libc_fatal ("hurd: Can't allocate sigstate\n");
       ss->thread = thread;
       __spin_lock_init (&ss->lock);
 
@@ -98,16 +98,19 @@ _hurd_thread_sigstate (thread_t thread)
       ss->intr_port = MACH_PORT_NULL;
       ss->context = NULL;
 
-      /* Initialize the sigaction vector from the default signal receiving
-	 thread's state, and its from the system defaults.  */
-      if (thread == _hurd_sigthread)
-	default_sigaction (ss->actions);
+      if (thread == MACH_PORT_NULL)
+	{
+	  /* Process-wide sigstate, use the system defaults.  */
+	  default_sigaction (ss->actions);
+
+	  /* The global sigstate is not added to the _hurd_sigstates list.
+	     It is created with _hurd_thread_sigstate (MACH_PORT_NULL)
+	     but should be accessed through _hurd_global_sigstate.  */
+	}
       else
 	{
-	  struct hurd_sigstate *s;
-	  for (s = _hurd_sigstates; s != NULL; s = s->next)
-	    if (s->thread == _hurd_sigthread)
-	      break;
+	  /* Use the global actions as a default for new threads.  */
+	  struct hurd_sigstate *s = _hurd_global_sigstate;
 	  if (s)
 	    {
 	      __spin_lock (&s->lock);
@@ -116,15 +119,115 @@ _hurd_thread_sigstate (thread_t thread)
 	    }
 	  else
 	    default_sigaction (ss->actions);
-	}
 
-      ss->next = _hurd_sigstates;
-      _hurd_sigstates = ss;
+	  ss->next = _hurd_sigstates;
+	  _hurd_sigstates = ss;
+	}
     }
   __mutex_unlock (&_hurd_siglock);
   return ss;
 }
 libc_hidden_def (_hurd_thread_sigstate)
+
+/* Destroy a sigstate structure.  Called by libpthread just before the
+ * corresponding thread is terminated (the kernel thread port must remain valid
+ * until this function is called.) */
+void
+_hurd_sigstate_delete (thread_t thread)
+{
+  struct hurd_sigstate **ssp, *ss;
+
+  __mutex_lock (&_hurd_siglock);
+  for (ssp = &_hurd_sigstates; *ssp; ssp = &(*ssp)->next)
+    if ((*ssp)->thread == thread)
+      break;
+
+  ss = *ssp;
+  if (ss)
+    *ssp = ss->next;
+
+  __mutex_unlock (&_hurd_siglock);
+  if (ss)
+    free (ss);
+}
+
+/* Make SS a global receiver, with pthread signal semantics.  */
+void
+_hurd_sigstate_set_global_rcv (struct hurd_sigstate *ss)
+{
+  assert (ss->thread != MACH_PORT_NULL);
+  ss->actions[0].sa_handler = SIG_IGN;
+}
+
+/* Check whether SS is a global receiver.  */
+static int
+sigstate_is_global_rcv (const struct hurd_sigstate *ss)
+{
+  return (_hurd_global_sigstate != NULL)
+	 && (ss->actions[0].sa_handler == SIG_IGN);
+}
+libc_hidden_def (_hurd_sigstate_delete)
+
+/* Lock/unlock a hurd_sigstate structure.  If the accessors below require
+   it, the global sigstate will be locked as well.  */
+void
+_hurd_sigstate_lock (struct hurd_sigstate *ss)
+{
+  if (sigstate_is_global_rcv (ss))
+    __spin_lock (&_hurd_global_sigstate->lock);
+  __spin_lock (&ss->lock);
+}
+void
+_hurd_sigstate_unlock (struct hurd_sigstate *ss)
+{
+  __spin_unlock (&ss->lock);
+  if (sigstate_is_global_rcv (ss))
+    __spin_unlock (&_hurd_global_sigstate->lock);
+}
+libc_hidden_def (_hurd_sigstate_set_global_rcv)
+
+/* Retreive a thread's full set of pending signals, including the global
+   ones if appropriate.  SS must be locked.  */
+sigset_t
+_hurd_sigstate_pending (const struct hurd_sigstate *ss)
+{
+  sigset_t pending = ss->pending;
+  if (sigstate_is_global_rcv (ss))
+    __sigorset (&pending, &pending, &_hurd_global_sigstate->pending);
+  return pending;
+}
+
+/* Clear a pending signal and return the associated detailed
+   signal information. SS must be locked, and must have signal SIGNO
+   pending, either directly or through the global sigstate.  */
+static struct hurd_signal_detail
+sigstate_clear_pending (struct hurd_sigstate *ss, int signo)
+{
+  if (sigstate_is_global_rcv (ss)
+      && __sigismember (&_hurd_global_sigstate->pending, signo))
+    {
+      __sigdelset (&_hurd_global_sigstate->pending, signo);
+      return _hurd_global_sigstate->pending_data[signo];
+    }
+
+  assert (__sigismember (&ss->pending, signo));
+  __sigdelset (&ss->pending, signo);
+  return ss->pending_data[signo];
+}
+libc_hidden_def (_hurd_sigstate_lock)
+libc_hidden_def (_hurd_sigstate_unlock)
+
+/* Retreive a thread's action vector.  SS must be locked.  */
+struct sigaction *
+_hurd_sigstate_actions (struct hurd_sigstate *ss)
+{
+  if (sigstate_is_global_rcv (ss))
+    return _hurd_global_sigstate->actions;
+  else
+    return ss->actions;
+}
+libc_hidden_def (_hurd_sigstate_pending)
+
 
 /* Signal delivery itself is on this page.  */
 
@@ -219,6 +322,8 @@ static void
 abort_thread (struct hurd_sigstate *ss, struct machine_thread_all_state *state,
 	      void (*reply) (void))
 {
+  assert (ss->thread != MACH_PORT_NULL);
+
   if (!(state->set & THREAD_ABORTED))
     {
       error_t err = __thread_abort (ss->thread);
@@ -367,7 +472,7 @@ _hurdsig_abort_rpcs (struct hurd_sigstate *ss, int signo, int sigthread,
 	   call above will retry their RPCs unless we clear SS->intr_port.
 	   So we clear it for the thread taking a signal when SA_RESTART is
 	   clear, so that its call returns EINTR.  */
-	if (! signo || !(ss->actions[signo].sa_flags & SA_RESTART))
+	if (! signo || !(_hurd_sigstate_actions (ss) [signo].sa_flags & SA_RESTART))
 	  ss->intr_port = MACH_PORT_NULL;
       }
 
@@ -490,9 +595,11 @@ weak_alias (_hurdsig_preemptors, _hurdsig_preempters)
 		  | sigmask (SIGSTOP) | sigmask (SIGTSTP))
 
 /* Actual delivery of a single signal.  Called with SS unlocked.  When
-   the signal is delivered, return 1 with SS locked.  If the signal is
-   being traced, return 0 with SS unlocked.   */
-static int
+   the signal is delivered, return SS, locked (or, if SS was originally
+   _hurd_global_sigstate, the sigstate of the actual thread the signal
+   was delivered to).  If the signal is being traced, return NULL with
+   SS unlocked.   */
+static struct hurd_sigstate *
 post_signal (struct hurd_sigstate *ss,
 	     int signo, struct hurd_signal_detail *detail,
 	     int untraced, void (*reply) (void))
@@ -545,8 +652,12 @@ post_signal (struct hurd_sigstate *ss,
       assert_perror (err);
       for (i = 0; i < nthreads; ++i)
 	{
-	  if (threads[i] != _hurd_msgport_thread
-	      && (act != handle || threads[i] != ss->thread))
+	  if (act == handle && threads[i] == ss->thread)
+	    {
+	      /* The thread that will run the handler is kept suspended.  */
+	      ss_suspended = 1;
+	    }
+	  else if (threads[i] != _hurd_msgport_thread)
 	    {
 	      err = __thread_resume (threads[i]);
 	      assert_perror (err);
@@ -559,9 +670,6 @@ post_signal (struct hurd_sigstate *ss,
 		       (vm_address_t) threads,
 		       nthreads * sizeof *threads);
       _hurd_stopped = 0;
-      if (act == handle)
-	/* The thread that will run the handler is already suspended.  */
-	ss_suspended = 1;
     }
 
   error_t err;
@@ -577,13 +685,43 @@ post_signal (struct hurd_sigstate *ss,
 	}
 
       /* This call is just to check for pending signals.  */
-      __spin_lock (&ss->lock);
-      return 1;
+      _hurd_sigstate_lock (ss);
+      return ss;
     }
 
   thread_state.set = 0;		/* We know nothing.  */
 
-  __spin_lock (&ss->lock);
+  _hurd_sigstate_lock (ss);
+
+  /* If this is a global signal, try to find a thread ready to accept
+     it right away.  This is especially important for untraced signals,
+     since going through the global pending mask would de-untrace them.  */
+  if (ss->thread == MACH_PORT_NULL)
+  {
+    struct hurd_sigstate *rss;
+
+    __mutex_lock (&_hurd_siglock);
+    for (rss = _hurd_sigstates; rss != NULL; rss = rss->next)
+      {
+	if (! sigstate_is_global_rcv (rss))
+	  continue;
+
+	/* The global sigstate is already locked.  */
+	__spin_lock (&rss->lock);
+	if (! __sigismember (&rss->blocked, signo))
+	  {
+	    ss = rss;
+	    break;
+	  }
+	__spin_unlock (&rss->lock);
+      }
+    __mutex_unlock (&_hurd_siglock);
+  }
+
+  /* We want the preemptors to be able to update the blocking mask
+     without affecting the delivery of this signal, so we save the
+     current value to test against later.  */
+  sigset_t blocked = ss->blocked;
 
   /* Check for a preempted signal.  Preempted signals can arrive during
      critical sections.  */
@@ -641,12 +779,12 @@ post_signal (struct hurd_sigstate *ss,
 	    mark_pending ();
 	  else
 	    suspend ();
-	  __spin_unlock (&ss->lock);
+	  _hurd_sigstate_unlock (ss);
 	  reply ();
-	  return 0;
+	  return NULL;
 	}
 
-      handler = ss->actions[signo].sa_handler;
+      handler = _hurd_sigstate_actions (ss) [signo].sa_handler;
 
       if (handler == SIG_DFL)
 	/* Figure out the default action for this signal.  */
@@ -739,9 +877,7 @@ post_signal (struct hurd_sigstate *ss,
     }
 
   /* Handle receipt of a blocked signal, or any signal while stopped.  */
-  if (act != ignore		/* Signals ignored now are forgotten now.  */
-      && __sigismember (&ss->blocked, signo)
-      || (signo != SIGKILL && _hurd_stopped))
+  if (__sigismember (&blocked, signo) || (signo != SIGKILL && _hurd_stopped))
     {
       mark_pending ();
       act = ignore;
@@ -776,6 +912,7 @@ post_signal (struct hurd_sigstate *ss,
 	 now's the time to set it going. */
       if (ss_suspended)
 	{
+	  assert (ss->thread != MACH_PORT_NULL);
 	  err = __thread_resume (ss->thread);
 	  assert_perror (err);
 	  ss_suspended = 0;
@@ -820,6 +957,8 @@ post_signal (struct hurd_sigstate *ss,
 	struct sigcontext *scp, ocontext;
 	int wait_for_reply, state_changed;
 
+	assert (ss->thread != MACH_PORT_NULL);
+
 	/* Stop the thread and abort its pending RPC operations.  */
 	if (! ss_suspended)
 	  {
@@ -956,23 +1095,25 @@ post_signal (struct hurd_sigstate *ss,
 	    }
 	}
 
+	struct sigaction *action = & _hurd_sigstate_actions (ss) [signo];
+
 	/* Backdoor extra argument to signal handler.  */
 	scp->sc_error = detail->error;
 
 	/* Block requested signals while running the handler.  */
 	scp->sc_mask = ss->blocked;
-	__sigorset (&ss->blocked, &ss->blocked, &ss->actions[signo].sa_mask);
+	__sigorset (&ss->blocked, &ss->blocked, &action->sa_mask);
 
 	/* Also block SIGNO unless we're asked not to.  */
-	if (! (ss->actions[signo].sa_flags & (SA_RESETHAND | SA_NODEFER)))
+	if (! (action->sa_flags & (SA_RESETHAND | SA_NODEFER)))
 	  __sigaddset (&ss->blocked, signo);
 
 	/* Reset to SIG_DFL if requested.  SIGILL and SIGTRAP cannot
            be automatically reset when delivered; the system silently
            enforces this restriction.  */
-	if (ss->actions[signo].sa_flags & SA_RESETHAND
+	if (action->sa_flags & SA_RESETHAND
 	    && signo != SIGILL && signo != SIGTRAP)
-	  ss->actions[signo].sa_handler = SIG_DFL;
+	  action->sa_handler = SIG_DFL;
 
 	/* Any sigsuspend call must return after the handler does.  */
 	wake_sigsuspend (ss);
@@ -990,7 +1131,7 @@ post_signal (struct hurd_sigstate *ss,
       }
     }
 
-  return 1;
+  return ss;
 }
 
 /* Return the set of pending signals in SS which should be delivered. */
@@ -1005,7 +1146,7 @@ pending_signals (struct hurd_sigstate *ss)
   if (_hurd_stopped || __spin_lock_locked (&ss->critical_section_lock))
     return 0;
 
-  return ss->pending & ~ss->blocked;
+  return _hurd_sigstate_pending (ss) & ~ss->blocked;
 }
 
 /* Post the specified pending signals in SS and return 1.  If one of
@@ -1017,12 +1158,15 @@ post_pending (struct hurd_sigstate *ss, sigset_t pending, void (*reply) (void))
   int signo;
   struct hurd_signal_detail detail;
 
+  /* Make sure SS corresponds to an actual thread, since we assume it won't
+     change in post_signal. */
+  assert (ss->thread != MACH_PORT_NULL);
+
   for (signo = 1; signo < NSIG; ++signo)
     if (__sigismember (&pending, signo))
       {
-	__sigdelset (&ss->pending, signo);
-	detail = ss->pending_data[signo];
-	__spin_unlock (&ss->lock);
+	detail = sigstate_clear_pending (ss, signo);
+	_hurd_sigstate_unlock (ss);
 
 	/* Will reacquire the lock, except if the signal is traced.  */
 	if (! post_signal (ss, signo, &detail, 0, reply))
@@ -1030,7 +1174,7 @@ post_pending (struct hurd_sigstate *ss, sigset_t pending, void (*reply) (void))
       }
 
   /* No more signals pending; SS->lock is still locked.  */
-  __spin_unlock (&ss->lock);
+  _hurd_sigstate_unlock (ss);
 
   return 1;
 }
@@ -1048,14 +1192,14 @@ post_all_pending_signals (void (*reply) (void))
       __mutex_lock (&_hurd_siglock);
       for (ss = _hurd_sigstates; ss != NULL; ss = ss->next)
         {
-	  __spin_lock (&ss->lock);
+	  _hurd_sigstate_lock (ss);
 
 	  pending = pending_signals (ss);
 	  if (pending)
 	    /* post_pending() below will unlock SS. */
 	    break;
 
-	  __spin_unlock (&ss->lock);
+	  _hurd_sigstate_unlock (ss);
 	}
       __mutex_unlock (&_hurd_siglock);
 
@@ -1088,11 +1232,12 @@ _hurd_internal_post_signal (struct hurd_sigstate *ss,
 	assert_perror (err);
     }
 
-  if (! post_signal (ss, signo, detail, untraced, reply))
+  ss = post_signal (ss, signo, detail, untraced, reply);
+  if (! ss)
     return;
 
   /* The signal was neither fatal nor traced.  We still hold SS->lock.  */
-  if (signo != 0)
+  if (signo != 0 && ss->thread != MACH_PORT_NULL)
     {
       /* The signal has either been ignored or is now being handled.  We can
 	 consider it delivered and reply to the killer.  */
@@ -1104,8 +1249,9 @@ _hurd_internal_post_signal (struct hurd_sigstate *ss,
     }
   else
     {
-      /* We need to check for pending signals for all threads.  */
-      __spin_unlock (&ss->lock);
+      /* If this was a process-wide signal or a poll request, we need
+	 to check for pending signals for all threads.  */
+      _hurd_sigstate_unlock (ss);
       if (! post_all_pending_signals (reply))
 	return;
 
@@ -1231,9 +1377,10 @@ _S_msg_sig_post (mach_port_t me,
   d.code = sigcode;
   d.exc = 0;
 
-  /* Post the signal to the designated signal-receiving thread.  This will
-     reply when the signal can be considered delivered.  */
-  _hurd_internal_post_signal (_hurd_thread_sigstate (_hurd_sigthread),
+  /* Post the signal to a global receiver thread (or mark it pending in
+     the global sigstate).  This will reply when the signal can be
+     considered delivered.  */
+  _hurd_internal_post_signal (_hurd_global_sigstate,
 			      signo, &d, reply_port, reply_port_type,
 			      0); /* Stop if traced.  */
 
@@ -1261,7 +1408,7 @@ _S_msg_sig_post_untraced (mach_port_t me,
 
   /* Post the signal to the designated signal-receiving thread.  This will
      reply when the signal can be considered delivered.  */
-  _hurd_internal_post_signal (_hurd_thread_sigstate (_hurd_sigthread),
+  _hurd_internal_post_signal (_hurd_global_sigstate,
 			      signo, &d, reply_port, reply_port_type,
 			      1); /* Untraced flag. */
 
@@ -1272,8 +1419,8 @@ extern void __mig_init (void *);
 
 #include <mach/task_special_ports.h>
 
-/* Initialize the message port and _hurd_sigthread and start the signal
-   thread.  */
+/* Initialize the message port, _hurd_global_sigstate, and start the
+   signal thread.  */
 
 void
 _hurdsig_init (const int *intarray, size_t intarraysize)
@@ -1296,27 +1443,34 @@ _hurdsig_init (const int *intarray, size_t intarraysize)
 				  MACH_MSG_TYPE_MAKE_SEND);
   assert_perror (err);
 
+  /* Initialize the global signal state.  */
+  _hurd_global_sigstate = _hurd_thread_sigstate (MACH_PORT_NULL);
+
+  /* We block all signals, and let actual threads pull them from the
+     pending mask.  */
+  __sigfillset(& _hurd_global_sigstate->blocked);
+
   /* Initialize the main thread's signal state.  */
   ss = _hurd_self_sigstate ();
 
-  /* Copy inherited values from our parent (or pre-exec process state)
-     into the signal settings of the main thread.  */
+  /* Mark it as a process-wide signal receiver.  Threads in this set use
+     the common action vector in _hurd_global_sigstate.  */
+  _hurd_sigstate_set_global_rcv (ss);
+
+  /* Copy inherited signal settings from our parent (or pre-exec process
+     state) */
   if (intarraysize > INIT_SIGMASK)
     ss->blocked = intarray[INIT_SIGMASK];
   if (intarraysize > INIT_SIGPENDING)
-    ss->pending = intarray[INIT_SIGPENDING];
+    _hurd_global_sigstate->pending = intarray[INIT_SIGPENDING];
   if (intarraysize > INIT_SIGIGN && intarray[INIT_SIGIGN] != 0)
     {
       int signo;
       for (signo = 1; signo < NSIG; ++signo)
 	if (intarray[INIT_SIGIGN] & __sigmask(signo))
-	  ss->actions[signo].sa_handler = SIG_IGN;
+	  _hurd_global_sigstate->actions[signo].sa_handler = SIG_IGN;
     }
 
-  /* Set the default thread to receive task-global signals
-     to this one, the main (first) user thread.  */
-  _hurd_sigthread = ss->thread;
-
   /* Start the signal thread listening on the message port.  */
 
 #pragma weak __cthread_fork