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

source code of glibc/sysdeps/mach/hurd/setitimer.c