Blame hurd/intr-msg.c

Packit 6c4009
/* Replacement for mach_msg used in interruptible Hurd RPCs.
Packit 6c4009
   Copyright (C) 1995-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <mach.h>
Packit 6c4009
#include <mach/mig_errors.h>
Packit 6c4009
#include <mach/mig_support.h>
Packit 6c4009
#include <hurd/signal.h>
Packit 6c4009
#include <assert.h>
Packit 6c4009
Packit 6c4009
#include "intr-msg.h"
Packit 6c4009
Packit 6c4009
#ifdef NDR_CHAR_ASCII		/* OSF Mach flavors have different names.  */
Packit 6c4009
# define mig_reply_header_t	mig_reply_error_t
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
error_t
Packit 6c4009
_hurd_intr_rpc_mach_msg (mach_msg_header_t *msg,
Packit 6c4009
			 mach_msg_option_t option,
Packit 6c4009
			 mach_msg_size_t send_size,
Packit 6c4009
			 mach_msg_size_t rcv_size,
Packit 6c4009
			 mach_port_t rcv_name,
Packit 6c4009
			 mach_msg_timeout_t timeout,
Packit 6c4009
			 mach_port_t notify)
Packit 6c4009
{
Packit 6c4009
  error_t err;
Packit 6c4009
  struct hurd_sigstate *ss;
Packit 6c4009
  const mach_msg_option_t user_option = option;
Packit 6c4009
  const mach_msg_timeout_t user_timeout = timeout;
Packit 6c4009
Packit 6c4009
  struct clobber
Packit 6c4009
  {
Packit 6c4009
#ifdef NDR_CHAR_ASCII
Packit 6c4009
    NDR_record_t ndr;
Packit 6c4009
#else
Packit 6c4009
    mach_msg_type_t type;
Packit 6c4009
#endif
Packit 6c4009
    error_t err;
Packit 6c4009
  };
Packit 6c4009
  union msg
Packit 6c4009
  {
Packit 6c4009
    mach_msg_header_t header;
Packit 6c4009
    mig_reply_header_t reply;
Packit 6c4009
    struct
Packit 6c4009
    {
Packit 6c4009
      mach_msg_header_t header;
Packit 6c4009
#ifdef NDR_CHAR_ASCII
Packit 6c4009
      NDR_record_t ndr;
Packit 6c4009
#else
Packit 6c4009
      int type;
Packit 6c4009
#endif
Packit 6c4009
      int code;
Packit 6c4009
    } check;
Packit 6c4009
    struct
Packit 6c4009
    {
Packit 6c4009
      mach_msg_header_t header;
Packit 6c4009
      struct clobber data;
Packit 6c4009
    } request;
Packit 6c4009
  };
Packit 6c4009
  union msg *const m = (void *) msg;
Packit 6c4009
  mach_msg_bits_t msgh_bits;
Packit 6c4009
  mach_port_t remote_port;
Packit 6c4009
  mach_msg_id_t msgid;
Packit 6c4009
  struct clobber save_data;
Packit 6c4009
Packit 6c4009
  if ((option & (MACH_SEND_MSG|MACH_RCV_MSG)) != (MACH_SEND_MSG|MACH_RCV_MSG)
Packit 6c4009
      || _hurd_msgport_thread == MACH_PORT_NULL)
Packit 6c4009
    {
Packit 6c4009
      /* Either this is not an RPC (i.e., only a send or only a receive),
Packit 6c4009
	 so it can't be interruptible; or, the signal thread is not set up
Packit 6c4009
	 yet, so we cannot do the normal signal magic.  Do a normal,
Packit 6c4009
	 uninterruptible mach_msg call instead.  */
Packit 6c4009
      return __mach_msg (&m->header, option, send_size, rcv_size, rcv_name,
Packit 6c4009
			 timeout, notify);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  ss = _hurd_self_sigstate ();
Packit 6c4009
Packit 6c4009
  /* Save state that gets clobbered by an EINTR reply message.
Packit 6c4009
     We will need to restore it if we want to retry the RPC.  */
Packit 6c4009
  msgh_bits = m->header.msgh_bits;
Packit 6c4009
  remote_port = m->header.msgh_remote_port;
Packit 6c4009
  msgid = m->header.msgh_id;
Packit 6c4009
  assert (rcv_size >= sizeof m->request);
Packit 6c4009
  save_data = m->request.data;
Packit 6c4009
Packit 6c4009
  /* Tell the signal thread that we are doing an interruptible RPC on
Packit 6c4009
     this port.  If we get a signal and should return EINTR, the signal
Packit 6c4009
     thread will set this variable to MACH_PORT_NULL.  The RPC might
Packit 6c4009
     return EINTR when some other thread gets a signal, in which case we
Packit 6c4009
     want to restart our call.  */
Packit 6c4009
  ss->intr_port = m->header.msgh_remote_port;
Packit 6c4009
Packit 6c4009
  /* A signal may arrive here, after intr_port is set, but before the
Packit 6c4009
     mach_msg system call.  The signal handler might do an interruptible
Packit 6c4009
     RPC, and clobber intr_port; then it would not be set properly when we
Packit 6c4009
     actually did send the RPC, and a later signal wouldn't interrupt that
Packit 6c4009
     RPC.  So, _hurd_setup_sighandler saves intr_port in the sigcontext,
Packit 6c4009
     and sigreturn restores it.  */
Packit 6c4009
Packit 6c4009
 message:
Packit 6c4009
Packit 6c4009
  /* XXX
Packit 6c4009
     At all points here (once SS->intr_port is set), the signal thread
Packit 6c4009
     thinks we are "about to enter the syscall", and might mutate our
Packit 6c4009
     return-value register.  This is bogus.
Packit 6c4009
   */
Packit 6c4009
Packit 6c4009
  if (ss->cancel)
Packit 6c4009
    {
Packit 6c4009
      /* We have been cancelled.  Don't do an RPC at all.  */
Packit 6c4009
      ss->intr_port = MACH_PORT_NULL;
Packit 6c4009
      ss->cancel = 0;
Packit 6c4009
      return EINTR;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Note that the signal trampoline code might modify our OPTION!  */
Packit 6c4009
  err = INTR_MSG_TRAP (msg, option, send_size,
Packit 6c4009
		       rcv_size, rcv_name, timeout, notify);
Packit 6c4009
Packit 6c4009
  switch (err)
Packit 6c4009
    {
Packit 6c4009
    case MACH_RCV_TIMED_OUT:
Packit 6c4009
      if (user_option & MACH_RCV_TIMEOUT)
Packit 6c4009
	/* The real user RPC timed out.  */
Packit 6c4009
	break;
Packit 6c4009
      else
Packit 6c4009
	/* The operation was supposedly interrupted, but still has
Packit 6c4009
	   not returned.  Declare it interrupted.  */
Packit 6c4009
	goto interrupted;
Packit 6c4009
Packit 6c4009
    case MACH_SEND_INTERRUPTED: /* RPC didn't get out.  */
Packit 6c4009
      if (!(option & MACH_SEND_MSG))
Packit 6c4009
	{
Packit 6c4009
	  /* Oh yes, it did!  Since we were not doing a message send,
Packit 6c4009
	     this return code cannot have come from the kernel!
Packit 6c4009
	     Instead, it was the signal thread mutating our state to tell
Packit 6c4009
	     us not to enter this RPC.  However, we are already in the receive!
Packit 6c4009
	     Since the signal thread thought we weren't in the RPC yet,
Packit 6c4009
	     it didn't do an interrupt_operation.
Packit 6c4009
	     XXX */
Packit 6c4009
	  goto retry_receive;
Packit 6c4009
	}
Packit 6c4009
      /* FALLTHROUGH */
Packit 6c4009
Packit 6c4009
      /* These are the other codes that mean a pseudo-receive modified
Packit 6c4009
	 the message buffer and we might need to clean up the port rights.  */
Packit 6c4009
    case MACH_SEND_TIMED_OUT:
Packit 6c4009
    case MACH_SEND_INVALID_NOTIFY:
Packit 6c4009
#ifdef MACH_SEND_NO_NOTIFY
Packit 6c4009
    case MACH_SEND_NO_NOTIFY:
Packit 6c4009
#endif
Packit 6c4009
#ifdef MACH_SEND_NOTIFY_IN_PROGRESS
Packit 6c4009
    case MACH_SEND_NOTIFY_IN_PROGRESS:
Packit 6c4009
#endif
Packit 6c4009
      if (MACH_MSGH_BITS_REMOTE (msg->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND)
Packit 6c4009
	{
Packit 6c4009
	  __mach_port_deallocate (__mach_task_self (), msg->msgh_remote_port);
Packit 6c4009
	  msg->msgh_bits
Packit 6c4009
	    = (MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND,
Packit 6c4009
			       MACH_MSGH_BITS_LOCAL (msg->msgh_bits))
Packit 6c4009
	       | MACH_MSGH_BITS_OTHER (msg->msgh_bits));
Packit 6c4009
	}
Packit 6c4009
      if (msg->msgh_bits & MACH_MSGH_BITS_COMPLEX)
Packit 6c4009
	{
Packit 6c4009
#ifndef MACH_MSG_PORT_DESCRIPTOR
Packit 6c4009
	  /* Check for MOVE_SEND rights in the message.  These hold refs
Packit 6c4009
	     that we need to release in case the message is in fact never
Packit 6c4009
	     re-sent later.  Since it might in fact be re-sent, we turn
Packit 6c4009
	     these into COPY_SEND's after deallocating the extra user ref;
Packit 6c4009
	     the caller is responsible for still holding a ref to go with
Packit 6c4009
	     the original COPY_SEND right, so the resend copies it again.  */
Packit 6c4009
Packit 6c4009
	  mach_msg_type_long_t *ty = (void *) (msg + 1);
Packit 6c4009
	  while ((void *) ty < (void *) msg + msg->msgh_size)
Packit 6c4009
	    {
Packit 6c4009
	      mach_msg_type_name_t name;
Packit 6c4009
	      mach_msg_type_size_t size;
Packit 6c4009
	      mach_msg_type_number_t number;
Packit 6c4009
Packit 6c4009
	      inline void clean_ports (mach_port_t *ports, int dealloc)
Packit 6c4009
		{
Packit 6c4009
		  mach_msg_type_number_t i;
Packit 6c4009
		  switch (name)
Packit 6c4009
		    {
Packit 6c4009
		    case MACH_MSG_TYPE_MOVE_SEND:
Packit 6c4009
		      for (i = 0; i < number; i++)
Packit 6c4009
			__mach_port_deallocate (__mach_task_self (), *ports++);
Packit 6c4009
		      if (ty->msgtl_header.msgt_longform)
Packit 6c4009
			ty->msgtl_name = MACH_MSG_TYPE_COPY_SEND;
Packit 6c4009
		      else
Packit 6c4009
			ty->msgtl_header.msgt_name = MACH_MSG_TYPE_COPY_SEND;
Packit 6c4009
		      break;
Packit 6c4009
		    case MACH_MSG_TYPE_COPY_SEND:
Packit 6c4009
		    case MACH_MSG_TYPE_MOVE_RECEIVE:
Packit 6c4009
		      break;
Packit 6c4009
		    default:
Packit 6c4009
		      if (MACH_MSG_TYPE_PORT_ANY (name))
Packit 6c4009
			assert (! "unexpected port type in interruptible RPC");
Packit 6c4009
		    }
Packit 6c4009
		  if (dealloc)
Packit 6c4009
		    __vm_deallocate (__mach_task_self (),
Packit 6c4009
				     (vm_address_t) ports,
Packit 6c4009
				     number * sizeof (mach_port_t));
Packit 6c4009
		}
Packit 6c4009
Packit 6c4009
	      if (ty->msgtl_header.msgt_longform)
Packit 6c4009
		{
Packit 6c4009
		  name = ty->msgtl_name;
Packit 6c4009
		  size = ty->msgtl_size;
Packit 6c4009
		  number = ty->msgtl_number;
Packit 6c4009
		  ty = (void *) ty + sizeof (mach_msg_type_long_t);
Packit 6c4009
		}
Packit 6c4009
	      else
Packit 6c4009
		{
Packit 6c4009
		  name = ty->msgtl_header.msgt_name;
Packit 6c4009
		  size = ty->msgtl_header.msgt_size;
Packit 6c4009
		  number = ty->msgtl_header.msgt_number;
Packit 6c4009
		  ty = (void *) ty + sizeof (mach_msg_type_t);
Packit 6c4009
		}
Packit 6c4009
Packit 6c4009
	      if (ty->msgtl_header.msgt_inline)
Packit 6c4009
		{
Packit 6c4009
		  clean_ports ((void *) ty, 0);
Packit 6c4009
		  /* calculate length of data in bytes, rounding up */
Packit 6c4009
		  ty = (void *) ty + (((((number * size) + 7) >> 3)
Packit 6c4009
				       + sizeof (mach_msg_type_t) - 1)
Packit 6c4009
				      &~ (sizeof (mach_msg_type_t) - 1));
Packit 6c4009
		}
Packit 6c4009
	      else
Packit 6c4009
		{
Packit 6c4009
		  clean_ports (*(void **) ty,
Packit 6c4009
			       ty->msgtl_header.msgt_deallocate);
Packit 6c4009
		  ty = (void *) ty + sizeof (void *);
Packit 6c4009
		}
Packit 6c4009
	    }
Packit 6c4009
#else  /* Untyped Mach IPC flavor. */
Packit 6c4009
	  mach_msg_body_t *body = (void *) (msg + 1);
Packit 6c4009
	  mach_msg_descriptor_t *desc = (void *) (body + 1);
Packit 6c4009
	  mach_msg_descriptor_t *desc_end = desc + body->msgh_descriptor_count;
Packit 6c4009
	  for (; desc < desc_end; ++desc)
Packit 6c4009
	    switch (desc->type.type)
Packit 6c4009
	      {
Packit 6c4009
	      case MACH_MSG_PORT_DESCRIPTOR:
Packit 6c4009
		switch (desc->port.disposition)
Packit 6c4009
		  {
Packit 6c4009
		  case MACH_MSG_TYPE_MOVE_SEND:
Packit 6c4009
		    __mach_port_deallocate (mach_task_self (),
Packit 6c4009
					    desc->port.name);
Packit 6c4009
		    desc->port.disposition = MACH_MSG_TYPE_COPY_SEND;
Packit 6c4009
		    break;
Packit 6c4009
		  case MACH_MSG_TYPE_COPY_SEND:
Packit 6c4009
		  case MACH_MSG_TYPE_MOVE_RECEIVE:
Packit 6c4009
		    break;
Packit 6c4009
		  default:
Packit 6c4009
		    assert (! "unexpected port type in interruptible RPC");
Packit 6c4009
		  }
Packit 6c4009
		break;
Packit 6c4009
	      case MACH_MSG_OOL_DESCRIPTOR:
Packit 6c4009
		if (desc->out_of_line.deallocate)
Packit 6c4009
		  __vm_deallocate (__mach_task_self (),
Packit 6c4009
				   (vm_address_t) desc->out_of_line.address,
Packit 6c4009
				   desc->out_of_line.size);
Packit 6c4009
		break;
Packit 6c4009
	      case MACH_MSG_OOL_PORTS_DESCRIPTOR:
Packit 6c4009
		switch (desc->ool_ports.disposition)
Packit 6c4009
		  {
Packit 6c4009
		  case MACH_MSG_TYPE_MOVE_SEND:
Packit 6c4009
		    {
Packit 6c4009
		      mach_msg_size_t i;
Packit 6c4009
		      const mach_port_t *ports = desc->ool_ports.address;
Packit 6c4009
		      for (i = 0; i < desc->ool_ports.count; ++i)
Packit 6c4009
			__mach_port_deallocate (__mach_task_self (), ports[i]);
Packit 6c4009
		      desc->ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;
Packit 6c4009
		      break;
Packit 6c4009
		    }
Packit 6c4009
		  case MACH_MSG_TYPE_COPY_SEND:
Packit 6c4009
		  case MACH_MSG_TYPE_MOVE_RECEIVE:
Packit 6c4009
		    break;
Packit 6c4009
		  default:
Packit 6c4009
		    assert (! "unexpected port type in interruptible RPC");
Packit 6c4009
		  }
Packit 6c4009
		if (desc->ool_ports.deallocate)
Packit 6c4009
		  __vm_deallocate (__mach_task_self (),
Packit 6c4009
				   (vm_address_t) desc->ool_ports.address,
Packit 6c4009
				   desc->ool_ports.count
Packit 6c4009
				   * sizeof (mach_port_t));
Packit 6c4009
		break;
Packit 6c4009
	      default:
Packit 6c4009
		assert (! "unexpected descriptor type in interruptible RPC");
Packit 6c4009
	      }
Packit 6c4009
#endif
Packit 6c4009
	}
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case EINTR:
Packit 6c4009
      /* Either the process was stopped and continued,
Packit 6c4009
	 or the server doesn't support interrupt_operation.  */
Packit 6c4009
      if (ss->intr_port != MACH_PORT_NULL)
Packit 6c4009
	/* If this signal was for us and it should interrupt calls, the
Packit 6c4009
	   signal thread will have cleared SS->intr_port.
Packit 6c4009
	   Since it's not cleared, the signal was for another thread,
Packit 6c4009
	   or SA_RESTART is set.  Restart the interrupted call.  */
Packit 6c4009
	{
Packit 6c4009
	  /* Make sure we have a valid reply port.  The one we were using
Packit 6c4009
	     may have been destroyed by interruption.  */
Packit 6c4009
	  m->header.msgh_local_port = rcv_name = __mig_get_reply_port ();
Packit 6c4009
	  m->header.msgh_bits = msgh_bits;
Packit 6c4009
	  option = user_option;
Packit 6c4009
	  timeout = user_timeout;
Packit 6c4009
	  goto message;
Packit 6c4009
	}
Packit 6c4009
      /* FALLTHROUGH */
Packit 6c4009
Packit 6c4009
    case MACH_RCV_PORT_DIED:
Packit 6c4009
      /* Server didn't respond to interrupt_operation,
Packit 6c4009
	 so the signal thread destroyed the reply port.  */
Packit 6c4009
      /* FALLTHROUGH */
Packit 6c4009
Packit 6c4009
    interrupted:
Packit 6c4009
      err = EINTR;
Packit 6c4009
Packit 6c4009
      /* The EINTR return indicates cancellation, so clear the flag.  */
Packit 6c4009
      ss->cancel = 0;
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    case MACH_RCV_INTERRUPTED:	/* RPC sent; no reply.  */
Packit 6c4009
      option &= ~MACH_SEND_MSG;	/* Don't send again.  */
Packit 6c4009
    retry_receive:
Packit 6c4009
      if (ss->intr_port == MACH_PORT_NULL)
Packit 6c4009
	{
Packit 6c4009
	  /* This signal or cancellation was for us.  We need to wait for
Packit 6c4009
             the reply, but not hang forever.  */
Packit 6c4009
	  option |= MACH_RCV_TIMEOUT;
Packit 6c4009
	  /* Never decrease the user's timeout.  */
Packit 6c4009
	  if (!(user_option & MACH_RCV_TIMEOUT)
Packit 6c4009
	      || timeout > _hurd_interrupted_rpc_timeout)
Packit 6c4009
	    timeout = _hurd_interrupted_rpc_timeout;
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  option = user_option;
Packit 6c4009
	  timeout = user_timeout;
Packit 6c4009
	}
Packit 6c4009
      goto message;		/* Retry the receive.  */
Packit 6c4009
Packit 6c4009
    case MACH_MSG_SUCCESS:
Packit 6c4009
      {
Packit 6c4009
	/* We got a reply.  Was it EINTR?  */
Packit 6c4009
#ifdef MACH_MSG_TYPE_BIT
Packit 6c4009
	const union
Packit 6c4009
	{
Packit 6c4009
	  mach_msg_type_t t;
Packit 6c4009
	  int i;
Packit 6c4009
	} check =
Packit 6c4009
	  { t: { MACH_MSG_TYPE_INTEGER_T, sizeof (integer_t) * 8,
Packit 6c4009
		 1, TRUE, FALSE, FALSE, 0 } };
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
        if (m->reply.RetCode == EINTR &&
Packit 6c4009
	    m->header.msgh_size == sizeof m->reply &&
Packit 6c4009
#ifdef MACH_MSG_TYPE_BIT
Packit 6c4009
	    m->check.type == check.i &&
Packit 6c4009
#endif
Packit 6c4009
	    !(m->header.msgh_bits & MACH_MSGH_BITS_COMPLEX))
Packit 6c4009
	  {
Packit 6c4009
	    /* It is indeed EINTR.  Is the interrupt for us?  */
Packit 6c4009
	    if (ss->intr_port != MACH_PORT_NULL)
Packit 6c4009
	      {
Packit 6c4009
		/* Nope; repeat the RPC.
Packit 6c4009
		   XXX Resources moved? */
Packit 6c4009
Packit 6c4009
		assert (m->header.msgh_id == msgid + 100);
Packit 6c4009
Packit 6c4009
		/* We know we have a valid reply port, because we just
Packit 6c4009
		   received the EINTR reply on it.  Restore it and the
Packit 6c4009
		   other fields in the message header needed for send,
Packit 6c4009
		   since the header now reflects receipt of the reply.  */
Packit 6c4009
		m->header.msgh_local_port = rcv_name;
Packit 6c4009
		m->header.msgh_remote_port = remote_port;
Packit 6c4009
		m->header.msgh_id = msgid;
Packit 6c4009
		m->header.msgh_bits = msgh_bits;
Packit 6c4009
		/* Restore the two words clobbered by the reply data.  */
Packit 6c4009
		m->request.data = save_data;
Packit 6c4009
Packit 6c4009
		/* Restore the original mach_msg options.
Packit 6c4009
		   OPTION may have had MACH_RCV_TIMEOUT added,
Packit 6c4009
		   and/or MACH_SEND_MSG removed.  */
Packit 6c4009
		option = user_option;
Packit 6c4009
		timeout = user_timeout;
Packit 6c4009
Packit 6c4009
		/* Now we are ready to repeat the original message send.  */
Packit 6c4009
		goto message;
Packit 6c4009
	      }
Packit 6c4009
	    else
Packit 6c4009
	      /* The EINTR return indicates cancellation,
Packit 6c4009
		 so clear the flag.  */
Packit 6c4009
	      ss->cancel = 0;
Packit 6c4009
	  }
Packit 6c4009
      }
Packit 6c4009
      break;
Packit 6c4009
Packit 6c4009
    default:			/* Quiet -Wswitch-enum.  */
Packit 6c4009
      break;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  ss->intr_port = MACH_PORT_NULL;
Packit 6c4009
Packit 6c4009
  return err;
Packit 6c4009
}
Packit 6c4009
libc_hidden_def (_hurd_intr_rpc_mach_msg)