2000-01-08 Mark Kettenis <kettenis@gnu.org>
[kopensolaris-gnu/glibc.git] / sysdeps / mach / hurd / setitimer.c
1 /* Copyright (C) 1994, 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public
15    License along with the GNU C Library; see the file COPYING.LIB.  If not,
16    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17    Boston, MA 02111-1307, USA.  */
18
19 #include <stddef.h>
20 #include <errno.h>
21 #include <sys/time.h>
22 #include <hurd.h>
23 #include <hurd/signal.h>
24 #include <hurd/sigpreempt.h>
25 #include <hurd/msg_request.h>
26 #include <mach/message.h>
27
28 /* XXX Temporary cheezoid implementation of ITIMER_REAL/SIGALRM.  */
29
30 spin_lock_t _hurd_itimer_lock = SPIN_LOCK_INITIALIZER;
31 struct itimerval _hurd_itimerval; /* Current state of the timer.  */
32 mach_port_t _hurd_itimer_port;  /* Port the timer thread blocks on.  */
33 thread_t _hurd_itimer_thread;   /* Thread waiting for timeout.  */
34 int _hurd_itimer_thread_suspended; /* Nonzero if that thread is suspended.  */
35 vm_address_t _hurd_itimer_thread_stack_base; /* Base of its stack.  */
36 vm_address_t _hurd_itimer_thread_stack_size; /* Size of its stack.  */
37 struct timeval _hurd_itimer_started; /* Time the thread started waiting.  */
38
39 static void
40 quantize_timeval (struct timeval *tv)
41 {
42   static time_t quantum = -1;
43   
44   if (quantum == -1)
45     quantum = 1000000 / __libc_clk_tck ();
46
47   tv->tv_usec = ((tv->tv_usec + (quantum - 1)) / quantum) * quantum;
48   if (tv->tv_usec >= 1000000)
49     {
50       ++tv->tv_sec;
51       tv->tv_usec -= 1000000;
52     }
53 }
54
55 static inline void
56 subtract_timeval (struct timeval *from, const struct timeval *subtract)
57 {
58   from->tv_usec -= subtract->tv_usec;
59   from->tv_sec -= subtract->tv_sec;
60   while (from->tv_usec < 0)
61     {
62       --from->tv_sec;
63       from->tv_usec += 1000000;
64     }
65 }
66
67 /* Function run by the itimer thread.
68    This code must be very careful not ever to require a MiG reply port.  */
69
70 static void
71 timer_thread (void)
72 {
73   while (1)
74     {
75       error_t err;
76       /* The only message we ever expect to receive is the reply from the
77          signal thread to a sig_post call we did.  We never examine the
78          contents.  */
79       struct
80         {
81           mach_msg_header_t header;
82           error_t return_code;
83         } msg;
84
85       /* Wait for a message on a port that noone sends to.  The purpose is
86          the receive timeout.  Notice interrupts so that if we are
87          thread_abort'd, we will loop around and fetch new values from
88          _hurd_itimerval.  */
89       err = __mach_msg (&msg.header,
90                         MACH_RCV_MSG|MACH_RCV_TIMEOUT|MACH_RCV_INTERRUPT,
91                         0, 0, _hurd_itimer_port,
92                         _hurd_itimerval.it_value.tv_sec * 1000 +
93                         _hurd_itimerval.it_value.tv_usec / 1000,
94                         MACH_PORT_NULL);
95       switch (err)
96         {
97         case MACH_RCV_TIMED_OUT:
98           /* We got the expected timeout.  Send a message to the signal
99              thread to tell it to post a SIGALRM signal.  We use
100              _hurd_itimer_port as the reply port just so we will block until
101              the signal thread has frobnicated things to reload the itimer or
102              has terminated this thread.  */
103           __msg_sig_post_request (_hurd_msgport,
104                                   _hurd_itimer_port,
105                                   MACH_MSG_TYPE_MAKE_SEND_ONCE,
106                                   SIGALRM, 0, __mach_task_self ());
107           break;
108
109         case MACH_RCV_INTERRUPTED:
110           /* We were thread_abort'd.  This is to tell us that
111              _hurd_itimerval has changed and we need to reexamine it
112              and start waiting with the new timeout value.  */
113           break;
114
115         case MACH_MSG_SUCCESS:
116           /* We got the reply message from the sig_post_request above.
117              Ignore it and reexamine the timer value.  */
118           __mach_msg_destroy (&msg.header); /* Just in case.  */
119           break;
120
121         default:
122           /* Unexpected lossage.  Oh well, keep trying.  */
123           break;
124         }
125     }
126 }
127
128
129 static sighandler_t
130 restart_itimer (struct hurd_signal_preemptor *preemptor,
131                 struct hurd_sigstate *ss,
132                 int *signo, struct hurd_signal_detail *detail)
133 {
134   static int setitimer_locked (const struct itimerval *new,
135                                struct itimerval *old, void *crit);
136
137   /* This function gets called in the signal thread
138      each time a SIGALRM is arriving (even if blocked).  */
139   struct itimerval it;
140
141   /* Either reload or disable the itimer.  */
142   __spin_lock (&_hurd_itimer_lock);
143   it.it_value = it.it_interval = _hurd_itimerval.it_interval;
144   setitimer_locked (&it, NULL, NULL);
145
146   /* Continue with normal delivery (or hold, etc.) of SIGALRM.  */
147   return SIG_ERR;
148 }
149
150
151 /* Called before any normal SIGALRM signal is delivered.
152    Reload the itimer, or disable the itimer.  */
153
154 static int
155 setitimer_locked (const struct itimerval *new, struct itimerval *old,
156                   void *crit)
157 {
158   struct itimerval newval;
159   struct timeval now, remaining, elapsed;
160   struct timeval old_interval;
161   error_t err;
162
163   inline void kill_itimer_thread (void)
164     {
165       __thread_terminate (_hurd_itimer_thread);
166       __vm_deallocate (__mach_task_self (),
167                        _hurd_itimer_thread_stack_base,
168                        _hurd_itimer_thread_stack_size);
169       _hurd_itimer_thread = MACH_PORT_NULL;
170     }
171
172   if (!new)
173     {
174       /* Just return the current value in OLD without changing anything.
175          This is what BSD does, even though it's not documented. */
176       if (old)
177         *old = _hurd_itimerval;
178       spin_unlock (&_hurd_itimer_lock);
179       _hurd_critical_section_unlock (crit);
180       return 0;
181     }
182
183   newval = *new;
184   quantize_timeval (&newval.it_interval);
185   quantize_timeval (&newval.it_value);
186   if ((newval.it_value.tv_sec | newval.it_value.tv_usec) != 0)
187     {
188       /* Make sure the itimer thread is set up.  */
189
190       /* Set up a signal preemptor global for all threads to
191          run `restart_itimer' each time a SIGALRM would arrive.  */
192       static struct hurd_signal_preemptor preemptor =
193         {
194           __sigmask (SIGALRM), 0, 0,
195           &restart_itimer,
196         };
197       __mutex_lock (&_hurd_siglock);
198       if (! preemptor.next && _hurdsig_preemptors != &preemptor)
199         {
200           preemptor.next = _hurdsig_preemptors;
201           _hurdsig_preemptors = &preemptor;
202         }
203       __mutex_unlock (&_hurd_siglock);
204
205       if (_hurd_itimer_port == MACH_PORT_NULL)
206         {
207           /* Allocate a receive right that the itimer thread will
208              block waiting for a message on.  */
209           if (err = __mach_port_allocate (__mach_task_self (),
210                                           MACH_PORT_RIGHT_RECEIVE,
211                                           &_hurd_itimer_port))
212             goto out;
213         }
214
215       if (_hurd_itimer_thread == MACH_PORT_NULL)
216         {
217           /* Start up the itimer thread running `timer_thread' (below).  */
218           if (err = __thread_create (__mach_task_self (),
219                                      &_hurd_itimer_thread))
220             return __hurd_fail (err);
221           _hurd_itimer_thread_stack_base = 0; /* Anywhere.  */
222           _hurd_itimer_thread_stack_size = __vm_page_size; /* Small stack.  */
223           if (err = __mach_setup_thread (__mach_task_self (),
224                                          _hurd_itimer_thread,
225                                          &timer_thread,
226                                          &_hurd_itimer_thread_stack_base,
227                                          &_hurd_itimer_thread_stack_size))
228             {
229               __thread_terminate (_hurd_itimer_thread);
230               _hurd_itimer_thread = MACH_PORT_NULL;
231               goto out;
232             }
233           _hurd_itimer_thread_suspended = 1;
234         }
235     }
236
237   if ((newval.it_value.tv_sec | newval.it_value.tv_usec) != 0 || old != NULL)
238     {
239       /* Calculate how much time is remaining for the pending alarm.  */
240       if (__gettimeofday (&now, NULL) < 0)
241         {
242           __spin_unlock (&_hurd_itimer_lock);
243           _hurd_critical_section_unlock (crit);
244           return -1;
245         }
246       elapsed = now;
247       subtract_timeval (&elapsed, &_hurd_itimer_started);
248       remaining = _hurd_itimerval.it_value;
249       if (timercmp (&remaining, &elapsed, <))
250         {
251           /* Hmm.  The timer should have just gone off, but has not been reset.
252              This is a possible timing glitch.  The alarm will signal soon. */
253           /* XXX wrong */
254           remaining.tv_sec = 0;
255           remaining.tv_usec = 0;
256         }
257       else
258         subtract_timeval (&remaining, &elapsed);
259
260       /* Remember the old reload interval before changing it.  */
261       old_interval = _hurd_itimerval.it_interval;
262
263       /* Record the starting time that the timer interval relates to.  */
264       _hurd_itimer_started = now;
265     }
266
267   /* Load the new itimer value.  */
268   _hurd_itimerval = newval;
269
270   if ((newval.it_value.tv_sec | newval.it_value.tv_usec) == 0)
271     {
272       /* Disable the itimer.  */
273       if (_hurd_itimer_thread && !_hurd_itimer_thread_suspended)
274         {
275           /* Suspend the itimer thread so it does nothing.  Then abort its
276              kernel context so that when the thread is resumed, mach_msg
277              will return to timer_thread (below) and it will fetch new
278              values from _hurd_itimerval.  */
279           if ((err = __thread_suspend (_hurd_itimer_thread)) ||
280               (err = __thread_abort (_hurd_itimer_thread)))
281             /* If we can't save it for later, nuke it.  */
282             kill_itimer_thread ();
283           else
284             _hurd_itimer_thread_suspended = 1;
285         }
286     }
287   /* See if the timeout changed.  If so, we must alert the itimer thread.  */
288   else if (remaining.tv_sec != newval.it_value.tv_sec ||
289            remaining.tv_usec != newval.it_value.tv_usec)
290     {
291       /* The timeout value is changing.  Tell the itimer thread to
292          reexamine it and start counting down.  If the itimer thread is
293          marked as suspended, either we just created it, or it was
294          suspended and thread_abort'd last time the itimer was disabled;
295          either way it will wake up and start waiting for the new timeout
296          value when we resume it.  If it is not suspended, the itimer
297          thread is waiting to deliver a pending alarm that we will override
298          (since it would come later than the new alarm being set);
299          thread_abort will make mach_msg return MACH_RCV_INTERRUPTED, so it
300          will loop around and use the new timeout value.  */
301       if (err = (_hurd_itimer_thread_suspended
302                  ? __thread_resume : __thread_abort) (_hurd_itimer_thread))
303         {
304           kill_itimer_thread ();
305           goto out;
306         }
307       _hurd_itimer_thread_suspended = 0;
308     }
309
310   __spin_unlock (&_hurd_itimer_lock);
311   _hurd_critical_section_unlock (crit);
312
313   if (old != NULL)
314     {
315       old->it_value = remaining;
316       old->it_interval = old_interval;
317     }
318   return 0;
319
320  out:
321   __spin_unlock (&_hurd_itimer_lock);
322   _hurd_critical_section_unlock (crit);
323   return __hurd_fail (err);
324 }
325
326 /* Set the timer WHICH to *NEW.  If OLD is not NULL,
327    set *OLD to the old value of timer WHICH.
328    Returns 0 on success, -1 on errors.  */
329 int
330 __setitimer (enum __itimer_which which, const struct itimerval *new,
331              struct itimerval *old)
332 {
333   void *crit;
334
335   switch (which)
336     {
337     default:
338       return __hurd_fail (EINVAL);
339
340     case ITIMER_VIRTUAL:
341     case ITIMER_PROF:
342       return __hurd_fail (ENOSYS);
343
344     case ITIMER_REAL:
345       break;
346     }
347
348   crit = _hurd_critical_section_lock ();
349   __spin_lock (&_hurd_itimer_lock);
350   return setitimer_locked (new, old, crit);
351 }
352 \f
353 static void
354 fork_itimer (void)
355 {
356   /* We must restart the itimer in the child.  */
357
358   struct itimerval it;
359
360   __spin_lock (&_hurd_itimer_lock);
361   _hurd_itimer_thread = MACH_PORT_NULL;
362   it = _hurd_itimerval;
363   it.it_value = it.it_interval;
364
365   setitimer_locked (&it, NULL, NULL);
366
367   (void) &fork_itimer;          /* Avoid gcc optimizing out the function.  */
368 }
369 text_set_element (_hurd_fork_child_hook, fork_itimer);
370
371 weak_alias (__setitimer, setitimer)