1 /* Replacement for mach_msg used in interruptible Hurd RPCs.
2    Copyright (C) 1995-2022 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    <https://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
_hurd_intr_rpc_mach_msg(mach_msg_header_t * msg,mach_msg_option_t option,mach_msg_size_t send_size,mach_msg_size_t rcv_size,mach_port_t rcv_name,mach_msg_timeout_t timeout,mach_port_t notify)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   /* Note that the signal trampoline code might modify our OPTION!  */
118   err = INTR_MSG_TRAP (msg, option, send_size,
119 		       rcv_size, rcv_name, timeout, notify,
120 		       &ss->cancel, &ss->intr_port);
121 
122   switch (err)
123     {
124     case MACH_RCV_TIMED_OUT:
125       if (user_option & MACH_RCV_TIMEOUT)
126 	/* The real user RPC timed out.  */
127 	break;
128       else
129 	/* The operation was supposedly interrupted, but still has
130 	   not returned.  Declare it interrupted.  */
131 	goto dead;
132 
133     case MACH_SEND_INTERRUPTED: /* RPC didn't get out.  */
134       if (!(option & MACH_SEND_MSG))
135 	{
136 	  /* Oh yes, it did!  Since we were not doing a message send,
137 	     this return code cannot have come from the kernel!
138 	     Instead, it was the signal thread mutating our state to tell
139 	     us not to enter this RPC.  However, we are already in the receive!
140 	     Since the signal thread thought we weren't in the RPC yet,
141 	     it didn't do an interrupt_operation.
142 	     XXX */
143 	  goto retry_receive;
144 	}
145       /* FALLTHROUGH */
146 
147       /* These are the other codes that mean a pseudo-receive modified
148 	 the message buffer and we might need to clean up the port rights.  */
149     case MACH_SEND_TIMED_OUT:
150     case MACH_SEND_INVALID_NOTIFY:
151 #ifdef MACH_SEND_NO_NOTIFY
152     case MACH_SEND_NO_NOTIFY:
153 #endif
154 #ifdef MACH_SEND_NOTIFY_IN_PROGRESS
155     case MACH_SEND_NOTIFY_IN_PROGRESS:
156 #endif
157       if (MACH_MSGH_BITS_REMOTE (msg->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND)
158 	{
159 	  __mach_port_deallocate (__mach_task_self (), msg->msgh_remote_port);
160 	  msg->msgh_bits
161 	    = (MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND,
162 			       MACH_MSGH_BITS_LOCAL (msg->msgh_bits))
163 	       | MACH_MSGH_BITS_OTHER (msg->msgh_bits));
164 	}
165       if (msg->msgh_bits & MACH_MSGH_BITS_COMPLEX)
166 	{
167 #ifndef MACH_MSG_PORT_DESCRIPTOR
168 	  /* Check for MOVE_SEND rights in the message.  These hold refs
169 	     that we need to release in case the message is in fact never
170 	     re-sent later.  Since it might in fact be re-sent, we turn
171 	     these into COPY_SEND's after deallocating the extra user ref;
172 	     the caller is responsible for still holding a ref to go with
173 	     the original COPY_SEND right, so the resend copies it again.  */
174 
175 	  mach_msg_type_long_t *ty = (void *) (msg + 1);
176 	  while ((void *) ty < (void *) msg + msg->msgh_size)
177 	    {
178 	      mach_msg_type_name_t name;
179 	      mach_msg_type_size_t size;
180 	      mach_msg_type_number_t number;
181 
182 	      inline void clean_ports (mach_port_t *ports, int dealloc)
183 		{
184 		  mach_msg_type_number_t i;
185 		  switch (name)
186 		    {
187 		    case MACH_MSG_TYPE_MOVE_SEND:
188 		      for (i = 0; i < number; i++)
189 			__mach_port_deallocate (__mach_task_self (), *ports++);
190 		      if (ty->msgtl_header.msgt_longform)
191 			ty->msgtl_name = MACH_MSG_TYPE_COPY_SEND;
192 		      else
193 			ty->msgtl_header.msgt_name = MACH_MSG_TYPE_COPY_SEND;
194 		      break;
195 		    case MACH_MSG_TYPE_COPY_SEND:
196 		    case MACH_MSG_TYPE_MOVE_RECEIVE:
197 		      break;
198 		    default:
199 		      if (MACH_MSG_TYPE_PORT_ANY (name))
200 			assert (! "unexpected port type in interruptible RPC");
201 		    }
202 		  if (dealloc)
203 		    __vm_deallocate (__mach_task_self (),
204 				     (vm_address_t) ports,
205 				     number * sizeof (mach_port_t));
206 		}
207 
208 	      if (ty->msgtl_header.msgt_longform)
209 		{
210 		  name = ty->msgtl_name;
211 		  size = ty->msgtl_size;
212 		  number = ty->msgtl_number;
213 		  ty = (void *) ty + sizeof (mach_msg_type_long_t);
214 		}
215 	      else
216 		{
217 		  name = ty->msgtl_header.msgt_name;
218 		  size = ty->msgtl_header.msgt_size;
219 		  number = ty->msgtl_header.msgt_number;
220 		  ty = (void *) ty + sizeof (mach_msg_type_t);
221 		}
222 
223 	      if (ty->msgtl_header.msgt_inline)
224 		{
225 		  clean_ports ((void *) ty, 0);
226 		  /* calculate length of data in bytes, rounding up */
227 		  ty = (void *) ty + (((((number * size) + 7) >> 3)
228 				       + sizeof (mach_msg_type_t) - 1)
229 				      &~ (sizeof (mach_msg_type_t) - 1));
230 		}
231 	      else
232 		{
233 		  clean_ports (*(void **) ty,
234 			       ty->msgtl_header.msgt_deallocate);
235 		  ty = (void *) ty + sizeof (void *);
236 		}
237 	    }
238 #else  /* Untyped Mach IPC flavor. */
239 	  mach_msg_body_t *body = (void *) (msg + 1);
240 	  mach_msg_descriptor_t *desc = (void *) (body + 1);
241 	  mach_msg_descriptor_t *desc_end = desc + body->msgh_descriptor_count;
242 	  for (; desc < desc_end; ++desc)
243 	    switch (desc->type.type)
244 	      {
245 	      case MACH_MSG_PORT_DESCRIPTOR:
246 		switch (desc->port.disposition)
247 		  {
248 		  case MACH_MSG_TYPE_MOVE_SEND:
249 		    __mach_port_deallocate (mach_task_self (),
250 					    desc->port.name);
251 		    desc->port.disposition = MACH_MSG_TYPE_COPY_SEND;
252 		    break;
253 		  case MACH_MSG_TYPE_COPY_SEND:
254 		  case MACH_MSG_TYPE_MOVE_RECEIVE:
255 		    break;
256 		  default:
257 		    assert (! "unexpected port type in interruptible RPC");
258 		  }
259 		break;
260 	      case MACH_MSG_OOL_DESCRIPTOR:
261 		if (desc->out_of_line.deallocate)
262 		  __vm_deallocate (__mach_task_self (),
263 				   (vm_address_t) desc->out_of_line.address,
264 				   desc->out_of_line.size);
265 		break;
266 	      case MACH_MSG_OOL_PORTS_DESCRIPTOR:
267 		switch (desc->ool_ports.disposition)
268 		  {
269 		  case MACH_MSG_TYPE_MOVE_SEND:
270 		    {
271 		      mach_msg_size_t i;
272 		      const mach_port_t *ports = desc->ool_ports.address;
273 		      for (i = 0; i < desc->ool_ports.count; ++i)
274 			__mach_port_deallocate (__mach_task_self (), ports[i]);
275 		      desc->ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;
276 		      break;
277 		    }
278 		  case MACH_MSG_TYPE_COPY_SEND:
279 		  case MACH_MSG_TYPE_MOVE_RECEIVE:
280 		    break;
281 		  default:
282 		    assert (! "unexpected port type in interruptible RPC");
283 		  }
284 		if (desc->ool_ports.deallocate)
285 		  __vm_deallocate (__mach_task_self (),
286 				   (vm_address_t) desc->ool_ports.address,
287 				   desc->ool_ports.count
288 				   * sizeof (mach_port_t));
289 		break;
290 	      default:
291 		assert (! "unexpected descriptor type in interruptible RPC");
292 	      }
293 #endif
294 	}
295       break;
296 
297     case EINTR:
298       /* Either the process was stopped and continued,
299 	 or the server doesn't support interrupt_operation.  */
300       if (ss->intr_port != MACH_PORT_NULL)
301 	/* If this signal was for us and it should interrupt calls, the
302 	   signal thread will have cleared SS->intr_port.
303 	   Since it's not cleared, the signal was for another thread,
304 	   or SA_RESTART is set.  Restart the interrupted call.  */
305 	{
306 	  /* Make sure we have a valid reply port.  The one we were using
307 	     may have been destroyed by interruption.  */
308 	  m->header.msgh_local_port = rcv_name = __mig_get_reply_port ();
309 	  m->header.msgh_bits = msgh_bits;
310 	  option = user_option;
311 	  timeout = user_timeout;
312 	  goto message;
313 	}
314       err = EINTR;
315 
316       /* The EINTR return indicates cancellation, so clear the flag.  */
317       ss->cancel = 0;
318       break;
319 
320     case MACH_RCV_PORT_DIED:
321       /* Server didn't respond to interrupt_operation,
322 	 so the signal thread destroyed the reply port.  */
323       /* FALLTHROUGH */
324 
325     dead:
326       err = EIEIO;
327 
328       /* The EIEIO return indicates cancellation, so clear the flag.  */
329       ss->cancel = 0;
330       break;
331 
332     case MACH_RCV_INTERRUPTED:	/* RPC sent; no reply.  */
333       option &= ~MACH_SEND_MSG;	/* Don't send again.  */
334     retry_receive:
335       if (ss->intr_port == MACH_PORT_NULL)
336 	{
337 	  /* This signal or cancellation was for us.  We need to wait for
338              the reply, but not hang forever.  */
339 	  option |= MACH_RCV_TIMEOUT;
340 	  /* Never decrease the user's timeout.  */
341 	  if (!(user_option & MACH_RCV_TIMEOUT)
342 	      || timeout > _hurd_interrupted_rpc_timeout)
343 	    timeout = _hurd_interrupted_rpc_timeout;
344 	}
345       else
346 	{
347 	  option = user_option;
348 	  timeout = user_timeout;
349 	}
350       goto message;		/* Retry the receive.  */
351 
352     case MACH_MSG_SUCCESS:
353       {
354 	/* We got a reply.  Was it EINTR?  */
355 #ifdef MACH_MSG_TYPE_BIT
356 	const union
357 	{
358 	  mach_msg_type_t t;
359 	  int i;
360 	} check =
361 	  { t: { MACH_MSG_TYPE_INTEGER_T, sizeof (integer_t) * 8,
362 		 1, TRUE, FALSE, FALSE, 0 } };
363 #endif
364 
365         if (m->reply.RetCode == EINTR
366 	    && m->header.msgh_size == sizeof m->reply
367 #ifdef MACH_MSG_TYPE_BIT
368 	    && m->check.type == check.i
369 #endif
370 	    && !(m->header.msgh_bits & MACH_MSGH_BITS_COMPLEX))
371 	  {
372 	    /* It is indeed EINTR.  Is the interrupt for us?  */
373 	    if (ss->intr_port != MACH_PORT_NULL)
374 	      {
375 		/* Nope; repeat the RPC.
376 		   XXX Resources moved? */
377 
378 		assert (m->header.msgh_id == msgid + 100);
379 
380 		/* We know we have a valid reply port, because we just
381 		   received the EINTR reply on it.  Restore it and the
382 		   other fields in the message header needed for send,
383 		   since the header now reflects receipt of the reply.  */
384 		m->header.msgh_local_port = rcv_name;
385 		m->header.msgh_remote_port = remote_port;
386 		m->header.msgh_id = msgid;
387 		m->header.msgh_bits = msgh_bits;
388 		/* Restore the two words clobbered by the reply data.  */
389 		m->request.data = save_data;
390 
391 		/* Restore the original mach_msg options.
392 		   OPTION may have had MACH_RCV_TIMEOUT added,
393 		   and/or MACH_SEND_MSG removed.  */
394 		option = user_option;
395 		timeout = user_timeout;
396 
397 		/* Now we are ready to repeat the original message send.  */
398 		goto message;
399 	      }
400 	    else
401 	      /* The EINTR return indicates cancellation,
402 		 so clear the flag.  */
403 	      ss->cancel = 0;
404 	  }
405       }
406       break;
407 
408     default:			/* Quiet -Wswitch-enum.  */
409       break;
410     }
411 
412   ss->intr_port = MACH_PORT_NULL;
413 
414   return err;
415 }
416 libc_hidden_def (_hurd_intr_rpc_mach_msg)
417