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