Blame sysdeps/mach/hurd/setitimer.c

Packit 6c4009
/* Copyright (C) 1994-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <stddef.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <sys/time.h>
Packit 6c4009
#include <time.h>
Packit 6c4009
#include <hurd.h>
Packit 6c4009
#include <hurd/signal.h>
Packit 6c4009
#include <hurd/sigpreempt.h>
Packit 6c4009
#include <hurd/msg_request.h>
Packit 6c4009
#include <mach/message.h>
Packit 6c4009
Packit 6c4009
/* XXX Temporary cheezoid implementation of ITIMER_REAL/SIGALRM.  */
Packit 6c4009
Packit 6c4009
spin_lock_t _hurd_itimer_lock = SPIN_LOCK_INITIALIZER;
Packit 6c4009
struct itimerval _hurd_itimerval; /* Current state of the timer.  */
Packit 6c4009
mach_port_t _hurd_itimer_port;	/* Port the timer thread blocks on.  */
Packit 6c4009
thread_t _hurd_itimer_thread;	/* Thread waiting for timeout.  */
Packit 6c4009
int _hurd_itimer_thread_suspended; /* Nonzero if that thread is suspended.  */
Packit 6c4009
vm_address_t _hurd_itimer_thread_stack_base; /* Base of its stack.  */
Packit 6c4009
vm_size_t _hurd_itimer_thread_stack_size; /* Size of its stack.  */
Packit 6c4009
struct timeval _hurd_itimer_started; /* Time the thread started waiting.  */
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
quantize_timeval (struct timeval *tv)
Packit 6c4009
{
Packit 6c4009
  static time_t quantum = -1;
Packit 6c4009
Packit 6c4009
  if (quantum == -1)
Packit 6c4009
    quantum = 1000000 / __getclktck ();
Packit 6c4009
Packit 6c4009
  tv->tv_usec = ((tv->tv_usec + (quantum - 1)) / quantum) * quantum;
Packit 6c4009
  if (tv->tv_usec >= 1000000)
Packit 6c4009
    {
Packit 6c4009
      ++tv->tv_sec;
Packit 6c4009
      tv->tv_usec -= 1000000;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static inline void
Packit 6c4009
subtract_timeval (struct timeval *from, const struct timeval *subtract)
Packit 6c4009
{
Packit 6c4009
  from->tv_usec -= subtract->tv_usec;
Packit 6c4009
  from->tv_sec -= subtract->tv_sec;
Packit 6c4009
  while (from->tv_usec < 0)
Packit 6c4009
    {
Packit 6c4009
      --from->tv_sec;
Packit 6c4009
      from->tv_usec += 1000000;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Function run by the itimer thread.
Packit 6c4009
   This code must be very careful not ever to require a MiG reply port.  */
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
timer_thread (void)
Packit 6c4009
{
Packit 6c4009
  while (1)
Packit 6c4009
    {
Packit 6c4009
      error_t err;
Packit 6c4009
      /* The only message we ever expect to receive is the reply from the
Packit 6c4009
         signal thread to a sig_post call we did.  We never examine the
Packit 6c4009
	 contents.  */
Packit 6c4009
      struct
Packit 6c4009
	{
Packit 6c4009
	  mach_msg_header_t header;
Packit 6c4009
	  error_t return_code;
Packit 6c4009
	} msg;
Packit 6c4009
Packit 6c4009
      /* Wait for a message on a port that noone sends to.  The purpose is
Packit 6c4009
	 the receive timeout.  Notice interrupts so that if we are
Packit 6c4009
	 thread_abort'd, we will loop around and fetch new values from
Packit 6c4009
	 _hurd_itimerval.  */
Packit 6c4009
      err = __mach_msg (&msg.header,
Packit 6c4009
			MACH_RCV_MSG|MACH_RCV_TIMEOUT|MACH_RCV_INTERRUPT,
Packit 6c4009
			0, 0, _hurd_itimer_port,
Packit 6c4009
			_hurd_itimerval.it_value.tv_sec * 1000 +
Packit 6c4009
			_hurd_itimerval.it_value.tv_usec / 1000,
Packit 6c4009
			MACH_PORT_NULL);
Packit 6c4009
      switch (err)
Packit 6c4009
	{
Packit 6c4009
	case MACH_RCV_TIMED_OUT:
Packit 6c4009
	  /* We got the expected timeout.  Send a message to the signal
Packit 6c4009
	     thread to tell it to post a SIGALRM signal.  We use
Packit 6c4009
	     _hurd_itimer_port as the reply port just so we will block until
Packit 6c4009
	     the signal thread has frobnicated things to reload the itimer or
Packit 6c4009
	     has terminated this thread.  */
Packit 6c4009
	  __msg_sig_post_request (_hurd_msgport,
Packit 6c4009
				  _hurd_itimer_port,
Packit 6c4009
				  MACH_MSG_TYPE_MAKE_SEND_ONCE,
Packit 6c4009
				  SIGALRM, 0, __mach_task_self ());
Packit 6c4009
	  break;
Packit 6c4009
Packit 6c4009
	case MACH_RCV_INTERRUPTED:
Packit 6c4009
	  /* We were thread_abort'd.  This is to tell us that
Packit 6c4009
	     _hurd_itimerval has changed and we need to reexamine it
Packit 6c4009
	     and start waiting with the new timeout value.  */
Packit 6c4009
	  break;
Packit 6c4009
Packit 6c4009
	case MACH_MSG_SUCCESS:
Packit 6c4009
	  /* We got the reply message from the sig_post_request above.
Packit 6c4009
	     Ignore it and reexamine the timer value.  */
Packit 6c4009
	  __mach_msg_destroy (&msg.header); /* Just in case.  */
Packit 6c4009
	  break;
Packit 6c4009
Packit 6c4009
	default:
Packit 6c4009
	  /* Unexpected lossage.  Oh well, keep trying.  */
Packit 6c4009
	  break;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Forward declaration.  */
Packit 6c4009
static int setitimer_locked (const struct itimerval *new,
Packit 6c4009
			     struct itimerval *old, void *crit);
Packit 6c4009
Packit 6c4009
static sighandler_t
Packit 6c4009
restart_itimer (struct hurd_signal_preemptor *preemptor,
Packit 6c4009
		struct hurd_sigstate *ss,
Packit 6c4009
		int *signo, struct hurd_signal_detail *detail)
Packit 6c4009
{
Packit 6c4009
  /* This function gets called in the signal thread
Packit 6c4009
     each time a SIGALRM is arriving (even if blocked).  */
Packit 6c4009
  struct itimerval it;
Packit 6c4009
Packit 6c4009
  /* Either reload or disable the itimer.  */
Packit 6c4009
  __spin_lock (&_hurd_itimer_lock);
Packit 6c4009
  it.it_value = it.it_interval = _hurd_itimerval.it_interval;
Packit 6c4009
  setitimer_locked (&it, NULL, NULL);
Packit 6c4009
Packit 6c4009
  /* Continue with normal delivery (or hold, etc.) of SIGALRM.  */
Packit 6c4009
  return SIG_ERR;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Called before any normal SIGALRM signal is delivered.
Packit 6c4009
   Reload the itimer, or disable the itimer.  */
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
setitimer_locked (const struct itimerval *new, struct itimerval *old,
Packit 6c4009
		  void *crit)
Packit 6c4009
{
Packit 6c4009
  struct itimerval newval;
Packit 6c4009
  struct timeval now, remaining, elapsed;
Packit 6c4009
  struct timeval old_interval;
Packit 6c4009
  error_t err;
Packit 6c4009
Packit 6c4009
  inline void kill_itimer_thread (void)
Packit 6c4009
    {
Packit 6c4009
      __thread_terminate (_hurd_itimer_thread);
Packit 6c4009
      __vm_deallocate (__mach_task_self (),
Packit 6c4009
		       _hurd_itimer_thread_stack_base,
Packit 6c4009
		       _hurd_itimer_thread_stack_size);
Packit 6c4009
      _hurd_itimer_thread = MACH_PORT_NULL;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (!new)
Packit 6c4009
    {
Packit 6c4009
      /* Just return the current value in OLD without changing anything.
Packit 6c4009
	 This is what BSD does, even though it's not documented. */
Packit 6c4009
      if (old)
Packit 6c4009
	*old = _hurd_itimerval;
Packit 6c4009
      spin_unlock (&_hurd_itimer_lock);
Packit 6c4009
      _hurd_critical_section_unlock (crit);
Packit 6c4009
      return 0;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  newval = *new;
Packit 6c4009
  quantize_timeval (&newval.it_interval);
Packit 6c4009
  quantize_timeval (&newval.it_value);
Packit 6c4009
  if ((newval.it_value.tv_sec | newval.it_value.tv_usec) != 0)
Packit 6c4009
    {
Packit 6c4009
      /* Make sure the itimer thread is set up.  */
Packit 6c4009
Packit 6c4009
      /* Set up a signal preemptor global for all threads to
Packit 6c4009
	 run `restart_itimer' each time a SIGALRM would arrive.  */
Packit 6c4009
      static struct hurd_signal_preemptor preemptor =
Packit 6c4009
	{
Packit 6c4009
	  __sigmask (SIGALRM), 0, 0,
Packit 6c4009
	  &restart_itimer,
Packit 6c4009
	};
Packit 6c4009
      __mutex_lock (&_hurd_siglock);
Packit 6c4009
      if (! preemptor.next && _hurdsig_preemptors != &preemptor)
Packit 6c4009
	{
Packit 6c4009
	  preemptor.next = _hurdsig_preemptors;
Packit 6c4009
	  _hurdsig_preemptors = &preemptor;
Packit 6c4009
	}
Packit 6c4009
      __mutex_unlock (&_hurd_siglock);
Packit 6c4009
Packit 6c4009
      if (_hurd_itimer_port == MACH_PORT_NULL)
Packit 6c4009
	{
Packit 6c4009
	  /* Allocate a receive right that the itimer thread will
Packit 6c4009
	     block waiting for a message on.  */
Packit 6c4009
	  if (err = __mach_port_allocate (__mach_task_self (),
Packit 6c4009
					  MACH_PORT_RIGHT_RECEIVE,
Packit 6c4009
					  &_hurd_itimer_port))
Packit 6c4009
	    goto out;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      if (_hurd_itimer_thread == MACH_PORT_NULL)
Packit 6c4009
	{
Packit 6c4009
	  /* Start up the itimer thread running `timer_thread' (below).  */
Packit 6c4009
	  if (err = __thread_create (__mach_task_self (),
Packit 6c4009
				     &_hurd_itimer_thread))
Packit 6c4009
	    goto out;
Packit 6c4009
	  _hurd_itimer_thread_stack_base = 0; /* Anywhere.  */
Packit 6c4009
	  _hurd_itimer_thread_stack_size = __vm_page_size; /* Small stack.  */
Packit 6c4009
	  if ((err = __mach_setup_thread (__mach_task_self (),
Packit 6c4009
					 _hurd_itimer_thread,
Packit 6c4009
					 &timer_thread,
Packit 6c4009
					 &_hurd_itimer_thread_stack_base,
Packit 6c4009
					 &_hurd_itimer_thread_stack_size))
Packit 6c4009
	      || (err = __mach_setup_tls(_hurd_itimer_thread)))
Packit 6c4009
	    {
Packit 6c4009
	      __thread_terminate (_hurd_itimer_thread);
Packit 6c4009
	      _hurd_itimer_thread = MACH_PORT_NULL;
Packit 6c4009
	      goto out;
Packit 6c4009
	    }
Packit 6c4009
	  _hurd_itimer_thread_suspended = 1;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if ((newval.it_value.tv_sec | newval.it_value.tv_usec) != 0 || old != NULL)
Packit 6c4009
    {
Packit 6c4009
      /* Calculate how much time is remaining for the pending alarm.  */
Packit 6c4009
      if (__gettimeofday (&now, NULL) < 0)
Packit 6c4009
	{
Packit 6c4009
	  __spin_unlock (&_hurd_itimer_lock);
Packit 6c4009
	  _hurd_critical_section_unlock (crit);
Packit 6c4009
	  return -1;
Packit 6c4009
	}
Packit 6c4009
      elapsed = now;
Packit 6c4009
      subtract_timeval (&elapsed, &_hurd_itimer_started);
Packit 6c4009
      remaining = _hurd_itimerval.it_value;
Packit 6c4009
      if (timercmp (&remaining, &elapsed, <))
Packit 6c4009
	{
Packit 6c4009
	  /* Hmm.  The timer should have just gone off, but has not been reset.
Packit 6c4009
	     This is a possible timing glitch.  The alarm will signal soon. */
Packit 6c4009
	  /* XXX wrong */
Packit 6c4009
	  remaining.tv_sec = 0;
Packit 6c4009
	  remaining.tv_usec = 0;
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	subtract_timeval (&remaining, &elapsed);
Packit 6c4009
Packit 6c4009
      /* Remember the old reload interval before changing it.  */
Packit 6c4009
      old_interval = _hurd_itimerval.it_interval;
Packit 6c4009
Packit 6c4009
      /* Record the starting time that the timer interval relates to.  */
Packit 6c4009
      _hurd_itimer_started = now;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Load the new itimer value.  */
Packit 6c4009
  _hurd_itimerval = newval;
Packit 6c4009
Packit 6c4009
  if ((newval.it_value.tv_sec | newval.it_value.tv_usec) == 0)
Packit 6c4009
    {
Packit 6c4009
      /* Disable the itimer.  */
Packit 6c4009
      if (_hurd_itimer_thread && !_hurd_itimer_thread_suspended)
Packit 6c4009
	{
Packit 6c4009
	  /* Suspend the itimer thread so it does nothing.  Then abort its
Packit 6c4009
	     kernel context so that when the thread is resumed, mach_msg
Packit 6c4009
	     will return to timer_thread (below) and it will fetch new
Packit 6c4009
	     values from _hurd_itimerval.  */
Packit 6c4009
	  if ((err = __thread_suspend (_hurd_itimer_thread)) ||
Packit 6c4009
	      (err = __thread_abort (_hurd_itimer_thread)))
Packit 6c4009
	    /* If we can't save it for later, nuke it.  */
Packit 6c4009
	    kill_itimer_thread ();
Packit 6c4009
	  else
Packit 6c4009
	    _hurd_itimer_thread_suspended = 1;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
  /* See if the timeout changed.  If so, we must alert the itimer thread.  */
Packit 6c4009
  else if (remaining.tv_sec != newval.it_value.tv_sec ||
Packit 6c4009
	   remaining.tv_usec != newval.it_value.tv_usec)
Packit 6c4009
    {
Packit 6c4009
      /* The timeout value is changing.  Tell the itimer thread to
Packit 6c4009
	 reexamine it and start counting down.  If the itimer thread is
Packit 6c4009
	 marked as suspended, either we just created it, or it was
Packit 6c4009
	 suspended and thread_abort'd last time the itimer was disabled;
Packit 6c4009
	 either way it will wake up and start waiting for the new timeout
Packit 6c4009
	 value when we resume it.  If it is not suspended, the itimer
Packit 6c4009
	 thread is waiting to deliver a pending alarm that we will override
Packit 6c4009
	 (since it would come later than the new alarm being set);
Packit 6c4009
	 thread_abort will make mach_msg return MACH_RCV_INTERRUPTED, so it
Packit 6c4009
	 will loop around and use the new timeout value.  */
Packit 6c4009
      if (err = (_hurd_itimer_thread_suspended
Packit 6c4009
		 ? __thread_resume : __thread_abort) (_hurd_itimer_thread))
Packit 6c4009
	{
Packit 6c4009
	  kill_itimer_thread ();
Packit 6c4009
	  goto out;
Packit 6c4009
	}
Packit 6c4009
      _hurd_itimer_thread_suspended = 0;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  __spin_unlock (&_hurd_itimer_lock);
Packit 6c4009
  _hurd_critical_section_unlock (crit);
Packit 6c4009
Packit 6c4009
  if (old != NULL)
Packit 6c4009
    {
Packit 6c4009
      old->it_value = remaining;
Packit 6c4009
      old->it_interval = old_interval;
Packit 6c4009
    }
Packit 6c4009
  return 0;
Packit 6c4009
Packit 6c4009
 out:
Packit 6c4009
  __spin_unlock (&_hurd_itimer_lock);
Packit 6c4009
  _hurd_critical_section_unlock (crit);
Packit 6c4009
  return __hurd_fail (err);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Set the timer WHICH to *NEW.  If OLD is not NULL,
Packit 6c4009
   set *OLD to the old value of timer WHICH.
Packit 6c4009
   Returns 0 on success, -1 on errors.  */
Packit 6c4009
int
Packit 6c4009
__setitimer (enum __itimer_which which, const struct itimerval *new,
Packit 6c4009
	     struct itimerval *old)
Packit 6c4009
{
Packit 6c4009
  void *crit;
Packit 6c4009
Packit 6c4009
  switch (which)
Packit 6c4009
    {
Packit 6c4009
    default:
Packit 6c4009
      return __hurd_fail (EINVAL);
Packit 6c4009
Packit 6c4009
    case ITIMER_VIRTUAL:
Packit 6c4009
    case ITIMER_PROF:
Packit 6c4009
      return __hurd_fail (ENOSYS);
Packit 6c4009
Packit 6c4009
    case ITIMER_REAL:
Packit 6c4009
      break;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  crit = _hurd_critical_section_lock ();
Packit 6c4009
  __spin_lock (&_hurd_itimer_lock);
Packit 6c4009
  return setitimer_locked (new, old, crit);
Packit 6c4009
}
Packit 6c4009

Packit 6c4009
static void
Packit 6c4009
fork_itimer (void)
Packit 6c4009
{
Packit 6c4009
  /* We must restart the itimer in the child.  */
Packit 6c4009
Packit 6c4009
  struct itimerval it;
Packit 6c4009
Packit 6c4009
  __spin_lock (&_hurd_itimer_lock);
Packit 6c4009
  _hurd_itimer_thread = MACH_PORT_NULL;
Packit 6c4009
  it = _hurd_itimerval;
Packit 6c4009
  it.it_value = it.it_interval;
Packit 6c4009
Packit 6c4009
  setitimer_locked (&it, NULL, NULL);
Packit 6c4009
Packit 6c4009
  (void) &fork_itimer;		/* Avoid gcc optimizing out the function.  */
Packit 6c4009
}
Packit 6c4009
text_set_element (_hurd_fork_child_hook, fork_itimer);
Packit 6c4009
Packit 6c4009
weak_alias (__setitimer, setitimer)