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

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