]>
Commit | Line | Data |
---|---|---|
54da5be3 | 1 | /* Replacement for mach_msg used in interruptible Hurd RPCs. |
dff8da6b | 2 | Copyright (C) 1995-2024 Free Software Foundation, Inc. |
c84142e8 | 3 | This file is part of the GNU C Library. |
54da5be3 | 4 | |
c84142e8 | 5 | The GNU C Library is free software; you can redistribute it and/or |
41bdb6e2 AJ |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either | |
8 | version 2.1 of the License, or (at your option) any later version. | |
54da5be3 | 9 | |
c84142e8 UD |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
41bdb6e2 | 13 | Lesser General Public License for more details. |
54da5be3 | 14 | |
41bdb6e2 | 15 | You should have received a copy of the GNU Lesser General Public |
59ba27a6 | 16 | License along with the GNU C Library; if not, see |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
54da5be3 RM |
18 | |
19 | #include <mach.h> | |
84b4a81a | 20 | #include <mach_rpc.h> |
54da5be3 | 21 | #include <mach/mig_errors.h> |
fb8e70d6 | 22 | #include <mach/mig_support.h> |
54da5be3 | 23 | #include <hurd/signal.h> |
38e30b39 | 24 | #include <assert.h> |
54da5be3 | 25 | |
3fe9de0d RM |
26 | #include "intr-msg.h" |
27 | ||
54da5be3 RM |
28 | error_t |
29 | _hurd_intr_rpc_mach_msg (mach_msg_header_t *msg, | |
30 | mach_msg_option_t option, | |
31 | mach_msg_size_t send_size, | |
32 | mach_msg_size_t rcv_size, | |
33 | mach_port_t rcv_name, | |
34 | mach_msg_timeout_t timeout, | |
35 | mach_port_t notify) | |
36 | { | |
54da5be3 | 37 | error_t err; |
37eb58de | 38 | struct hurd_sigstate *ss; |
38e30b39 RM |
39 | const mach_msg_option_t user_option = option; |
40 | const mach_msg_timeout_t user_timeout = timeout; | |
41 | ||
21297437 RM |
42 | struct clobber |
43 | { | |
21297437 | 44 | mach_msg_type_t type; |
c8b4ce0b ST |
45 | union { |
46 | error_t err; | |
47 | uintptr_t align; | |
48 | }; | |
21297437 | 49 | }; |
38e30b39 RM |
50 | union msg |
51 | { | |
52 | mach_msg_header_t header; | |
53 | mig_reply_header_t reply; | |
54 | struct | |
55 | { | |
56 | mach_msg_header_t header; | |
4571fb8f | 57 | mach_msg_type_t type; |
38e30b39 RM |
58 | int code; |
59 | } check; | |
60 | struct | |
37eb58de | 61 | { |
38e30b39 RM |
62 | mach_msg_header_t header; |
63 | struct clobber data; | |
64 | } request; | |
65 | }; | |
66 | union msg *const m = (void *) msg; | |
67 | mach_msg_bits_t msgh_bits; | |
68 | mach_port_t remote_port; | |
69 | mach_msg_id_t msgid; | |
70 | struct clobber save_data; | |
71 | ||
72 | if ((option & (MACH_SEND_MSG|MACH_RCV_MSG)) != (MACH_SEND_MSG|MACH_RCV_MSG) | |
73 | || _hurd_msgport_thread == MACH_PORT_NULL) | |
74 | { | |
75 | /* Either this is not an RPC (i.e., only a send or only a receive), | |
76 | so it can't be interruptible; or, the signal thread is not set up | |
77 | yet, so we cannot do the normal signal magic. Do a normal, | |
78 | uninterruptible mach_msg call instead. */ | |
79 | return __mach_msg (&m->header, option, send_size, rcv_size, rcv_name, | |
37eb58de RM |
80 | timeout, notify); |
81 | } | |
82 | ||
83 | ss = _hurd_self_sigstate (); | |
84 | ||
38e30b39 RM |
85 | /* Save state that gets clobbered by an EINTR reply message. |
86 | We will need to restore it if we want to retry the RPC. */ | |
87 | msgh_bits = m->header.msgh_bits; | |
88 | remote_port = m->header.msgh_remote_port; | |
89 | msgid = m->header.msgh_id; | |
90 | assert (rcv_size >= sizeof m->request); | |
91 | save_data = m->request.data; | |
54da5be3 RM |
92 | |
93 | /* Tell the signal thread that we are doing an interruptible RPC on | |
94 | this port. If we get a signal and should return EINTR, the signal | |
95 | thread will set this variable to MACH_PORT_NULL. The RPC might | |
96 | return EINTR when some other thread gets a signal, in which case we | |
97 | want to restart our call. */ | |
38e30b39 | 98 | ss->intr_port = m->header.msgh_remote_port; |
fb8e70d6 | 99 | |
38e30b39 RM |
100 | /* A signal may arrive here, after intr_port is set, but before the |
101 | mach_msg system call. The signal handler might do an interruptible | |
102 | RPC, and clobber intr_port; then it would not be set properly when we | |
103 | actually did send the RPC, and a later signal wouldn't interrupt that | |
104 | RPC. So, _hurd_setup_sighandler saves intr_port in the sigcontext, | |
105 | and sigreturn restores it. */ | |
54da5be3 RM |
106 | |
107 | message: | |
108 | ||
38e30b39 | 109 | /* Note that the signal trampoline code might modify our OPTION! */ |
fb8e70d6 | 110 | err = INTR_MSG_TRAP (msg, option, send_size, |
5c81be53 ST |
111 | rcv_size, rcv_name, timeout, notify, |
112 | &ss->cancel, &ss->intr_port); | |
54da5be3 RM |
113 | |
114 | switch (err) | |
115 | { | |
a993273c | 116 | case MACH_RCV_TIMED_OUT: |
38e30b39 | 117 | if (user_option & MACH_RCV_TIMEOUT) |
a993273c RM |
118 | /* The real user RPC timed out. */ |
119 | break; | |
120 | else | |
121 | /* The operation was supposedly interrupted, but still has | |
122 | not returned. Declare it interrupted. */ | |
2d0d1d38 | 123 | goto dead; |
a993273c | 124 | |
54da5be3 | 125 | case MACH_SEND_INTERRUPTED: /* RPC didn't get out. */ |
38e30b39 RM |
126 | if (!(option & MACH_SEND_MSG)) |
127 | { | |
128 | /* Oh yes, it did! Since we were not doing a message send, | |
129 | this return code cannot have come from the kernel! | |
130 | Instead, it was the signal thread mutating our state to tell | |
131 | us not to enter this RPC. However, we are already in the receive! | |
132 | Since the signal thread thought we weren't in the RPC yet, | |
133 | it didn't do an interrupt_operation. | |
134 | XXX */ | |
135 | goto retry_receive; | |
136 | } | |
8f22e362 ST |
137 | if (!(option & MACH_SEND_INTERRUPT)) |
138 | { | |
139 | option = user_option; | |
140 | timeout = user_timeout; | |
141 | goto message; | |
142 | } | |
38e30b39 RM |
143 | /* FALLTHROUGH */ |
144 | ||
024c4ccb RM |
145 | /* These are the other codes that mean a pseudo-receive modified |
146 | the message buffer and we might need to clean up the port rights. */ | |
147 | case MACH_SEND_TIMED_OUT: | |
148 | case MACH_SEND_INVALID_NOTIFY: | |
21297437 | 149 | #ifdef MACH_SEND_NO_NOTIFY |
024c4ccb | 150 | case MACH_SEND_NO_NOTIFY: |
21297437 RM |
151 | #endif |
152 | #ifdef MACH_SEND_NOTIFY_IN_PROGRESS | |
024c4ccb | 153 | case MACH_SEND_NOTIFY_IN_PROGRESS: |
21297437 | 154 | #endif |
024c4ccb RM |
155 | if (MACH_MSGH_BITS_REMOTE (msg->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND) |
156 | { | |
157 | __mach_port_deallocate (__mach_task_self (), msg->msgh_remote_port); | |
158 | msg->msgh_bits | |
159 | = (MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, | |
160 | MACH_MSGH_BITS_LOCAL (msg->msgh_bits)) | |
161 | | MACH_MSGH_BITS_OTHER (msg->msgh_bits)); | |
162 | } | |
163 | if (msg->msgh_bits & MACH_MSGH_BITS_COMPLEX) | |
164 | { | |
165 | /* Check for MOVE_SEND rights in the message. These hold refs | |
166 | that we need to release in case the message is in fact never | |
167 | re-sent later. Since it might in fact be re-sent, we turn | |
168 | these into COPY_SEND's after deallocating the extra user ref; | |
169 | the caller is responsible for still holding a ref to go with | |
170 | the original COPY_SEND right, so the resend copies it again. */ | |
171 | ||
172 | mach_msg_type_long_t *ty = (void *) (msg + 1); | |
173 | while ((void *) ty < (void *) msg + msg->msgh_size) | |
174 | { | |
175 | mach_msg_type_name_t name; | |
176 | mach_msg_type_size_t size; | |
177 | mach_msg_type_number_t number; | |
178 | ||
f11a9299 FC |
179 | inline void clean_ports_and_memory (char *data, const vm_size_t length, |
180 | int dealloc) | |
024c4ccb RM |
181 | { |
182 | mach_msg_type_number_t i; | |
183 | switch (name) | |
184 | { | |
185 | case MACH_MSG_TYPE_MOVE_SEND: | |
f11a9299 | 186 | mach_port_t *ports = (mach_port_t *) data; |
024c4ccb RM |
187 | for (i = 0; i < number; i++) |
188 | __mach_port_deallocate (__mach_task_self (), *ports++); | |
4e4c417b RM |
189 | if (ty->msgtl_header.msgt_longform) |
190 | ty->msgtl_name = MACH_MSG_TYPE_COPY_SEND; | |
191 | else | |
192 | ty->msgtl_header.msgt_name = MACH_MSG_TYPE_COPY_SEND; | |
024c4ccb RM |
193 | break; |
194 | case MACH_MSG_TYPE_COPY_SEND: | |
195 | case MACH_MSG_TYPE_MOVE_RECEIVE: | |
196 | break; | |
197 | default: | |
198 | if (MACH_MSG_TYPE_PORT_ANY (name)) | |
199 | assert (! "unexpected port type in interruptible RPC"); | |
200 | } | |
201 | if (dealloc) | |
f11a9299 | 202 | __vm_deallocate (__mach_task_self (), (vm_address_t) data, length); |
024c4ccb RM |
203 | } |
204 | ||
ad26c251 FC |
205 | inline void clean_inlined_ports (mach_port_name_inlined_t *ports) |
206 | { | |
207 | mach_msg_type_number_t i; | |
208 | switch (name) | |
209 | { | |
210 | case MACH_MSG_TYPE_MOVE_SEND: | |
211 | for (i = 0; i < number; i++) | |
212 | __mach_port_deallocate (__mach_task_self (), ports[i].name); | |
213 | if (ty->msgtl_header.msgt_longform) | |
214 | ty->msgtl_name = MACH_MSG_TYPE_COPY_SEND; | |
215 | else | |
216 | ty->msgtl_header.msgt_name = MACH_MSG_TYPE_COPY_SEND; | |
217 | break; | |
218 | case MACH_MSG_TYPE_COPY_SEND: | |
219 | case MACH_MSG_TYPE_MOVE_RECEIVE: | |
220 | break; | |
221 | default: | |
222 | if (MACH_MSG_TYPE_PORT_ANY (name)) | |
223 | assert (! "unexpected port type in interruptible RPC"); | |
224 | } | |
225 | } | |
226 | ||
f11a9299 | 227 | char *data; |
024c4ccb RM |
228 | if (ty->msgtl_header.msgt_longform) |
229 | { | |
230 | name = ty->msgtl_name; | |
231 | size = ty->msgtl_size; | |
232 | number = ty->msgtl_number; | |
f11a9299 | 233 | data = (char *) ty + sizeof (mach_msg_type_long_t); |
024c4ccb RM |
234 | } |
235 | else | |
236 | { | |
237 | name = ty->msgtl_header.msgt_name; | |
238 | size = ty->msgtl_header.msgt_size; | |
239 | number = ty->msgtl_header.msgt_number; | |
f11a9299 | 240 | data = (char *) ty + sizeof (mach_msg_type_t); |
024c4ccb RM |
241 | } |
242 | ||
ad26c251 | 243 | /* Calculate length of data in bytes. */ |
f11a9299 | 244 | const vm_size_t length = ((number * size) + 7) >> 3; |
024c4ccb RM |
245 | if (ty->msgtl_header.msgt_inline) |
246 | { | |
ad26c251 | 247 | clean_inlined_ports ((mach_port_name_inlined_t *) data); |
4571fb8f | 248 | /* Move to the next argument. */ |
f11a9299 | 249 | ty = (void *) PTR_ALIGN_UP (data + length, __alignof__ (uintptr_t)); |
024c4ccb RM |
250 | } |
251 | else | |
ca18306b | 252 | { |
f11a9299 | 253 | clean_ports_and_memory (*(void **) data, length, |
ca18306b | 254 | ty->msgtl_header.msgt_deallocate); |
f11a9299 | 255 | ty = (void *) data + sizeof (void *); |
ca18306b | 256 | } |
024c4ccb RM |
257 | } |
258 | } | |
259 | break; | |
260 | ||
38e30b39 RM |
261 | case EINTR: |
262 | /* Either the process was stopped and continued, | |
263 | or the server doesn't support interrupt_operation. */ | |
54da5be3 RM |
264 | if (ss->intr_port != MACH_PORT_NULL) |
265 | /* If this signal was for us and it should interrupt calls, the | |
266 | signal thread will have cleared SS->intr_port. | |
267 | Since it's not cleared, the signal was for another thread, | |
268 | or SA_RESTART is set. Restart the interrupted call. */ | |
fb8e70d6 | 269 | { |
38e30b39 RM |
270 | /* Make sure we have a valid reply port. The one we were using |
271 | may have been destroyed by interruption. */ | |
4e506f67 | 272 | __mig_dealloc_reply_port (rcv_name); |
38e30b39 RM |
273 | m->header.msgh_local_port = rcv_name = __mig_get_reply_port (); |
274 | m->header.msgh_bits = msgh_bits; | |
275 | option = user_option; | |
276 | timeout = user_timeout; | |
fb8e70d6 RM |
277 | goto message; |
278 | } | |
2d0d1d38 ST |
279 | err = EINTR; |
280 | ||
281 | /* The EINTR return indicates cancellation, so clear the flag. */ | |
282 | ss->cancel = 0; | |
283 | break; | |
54da5be3 RM |
284 | |
285 | case MACH_RCV_PORT_DIED: | |
286 | /* Server didn't respond to interrupt_operation, | |
287 | so the signal thread destroyed the reply port. */ | |
288 | /* FALLTHROUGH */ | |
289 | ||
2d0d1d38 | 290 | dead: |
b6e7c449 | 291 | err = EIEIO; |
54da5be3 | 292 | |
b6e7c449 | 293 | /* The EIEIO return indicates cancellation, so clear the flag. */ |
54da5be3 RM |
294 | ss->cancel = 0; |
295 | break; | |
296 | ||
297 | case MACH_RCV_INTERRUPTED: /* RPC sent; no reply. */ | |
298 | option &= ~MACH_SEND_MSG; /* Don't send again. */ | |
38e30b39 | 299 | retry_receive: |
54da5be3 RM |
300 | if (ss->intr_port == MACH_PORT_NULL) |
301 | { | |
302 | /* This signal or cancellation was for us. We need to wait for | |
303 | the reply, but not hang forever. */ | |
304 | option |= MACH_RCV_TIMEOUT; | |
a993273c | 305 | /* Never decrease the user's timeout. */ |
38e30b39 RM |
306 | if (!(user_option & MACH_RCV_TIMEOUT) |
307 | || timeout > _hurd_interrupted_rpc_timeout) | |
a993273c | 308 | timeout = _hurd_interrupted_rpc_timeout; |
54da5be3 | 309 | } |
38e30b39 RM |
310 | else |
311 | { | |
312 | option = user_option; | |
313 | timeout = user_timeout; | |
314 | } | |
54da5be3 RM |
315 | goto message; /* Retry the receive. */ |
316 | ||
317 | case MACH_MSG_SUCCESS: | |
38e30b39 RM |
318 | { |
319 | /* We got a reply. Was it EINTR? */ | |
21297437 | 320 | #ifdef MACH_MSG_TYPE_BIT |
4571fb8f FC |
321 | static const mach_msg_type_t type_check = { |
322 | .msgt_name = MACH_MSG_TYPE_INTEGER_T, | |
323 | .msgt_size = sizeof (integer_t) * 8, | |
324 | .msgt_number = 1, | |
325 | .msgt_inline = TRUE, | |
326 | .msgt_longform = FALSE, | |
327 | .msgt_deallocate = FALSE, | |
328 | .msgt_unused = 0 | |
329 | }; | |
21297437 | 330 | #endif |
38e30b39 | 331 | |
34a5a146 JM |
332 | if (m->reply.RetCode == EINTR |
333 | && m->header.msgh_size == sizeof m->reply | |
21297437 | 334 | #ifdef MACH_MSG_TYPE_BIT |
4571fb8f | 335 | && !BAD_TYPECHECK(&m->check.type, &type_check) |
21297437 | 336 | #endif |
34a5a146 | 337 | && !(m->header.msgh_bits & MACH_MSGH_BITS_COMPLEX)) |
38e30b39 RM |
338 | { |
339 | /* It is indeed EINTR. Is the interrupt for us? */ | |
340 | if (ss->intr_port != MACH_PORT_NULL) | |
341 | { | |
54da5be3 RM |
342 | /* Nope; repeat the RPC. |
343 | XXX Resources moved? */ | |
38e30b39 RM |
344 | |
345 | assert (m->header.msgh_id == msgid + 100); | |
346 | ||
347 | /* We know we have a valid reply port, because we just | |
348 | received the EINTR reply on it. Restore it and the | |
349 | other fields in the message header needed for send, | |
350 | since the header now reflects receipt of the reply. */ | |
351 | m->header.msgh_local_port = rcv_name; | |
352 | m->header.msgh_remote_port = remote_port; | |
353 | m->header.msgh_id = msgid; | |
354 | m->header.msgh_bits = msgh_bits; | |
355 | /* Restore the two words clobbered by the reply data. */ | |
356 | m->request.data = save_data; | |
357 | ||
358 | /* Restore the original mach_msg options. | |
359 | OPTION may have had MACH_RCV_TIMEOUT added, | |
360 | and/or MACH_SEND_MSG removed. */ | |
361 | option = user_option; | |
362 | timeout = user_timeout; | |
363 | ||
364 | /* Now we are ready to repeat the original message send. */ | |
365 | goto message; | |
366 | } | |
367 | else | |
368 | /* The EINTR return indicates cancellation, | |
369 | so clear the flag. */ | |
370 | ss->cancel = 0; | |
371 | } | |
372 | } | |
54da5be3 RM |
373 | break; |
374 | ||
375 | default: /* Quiet -Wswitch-enum. */ | |
ca18306b | 376 | break; |
54da5be3 RM |
377 | } |
378 | ||
379 | ss->intr_port = MACH_PORT_NULL; | |
380 | ||
381 | return err; | |
382 | } | |
7a8f45e3 | 383 | libc_hidden_def (_hurd_intr_rpc_mach_msg) |