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