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 | |
32 | spin_lock_t _hurd_itimer_lock = SPIN_LOCK_INITIALIZER; |
33 | struct itimerval _hurd_itimerval; /* Current state of the timer. */ |
34 | mach_port_t _hurd_itimer_port; /* Port the timer thread blocks on. */ |
35 | thread_t _hurd_itimer_thread; /* Thread waiting for timeout. */ |
36 | int _hurd_itimer_thread_suspended; /* Nonzero if that thread is suspended. */ |
37 | vm_address_t _hurd_itimer_thread_stack_base; /* Base of its stack. */ |
38 | vm_size_t _hurd_itimer_thread_stack_size; /* Size of its stack. */ |
39 | struct timeval _hurd_itimer_started; /* Time the thread started waiting. */ |
40 | |
41 | static void |
42 | quantize_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 | |
57 | static inline void |
58 | subtract_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 | |
72 | static void |
73 | timer_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 ; |
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. */ |
133 | static int setitimer_locked (const struct itimerval *new, |
134 | struct itimerval *old, void *crit, |
135 | int hurd_siglocked); |
136 | |
137 | static sighandler_t |
138 | restart_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 | |
159 | static int |
160 | setitimer_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. */ |
338 | int |
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 | |
358 | retry: |
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 | |
369 | static void |
370 | fork_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 | } |
385 | text_set_element (_hurd_fork_child_hook, fork_itimer); |
386 | |
387 | weak_alias (__setitimer, setitimer) |
388 | |