about summary refs log tree commit diff
path: root/sysdeps/unix/sysv/linux/timer_create.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/unix/sysv/linux/timer_create.c')
-rw-r--r--sysdeps/unix/sysv/linux/timer_create.c292
1 files changed, 192 insertions, 100 deletions
diff --git a/sysdeps/unix/sysv/linux/timer_create.c b/sysdeps/unix/sysv/linux/timer_create.c
index b7e84e2bb1..4d73902589 100644
--- a/sysdeps/unix/sysv/linux/timer_create.c
+++ b/sysdeps/unix/sysv/linux/timer_create.c
@@ -15,46 +15,198 @@
    License along with the GNU C Library; see the file COPYING.LIB.  If
    not, see <https://www.gnu.org/licenses/>.  */
 
-#include <errno.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <sysdep.h>
-#include <internaltypes.h>
+#include <jmpbuf-unwind.h>
+#include <kernel-posix-cpu-timers.h>
+#include <kernel-posix-timers.h>
+#include <ldsodefs.h>
+#include <libc-internal.h>
+#include <libc-lock.h>
 #include <pthreadP.h>
-#include "kernel-posix-timers.h"
-#include "kernel-posix-cpu-timers.h"
 #include <shlib-compat.h>
 
+struct timer_helper_thread_args_t
+{
+  /* The barrier is used to synchronize the arguments copy from timer_created
+     to the timer thread.  */
+  pthread_barrier_t b;
+  struct sigevent *evp;
+};
+
+extern _Noreturn void __longjmp_cancel (__jmp_buf __env, int __val)
+  attribute_hidden;
+
+struct cleanup_args_t
+{
+  struct pthread_unwind_buf *cleanup_jmp_buf;
+  jmp_buf jb;
+};
+
+static void
+timer_helper_thread_cleanup (void *arg)
+{
+  struct pthread *self = THREAD_SELF;
+
+  /* Call destructors for the thread_local TLS variables.  */
+#ifndef SHARED
+  if (&__call_tls_dtors != NULL)
+#endif
+    __call_tls_dtors ();
+
+  /* Run the destructor for the thread-local data.  */
+  __nptl_deallocate_tsd ();
+
+  /* Clean up any state libc stored in thread-local variables.  */
+  __libc_thread_freeres ();
+
+  /* Reset internal TCB state.  */
+  struct cleanup_args_t *args = arg;
+  self->cleanup_jmp_buf = args->cleanup_jmp_buf;
+  self->cleanup_jmp_buf->priv.data.prev = NULL;
+  self->cleanup_jmp_buf->priv.data.cleanup = NULL;
+  self->cleanup_jmp_buf->priv.data.canceltype = 0;
+  self->cleanup = NULL;
+  self->exc = (struct _Unwind_Exception) { 0 };
+  self->cancelhandling = 0;
+  self->nextevent = NULL;
+
+  /* Re-initialize the TLS.  */
+  _dl_allocate_tls_init (TLS_TPADJ (self), true);
+
+  internal_sigset_t ss;
+  internal_sigfillset (&ss);
+  internal_sigdelset (&ss, SIGSETXID);
+  internal_sigprocmask (SIG_SETMASK, &ss, NULL);
+
+  /* There is no need to perform any additional cleanup by the frames.  */
+  struct __jmp_buf_tag *env = args->jb;
+  __longjmp (env[0].__jmpbuf, 1);
+}
+
+static void *
+timer_helper_thread (void *arg)
+{
+  struct pthread *self = THREAD_SELF;
+
+  struct timer_helper_thread_args_t *args = arg;
+
+  void (*thrfunc) (sigval_t) = args->evp->sigev_notify_function;
+  sigval_t sival = args->evp->sigev_value;
+  __pthread_barrier_wait (&args->b);
+
+  /* timer_create failed.  */
+  if (self->timerid < 0)
+    return 0;
+
+  struct cleanup_args_t clargs = {
+    .cleanup_jmp_buf = self->cleanup_jmp_buf
+  };
+
+  while (1)
+    {
+      siginfo_t si;
+      while (__sigwaitinfo (&sigtimer_set, &si) < 0) {};
+
+      if (si.si_code == SI_TIMER && !setjmp (clargs.jb))
+	{
+	  pthread_cleanup_push (timer_helper_thread_cleanup, &clargs);
+	  thrfunc (sival);
+	  pthread_cleanup_pop (0);
+	}
+
+      /* timer_delete will set the MSB and signal the thread.  */
+      if (atomic_load_relaxed (&self->timerid) < 0)
+	break;
+    }
+
+  /* Clear the MSB bit set by timer_delete.  */
+  INTERNAL_SYSCALL_CALL (timer_delete, self->timerid & INT_MAX);
+
+  return NULL;
+}
+
+static int
+timer_create_sigev_thread (clockid_t syscall_clockid, struct sigevent *evp,
+			   timer_t *timerid)
+{
+  int ret = -1;
+
+  pthread_attr_t attr;
+  if (evp->sigev_notify_attributes != NULL)
+    __pthread_attr_copy (&attr, evp->sigev_notify_attributes);
+  else
+    __pthread_attr_init (&attr);
+  __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+
+  /* Block all signals in the helper thread but SIGSETXID.  */
+  sigset_t ss;
+  __sigfillset (&ss);
+  __sigdelset (&ss, SIGSETXID);
+  ret = __pthread_attr_setsigmask_internal (&attr, &ss);
+  if (ret != 0)
+    goto out;
+
+  struct timer_helper_thread_args_t args;
+  __pthread_barrier_init (&args.b, NULL, 2);
+  args.evp = evp;
+
+  pthread_t th;
+  ret = __pthread_create (&th, &attr, timer_helper_thread, &args);
+  if (ret != 0)
+    {
+      __set_errno (ret);
+      goto out;
+    }
+
+  struct sigevent kevp = {
+    .sigev_value.sival_ptr = NULL,
+    .sigev_signo = SIGTIMER,
+    .sigev_notify = SIGEV_THREAD_ID,
+    ._sigev_un = { ._pad = { [0] = ((struct pthread *)th)->tid } }
+  };
+
+  struct pthread *pthr = (struct pthread *)th;
+
+  kernel_timer_t ktimerid;
+  if (INLINE_SYSCALL_CALL (timer_create, syscall_clockid, &kevp, &ktimerid) < 0)
+    pthr->timerid = -1;
+  pthr->timerid = ktimerid;
+  /* Signal the thread to continue execution after it copies the arguments
+     or exit if the timer can not be created.  */
+  __pthread_barrier_wait (&args.b);
+
+  if (timerid >= 0)
+    *timerid = pthread_to_timerid (th);
+
+  ret = 0;
+out:
+  if (&attr != evp->sigev_notify_attributes)
+    __pthread_attr_destroy (&attr);
+
+  return ret;
+}
+
 int
 ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
 {
-  {
-    clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
-				 ? PROCESS_CLOCK
-				 : clock_id == CLOCK_THREAD_CPUTIME_ID
-				 ? THREAD_CLOCK
-				 : clock_id);
-
-    /* If the user wants notification via a thread we need to handle
-       this special.  */
-    if (evp == NULL
-	|| __builtin_expect (evp->sigev_notify != SIGEV_THREAD, 1))
-      {
-	struct sigevent local_evp;
+  clockid_t syscall_clockid = (clock_id == CLOCK_PROCESS_CPUTIME_ID
+			       ? PROCESS_CLOCK
+			       : clock_id == CLOCK_THREAD_CPUTIME_ID
+			       ? THREAD_CLOCK
+			       : clock_id);
 
+  switch (evp != NULL ? evp->sigev_notify : SIGEV_SIGNAL)
+    {
+    case SIGEV_NONE:
+    case SIGEV_SIGNAL:
+    case SIGEV_THREAD_ID:
+      {
+	struct sigevent kevp;
 	if (evp == NULL)
 	  {
-	    /* The kernel has to pass up the timer ID which is a
-	       userlevel object.  Therefore we cannot leave it up to
-	       the kernel to determine it.  */
-	    local_evp.sigev_notify = SIGEV_SIGNAL;
-	    local_evp.sigev_signo = SIGALRM;
-	    local_evp.sigev_value.sival_ptr = NULL;
-
-	    evp = &local_evp;
+	    kevp.sigev_notify = SIGEV_SIGNAL;
+	    kevp.sigev_signo = SIGALRM;
+	    kevp.sigev_value.sival_ptr = NULL;
+	    evp = &kevp;
 	  }
 
 	kernel_timer_t ktimerid;
@@ -64,75 +216,15 @@ ___timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
 
 	*timerid = kernel_timer_to_timerid (ktimerid);
       }
-    else
-      {
-	/* Create the helper thread.  */
-	__pthread_once (&__timer_helper_once, __timer_start_helper_thread);
-	if (__timer_helper_tid == 0)
-	  {
-	    /* No resources to start the helper thread.  */
-	    __set_errno (EAGAIN);
-	    return -1;
-	  }
-
-	struct timer *newp = malloc (sizeof (struct timer));
-	if (newp == NULL)
-	  return -1;
-
-	/* Copy the thread parameters the user provided.  */
-	newp->sival = evp->sigev_value;
-	newp->thrfunc = evp->sigev_notify_function;
-
-	/* We cannot simply copy the thread attributes since the
-	   implementation might keep internal information for
-	   each instance.  */
-	__pthread_attr_init (&newp->attr);
-	if (evp->sigev_notify_attributes != NULL)
-	  {
-	    struct pthread_attr *nattr;
-	    struct pthread_attr *oattr;
-
-	    nattr = (struct pthread_attr *) &newp->attr;
-	    oattr = (struct pthread_attr *) evp->sigev_notify_attributes;
-
-	    nattr->schedparam = oattr->schedparam;
-	    nattr->schedpolicy = oattr->schedpolicy;
-	    nattr->flags = oattr->flags;
-	    nattr->guardsize = oattr->guardsize;
-	    nattr->stackaddr = oattr->stackaddr;
-	    nattr->stacksize = oattr->stacksize;
-	  }
-
-	/* In any case set the detach flag.  */
-	__pthread_attr_setdetachstate (&newp->attr, PTHREAD_CREATE_DETACHED);
-
-	/* Create the event structure for the kernel timer.  */
-	struct sigevent sev =
-	  { .sigev_value.sival_ptr = newp,
-	    .sigev_signo = SIGTIMER,
-	    .sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID,
-	    ._sigev_un = { ._pad = { [0] = __timer_helper_tid } } };
-
-	/* Create the timer.  */
-	int res;
-	res = INTERNAL_SYSCALL_CALL (timer_create, syscall_clockid, &sev,
-				     &newp->ktimerid);
-	if (INTERNAL_SYSCALL_ERROR_P (res))
-	  {
-	    free (newp);
-	    __set_errno (INTERNAL_SYSCALL_ERRNO (res));
-	    return -1;
-	  }
-
-	/* Add to the queue of active timers with thread delivery.  */
-	__pthread_mutex_lock (&__timer_active_sigev_thread_lock);
-	newp->next = __timer_active_sigev_thread;
-	__timer_active_sigev_thread = newp;
-	__pthread_mutex_unlock (&__timer_active_sigev_thread_lock);
-
-	*timerid = timer_to_timerid (newp);
-      }
-  }
+      break;
+    case SIGEV_THREAD:
+      if (timer_create_sigev_thread (syscall_clockid, evp, timerid) < 0)
+	return -1;
+      break;
+    default:
+      __set_errno (EINVAL);
+      return -1;
+    }
 
   return 0;
 }