summary refs log tree commit diff
path: root/sysdeps/mach/hurd/setitimer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/mach/hurd/setitimer.c')
-rw-r--r--sysdeps/mach/hurd/setitimer.c332
1 files changed, 332 insertions, 0 deletions
diff --git a/sysdeps/mach/hurd/setitimer.c b/sysdeps/mach/hurd/setitimer.c
new file mode 100644
index 0000000000..4351c5ea99
--- /dev/null
+++ b/sysdeps/mach/hurd/setitimer.c
@@ -0,0 +1,332 @@
+/* Copyright (C) 1994, 1995 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 Library General Public License as
+published by the Free Software Foundation; either version 2 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+#include <ansidecl.h>
+#include <stddef.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <hurd.h>
+#include <hurd/signal.h>
+#include <hurd/msg_request.h>
+#include <mach/message.h>
+
+/* XXX Temporary cheezoid implementation of ITIMER_REAL/SIGALRM.  */
+
+spin_lock_t _hurd_itimer_lock = SPIN_LOCK_INITIALIZER;
+struct itimerval _hurd_itimerval; /* Current state of the timer.  */
+mach_port_t _hurd_itimer_port;	/* Port the timer thread blocks on.  */
+thread_t _hurd_itimer_thread;	/* Thread waiting for timeout.  */
+int _hurd_itimer_thread_suspended; /* Nonzero if that thread is suspended.  */
+vm_address_t _hurd_itimer_thread_stack_base; /* Base of its stack.  */
+vm_address_t _hurd_itimer_thread_stack_size; /* Size of its stack.  */
+struct timeval _hurd_itimer_started; /* Time the thread started waiting.  */
+
+static inline void
+subtract_timeval (struct timeval *from, const struct timeval *subtract)
+{
+  from->tv_usec -= subtract->tv_usec;
+  from->tv_sec -= subtract->tv_sec;
+  while (from->tv_usec < 0)
+    {
+      --from->tv_sec;
+      from->tv_usec += 1000000;
+    }
+}
+
+/* Function run by the itimer thread.
+   This code must be very careful not ever to require a MiG reply port.  */
+
+static void
+timer_thread (void)
+{
+  while (1)
+    {
+      error_t err;
+      /* The only message we ever expect to receive is the reply from the
+         signal thread to a sig_post call we did.  We never examine the
+	 contents.  */
+      struct
+	{
+	  mach_msg_header_t header;
+	  error_t return_code;
+	} msg;
+
+      /* Wait for a message on a port that noone sends to.  The purpose is
+	 the receive timeout.  Notice interrupts so that if we are
+	 thread_abort'd, we will loop around and fetch new values from
+	 _hurd_itimerval.  */
+      err = __mach_msg (&msg.header,
+			MACH_RCV_MSG|MACH_RCV_TIMEOUT|MACH_RCV_INTERRUPT,
+			0, 0, _hurd_itimer_port,
+			_hurd_itimerval.it_value.tv_sec * 1000 +
+			_hurd_itimerval.it_value.tv_usec / 1000,
+			MACH_PORT_NULL);
+      switch (err)
+	{
+	case MACH_RCV_TIMED_OUT:
+	  /* We got the expected timeout.  Send a message to the signal
+	     thread to tell it to post a SIGALRM signal.  We use
+	     _hurd_itimer_port as the reply port just so we will block until
+	     the signal thread has frobnicated things to reload the itimer or
+	     has terminated this thread.  */
+	  __msg_sig_post_request (_hurd_msgport,
+				  _hurd_itimer_port,
+				  MACH_MSG_TYPE_MAKE_SEND_ONCE,
+				  SIGALRM, __mach_task_self ());
+	  break;
+
+	case MACH_RCV_INTERRUPTED:
+	  /* We were thread_abort'd.  This is to tell us that
+	     _hurd_itimerval has changed and we need to reexamine it
+	     and start waiting with the new timeout value.  */
+	  break;
+
+	case MACH_MSG_SUCCESS:
+	  /* We got the reply message from the sig_post_request above.
+	     Ignore it and reexamine the timer value.  */
+	  __mach_msg_destroy (&msg.header); /* Just in case.  */
+	  break;
+
+	default:
+	  /* Unexpected lossage.  Oh well, keep trying.  */
+	  break;
+	}
+    }
+}
+
+/* Forward declaration.  */
+static sighandler_t preempt_sigalrm (thread_t thread, int signo,
+				     long int sigcode, int sigerror);
+
+/* Called before any normal SIGALRM signal is delivered.
+   Reload the itimer, or disable the itimer.  */
+
+static int
+setitimer_locked (const struct itimerval *new, struct itimerval *old,
+		  void *crit)
+{
+  struct itimerval newval = *new;
+  struct timeval now, remaining, elapsed;
+  struct timeval old_interval;
+  error_t err;
+
+  inline void kill_itimer_thread (void)
+    {
+      __thread_terminate (_hurd_itimer_thread);
+      __vm_deallocate (__mach_task_self (),
+		       _hurd_itimer_thread_stack_base,
+		       _hurd_itimer_thread_stack_size);
+      _hurd_itimer_thread = MACH_PORT_NULL;
+    }
+
+  if ((newval.it_value.tv_sec | newval.it_value.tv_usec) != 0)
+    {
+      /* Make sure the itimer thread is set up.  */
+
+      if (_hurd_signal_preempt[SIGALRM] == NULL)
+	{
+	  static struct hurd_signal_preempt preempt =
+	    { preempt_sigalrm, 0, 0, NULL };
+	  _hurd_signal_preempt[SIGALRM] = &preempt;
+	}
+
+      if (_hurd_itimer_port == MACH_PORT_NULL)
+	{
+	  /* Allocate a receive right that the itimer thread will
+	     block waiting for a message on.  */
+	  if (err = __mach_port_allocate (__mach_task_self (),
+					  MACH_PORT_RIGHT_RECEIVE,
+					  &_hurd_itimer_port))
+	    goto out;
+	}
+
+      if (_hurd_itimer_thread == MACH_PORT_NULL)
+	{
+	  /* Start up the itimer thread running `timer_thread' (below).  */
+	  if (err = __thread_create (__mach_task_self (),
+				     &_hurd_itimer_thread))
+	    return __hurd_fail (err);
+	  _hurd_itimer_thread_stack_base = 0; /* Anywhere.  */
+	  _hurd_itimer_thread_stack_size = __vm_page_size; /* Small stack.  */
+	  if (err = __mach_setup_thread (__mach_task_self (),
+					 _hurd_itimer_thread,
+					 &timer_thread,
+					 &_hurd_itimer_thread_stack_base,
+					 &_hurd_itimer_thread_stack_size))
+	    {
+	      __thread_terminate (_hurd_itimer_thread);
+	      _hurd_itimer_thread = MACH_PORT_NULL;	  
+	      goto out;
+	    }
+	  _hurd_itimer_thread_suspended = 1;
+	}
+    }
+
+  if ((newval.it_value.tv_sec | newval.it_value.tv_usec) != 0 || old != NULL)
+    {
+      /* Calculate how much time is remaining for the pending alarm.  */
+      if (__gettimeofday (&now, NULL) < 0)
+	{
+	  __spin_unlock (&_hurd_itimer_lock);
+	  _hurd_critical_section_unlock (crit);
+	  return -1;
+	}
+      elapsed = now;
+      subtract_timeval (&elapsed, &_hurd_itimer_started);
+      remaining = _hurd_itimerval.it_value;
+      if (timercmp (&remaining, &elapsed, <))
+	{
+	  /* Hmm.  The timer should have just gone off, but has not been reset.
+	     This is a possible timing glitch.  The alarm will signal soon. */
+	  /* XXX wrong */
+	  remaining.tv_sec = 0;
+	  remaining.tv_usec = 0;
+	}
+      else
+	subtract_timeval (&remaining, &elapsed);
+
+      /* Remember the old reload interval before changing it.  */
+      old_interval = _hurd_itimerval.it_interval;
+
+      /* Record the starting time that the timer interval relates to.  */
+      _hurd_itimer_started = now;
+    }
+
+  /* Load the new itimer value.  */
+  _hurd_itimerval = newval;
+
+  if ((newval.it_value.tv_sec | newval.it_value.tv_usec) == 0)
+    {
+      /* Disable the itimer.  */
+      if (_hurd_itimer_thread && !_hurd_itimer_thread_suspended)
+	{
+	  /* Suspend the itimer thread so it does nothing.  Then abort its
+	     kernel context so that when the thread is resumed, mach_msg
+	     will return to timer_thread (below) and it will fetch new
+	     values from _hurd_itimerval.  */
+	  if ((err = __thread_suspend (_hurd_itimer_thread)) ||
+	      (err = __thread_abort (_hurd_itimer_thread)))
+	    /* If we can't save it for later, nuke it.  */
+	    kill_itimer_thread ();
+	  else
+	    _hurd_itimer_thread_suspended = 1;
+	}
+    }
+  /* See if the timeout changed.  If so, we must alert the itimer thread.  */
+  else if (remaining.tv_sec != new->it_value.tv_sec ||
+	   remaining.tv_usec != new->it_value.tv_usec)
+    {
+      /* The timeout value is changing.  Tell the itimer thread to
+	 reexamine it and start counting down.  If the itimer thread is
+	 marked as suspended, either we just created it, or it was
+	 suspended and thread_abort'd last time the itimer was disabled;
+	 either way it will wake up and start waiting for the new timeout
+	 value when we resume it.  If it is not suspended, the itimer
+	 thread is waiting to deliver a pending alarm that we will override
+	 (since it would come later than the new alarm being set);
+	 thread_abort will make mach_msg return MACH_RCV_INTERRUPTED, so it
+	 will loop around and use the new timeout value.  */
+      if (err = (_hurd_itimer_thread_suspended
+		 ? __thread_resume : __thread_abort) (_hurd_itimer_thread))
+	{
+	  kill_itimer_thread ();
+	  goto out;
+	}
+      _hurd_itimer_thread_suspended = 0;
+    }
+
+  __spin_unlock (&_hurd_itimer_lock);
+  _hurd_critical_section_unlock (crit);
+
+  if (old != NULL)
+    {
+      old->it_value = remaining;
+      old->it_interval = old_interval;
+    }
+  return 0;
+
+ out:
+  __spin_unlock (&_hurd_itimer_lock);
+  _hurd_critical_section_unlock (crit);
+  return __hurd_fail (err);
+}
+
+/* Set the timer WHICH to *NEW.  If OLD is not NULL,
+   set *OLD to the old value of timer WHICH.
+   Returns 0 on success, -1 on errors.  */
+int
+DEFUN(__setitimer, (which, new, old),
+      enum __itimer_which which AND
+      struct itimerval *new AND struct itimerval *old)
+{
+  void *crit;
+
+  switch (which)
+    {
+    default:
+      return __hurd_fail (EINVAL);
+
+    case ITIMER_VIRTUAL:
+    case ITIMER_PROF:
+      return __hurd_fail (ENOSYS);
+
+    case ITIMER_REAL:
+      break;
+    }
+
+  crit = _hurd_critical_section_lock ();
+  __spin_lock (&_hurd_itimer_lock);
+  return setitimer_locked (new, old, crit);
+}
+
+static sighandler_t
+preempt_sigalrm (thread_t thread, int signo, long int sigcode, int sigerror)
+{
+  struct itimerval it;
+
+  if (thread != _hurd_sigthread || signo != SIGALRM || sigcode != 0)
+    /* Too much monkey business.  */
+    return SIG_DFL;
+
+  /* Either reload or disable the itimer.  */
+  __spin_lock (&_hurd_itimer_lock);
+  it = _hurd_itimerval;
+  it.it_value = it.it_interval;
+  setitimer_locked (&it, NULL, NULL);
+
+  /* Continue with normal delivery of SIGALRM.  */
+  return SIG_DFL;
+}
+
+static void
+fork_itimer (void)
+{
+  /* We must restart the itimer in the child.  */
+
+  struct itimerval it;
+
+  __spin_lock (&_hurd_itimer_lock);
+  _hurd_itimer_thread = MACH_PORT_NULL;
+  it = _hurd_itimerval;
+  it.it_value = it.it_interval;
+
+  setitimer_locked (&it, NULL, NULL);
+}
+text_set_element (_hurd_fork_child_hook, fork_itimer);
+
+weak_alias (__setitimer, setitimer)