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