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