Back to index

glibc  2.9
intr-msg.c
Go to the documentation of this file.
00001 /* Replacement for mach_msg used in interruptible Hurd RPCs.
00002    Copyright (C) 1995,96,97,98,99,2000,2001,2002,2005
00003        Free Software Foundation, Inc.
00004    This file is part of the GNU C Library.
00005 
00006    The GNU C Library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Lesser General Public
00008    License as published by the Free Software Foundation; either
00009    version 2.1 of the License, or (at your option) any later version.
00010 
00011    The GNU C Library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Lesser General Public License for more details.
00015 
00016    You should have received a copy of the GNU Lesser General Public
00017    License along with the GNU C Library; if not, write to the Free
00018    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
00019    02111-1307 USA.  */
00020 
00021 #include <mach.h>
00022 #include <mach/mig_errors.h>
00023 #include <mach/mig_support.h>
00024 #include <hurd/signal.h>
00025 #include <assert.h>
00026 
00027 #include "intr-msg.h"
00028 
00029 #ifdef NDR_CHAR_ASCII              /* OSF Mach flavors have different names.  */
00030 # define mig_reply_header_t mig_reply_error_t
00031 #endif
00032 
00033 error_t
00034 _hurd_intr_rpc_mach_msg (mach_msg_header_t *msg,
00035                       mach_msg_option_t option,
00036                       mach_msg_size_t send_size,
00037                       mach_msg_size_t rcv_size,
00038                       mach_port_t rcv_name,
00039                       mach_msg_timeout_t timeout,
00040                       mach_port_t notify)
00041 {
00042   error_t err;
00043   struct hurd_sigstate *ss;
00044   const mach_msg_option_t user_option = option;
00045   const mach_msg_timeout_t user_timeout = timeout;
00046 
00047   struct clobber
00048   {
00049 #ifdef NDR_CHAR_ASCII
00050     NDR_record_t ndr;
00051 #else
00052     mach_msg_type_t type;
00053 #endif
00054     error_t err;
00055   };
00056   union msg
00057   {
00058     mach_msg_header_t header;
00059     mig_reply_header_t reply;
00060     struct
00061     {
00062       mach_msg_header_t header;
00063 #ifdef NDR_CHAR_ASCII
00064       NDR_record_t ndr;
00065 #else
00066       int type;
00067 #endif
00068       int code;
00069     } check;
00070     struct
00071     {
00072       mach_msg_header_t header;
00073       struct clobber data;
00074     } request;
00075   };
00076   union msg *const m = (void *) msg;
00077   mach_msg_bits_t msgh_bits;
00078   mach_port_t remote_port;
00079   mach_msg_id_t msgid;
00080   struct clobber save_data;
00081 
00082   if ((option & (MACH_SEND_MSG|MACH_RCV_MSG)) != (MACH_SEND_MSG|MACH_RCV_MSG)
00083       || _hurd_msgport_thread == MACH_PORT_NULL)
00084     {
00085       /* Either this is not an RPC (i.e., only a send or only a receive),
00086         so it can't be interruptible; or, the signal thread is not set up
00087         yet, so we cannot do the normal signal magic.  Do a normal,
00088         uninterruptible mach_msg call instead.  */
00089       return __mach_msg (&m->header, option, send_size, rcv_size, rcv_name,
00090                       timeout, notify);
00091     }
00092 
00093   ss = _hurd_self_sigstate ();
00094 
00095   /* Save state that gets clobbered by an EINTR reply message.
00096      We will need to restore it if we want to retry the RPC.  */
00097   msgh_bits = m->header.msgh_bits;
00098   remote_port = m->header.msgh_remote_port;
00099   msgid = m->header.msgh_id;
00100   assert (rcv_size >= sizeof m->request);
00101   save_data = m->request.data;
00102 
00103   /* Tell the signal thread that we are doing an interruptible RPC on
00104      this port.  If we get a signal and should return EINTR, the signal
00105      thread will set this variable to MACH_PORT_NULL.  The RPC might
00106      return EINTR when some other thread gets a signal, in which case we
00107      want to restart our call.  */
00108   ss->intr_port = m->header.msgh_remote_port;
00109 
00110   /* A signal may arrive here, after intr_port is set, but before the
00111      mach_msg system call.  The signal handler might do an interruptible
00112      RPC, and clobber intr_port; then it would not be set properly when we
00113      actually did send the RPC, and a later signal wouldn't interrupt that
00114      RPC.  So, _hurd_setup_sighandler saves intr_port in the sigcontext,
00115      and sigreturn restores it.  */
00116 
00117  message:
00118 
00119   /* XXX
00120      At all points here (once SS->intr_port is set), the signal thread
00121      thinks we are "about to enter the syscall", and might mutate our
00122      return-value register.  This is bogus.
00123    */
00124 
00125   if (ss->cancel)
00126     {
00127       /* We have been cancelled.  Don't do an RPC at all.  */
00128       ss->intr_port = MACH_PORT_NULL;
00129       ss->cancel = 0;
00130       return EINTR;
00131     }
00132 
00133   /* Note that the signal trampoline code might modify our OPTION!  */
00134   err = INTR_MSG_TRAP (msg, option, send_size,
00135                      rcv_size, rcv_name, timeout, notify);
00136 
00137   switch (err)
00138     {
00139     case MACH_RCV_TIMED_OUT:
00140       if (user_option & MACH_RCV_TIMEOUT)
00141        /* The real user RPC timed out.  */
00142        break;
00143       else
00144        /* The operation was supposedly interrupted, but still has
00145           not returned.  Declare it interrupted.  */
00146        goto interrupted;
00147 
00148     case MACH_SEND_INTERRUPTED: /* RPC didn't get out.  */
00149       if (!(option & MACH_SEND_MSG))
00150        {
00151          /* Oh yes, it did!  Since we were not doing a message send,
00152             this return code cannot have come from the kernel!
00153             Instead, it was the signal thread mutating our state to tell
00154             us not to enter this RPC.  However, we are already in the receive!
00155             Since the signal thread thought we weren't in the RPC yet,
00156             it didn't do an interrupt_operation.
00157             XXX */
00158          goto retry_receive;
00159        }
00160       /* FALLTHROUGH */
00161 
00162       /* These are the other codes that mean a pseudo-receive modified
00163         the message buffer and we might need to clean up the port rights.  */
00164     case MACH_SEND_TIMED_OUT:
00165     case MACH_SEND_INVALID_NOTIFY:
00166 #ifdef MACH_SEND_NO_NOTIFY
00167     case MACH_SEND_NO_NOTIFY:
00168 #endif
00169 #ifdef MACH_SEND_NOTIFY_IN_PROGRESS
00170     case MACH_SEND_NOTIFY_IN_PROGRESS:
00171 #endif
00172       if (MACH_MSGH_BITS_REMOTE (msg->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND)
00173        {
00174          __mach_port_deallocate (__mach_task_self (), msg->msgh_remote_port);
00175          msg->msgh_bits
00176            = (MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND,
00177                             MACH_MSGH_BITS_LOCAL (msg->msgh_bits))
00178               | MACH_MSGH_BITS_OTHER (msg->msgh_bits));
00179        }
00180       if (msg->msgh_bits & MACH_MSGH_BITS_COMPLEX)
00181        {
00182 #ifndef MACH_MSG_PORT_DESCRIPTOR
00183          /* Check for MOVE_SEND rights in the message.  These hold refs
00184             that we need to release in case the message is in fact never
00185             re-sent later.  Since it might in fact be re-sent, we turn
00186             these into COPY_SEND's after deallocating the extra user ref;
00187             the caller is responsible for still holding a ref to go with
00188             the original COPY_SEND right, so the resend copies it again.  */
00189 
00190          mach_msg_type_long_t *ty = (void *) (msg + 1);
00191          while ((void *) ty < (void *) msg + msg->msgh_size)
00192            {
00193              mach_msg_type_name_t name;
00194              mach_msg_type_size_t size;
00195              mach_msg_type_number_t number;
00196 
00197              inline void clean_ports (mach_port_t *ports, int dealloc)
00198               {
00199                 mach_msg_type_number_t i;
00200                 switch (name)
00201                   {
00202                   case MACH_MSG_TYPE_MOVE_SEND:
00203                     for (i = 0; i < number; i++)
00204                      __mach_port_deallocate (__mach_task_self (), *ports++);
00205                     if (ty->msgtl_header.msgt_longform)
00206                      ty->msgtl_name = MACH_MSG_TYPE_COPY_SEND;
00207                     else
00208                      ty->msgtl_header.msgt_name = MACH_MSG_TYPE_COPY_SEND;
00209                     break;
00210                   case MACH_MSG_TYPE_COPY_SEND:
00211                   case MACH_MSG_TYPE_MOVE_RECEIVE:
00212                     break;
00213                   default:
00214                     if (MACH_MSG_TYPE_PORT_ANY (name))
00215                      assert (! "unexpected port type in interruptible RPC");
00216                   }
00217                 if (dealloc)
00218                   __vm_deallocate (__mach_task_self (),
00219                                  (vm_address_t) ports,
00220                                  number * sizeof (mach_port_t));
00221               }
00222 
00223              if (ty->msgtl_header.msgt_longform)
00224               {
00225                 name = ty->msgtl_name;
00226                 size = ty->msgtl_size;
00227                 number = ty->msgtl_number;
00228                 ty = (void *) ty + sizeof (mach_msg_type_long_t);
00229               }
00230              else
00231               {
00232                 name = ty->msgtl_header.msgt_name;
00233                 size = ty->msgtl_header.msgt_size;
00234                 number = ty->msgtl_header.msgt_number;
00235                 ty = (void *) ty + sizeof (mach_msg_type_t);
00236               }
00237 
00238              if (ty->msgtl_header.msgt_inline)
00239               {
00240                 clean_ports ((void *) ty, 0);
00241                 /* calculate length of data in bytes, rounding up */
00242                 ty = (void *) ty + (((((number * size) + 7) >> 3)
00243                                    + sizeof (mach_msg_type_t) - 1)
00244                                   &~ (sizeof (mach_msg_type_t) - 1));
00245               }
00246              else
00247               {
00248                 clean_ports (*(void **) ty,
00249                             ty->msgtl_header.msgt_deallocate);
00250                 ty = (void *) ty + sizeof (void *);
00251               }
00252            }
00253 #else  /* Untyped Mach IPC flavor. */
00254          mach_msg_body_t *body = (void *) (msg + 1);
00255          mach_msg_descriptor_t *desc = (void *) (body + 1);
00256          mach_msg_descriptor_t *desc_end = desc + body->msgh_descriptor_count;
00257          for (; desc < desc_end; ++desc)
00258            switch (desc->type.type)
00259              {
00260              case MACH_MSG_PORT_DESCRIPTOR:
00261               switch (desc->port.disposition)
00262                 {
00263                 case MACH_MSG_TYPE_MOVE_SEND:
00264                   __mach_port_deallocate (mach_task_self (),
00265                                        desc->port.name);
00266                   desc->port.disposition = MACH_MSG_TYPE_COPY_SEND;
00267                   break;
00268                 case MACH_MSG_TYPE_COPY_SEND:
00269                 case MACH_MSG_TYPE_MOVE_RECEIVE:
00270                   break;
00271                 default:
00272                   assert (! "unexpected port type in interruptible RPC");
00273                 }
00274               break;
00275              case MACH_MSG_OOL_DESCRIPTOR:
00276               if (desc->out_of_line.deallocate)
00277                 __vm_deallocate (__mach_task_self (),
00278                                (vm_address_t) desc->out_of_line.address,
00279                                desc->out_of_line.size);
00280               break;
00281              case MACH_MSG_OOL_PORTS_DESCRIPTOR:
00282               switch (desc->ool_ports.disposition)
00283                 {
00284                 case MACH_MSG_TYPE_MOVE_SEND:
00285                   {
00286                     mach_msg_size_t i;
00287                     const mach_port_t *ports = desc->ool_ports.address;
00288                     for (i = 0; i < desc->ool_ports.count; ++i)
00289                      __mach_port_deallocate (__mach_task_self (), ports[i]);
00290                     desc->ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;
00291                     break;
00292                   }
00293                 case MACH_MSG_TYPE_COPY_SEND:
00294                 case MACH_MSG_TYPE_MOVE_RECEIVE:
00295                   break;
00296                 default:
00297                   assert (! "unexpected port type in interruptible RPC");
00298                 }
00299               if (desc->ool_ports.deallocate)
00300                 __vm_deallocate (__mach_task_self (),
00301                                (vm_address_t) desc->ool_ports.address,
00302                                desc->ool_ports.count
00303                                * sizeof (mach_port_t));
00304               break;
00305              default:
00306               assert (! "unexpected descriptor type in interruptible RPC");
00307              }
00308 #endif
00309        }
00310       break;
00311 
00312     case EINTR:
00313       /* Either the process was stopped and continued,
00314         or the server doesn't support interrupt_operation.  */
00315       if (ss->intr_port != MACH_PORT_NULL)
00316        /* If this signal was for us and it should interrupt calls, the
00317           signal thread will have cleared SS->intr_port.
00318           Since it's not cleared, the signal was for another thread,
00319           or SA_RESTART is set.  Restart the interrupted call.  */
00320        {
00321          /* Make sure we have a valid reply port.  The one we were using
00322             may have been destroyed by interruption.  */
00323          m->header.msgh_local_port = rcv_name = __mig_get_reply_port ();
00324          m->header.msgh_bits = msgh_bits;
00325          option = user_option;
00326          timeout = user_timeout;
00327          goto message;
00328        }
00329       /* FALLTHROUGH */
00330 
00331     case MACH_RCV_PORT_DIED:
00332       /* Server didn't respond to interrupt_operation,
00333         so the signal thread destroyed the reply port.  */
00334       /* FALLTHROUGH */
00335 
00336     interrupted:
00337       err = EINTR;
00338 
00339       /* The EINTR return indicates cancellation, so clear the flag.  */
00340       ss->cancel = 0;
00341       break;
00342 
00343     case MACH_RCV_INTERRUPTED:     /* RPC sent; no reply.  */
00344       option &= ~MACH_SEND_MSG;    /* Don't send again.  */
00345     retry_receive:
00346       if (ss->intr_port == MACH_PORT_NULL)
00347        {
00348          /* This signal or cancellation was for us.  We need to wait for
00349              the reply, but not hang forever.  */
00350          option |= MACH_RCV_TIMEOUT;
00351          /* Never decrease the user's timeout.  */
00352          if (!(user_option & MACH_RCV_TIMEOUT)
00353              || timeout > _hurd_interrupted_rpc_timeout)
00354            timeout = _hurd_interrupted_rpc_timeout;
00355        }
00356       else
00357        {
00358          option = user_option;
00359          timeout = user_timeout;
00360        }
00361       goto message;         /* Retry the receive.  */
00362 
00363     case MACH_MSG_SUCCESS:
00364       {
00365        /* We got a reply.  Was it EINTR?  */
00366 #ifdef MACH_MSG_TYPE_BIT
00367        const union
00368        {
00369          mach_msg_type_t t;
00370          int i;
00371        } check =
00372          { t: { MACH_MSG_TYPE_INTEGER_T, sizeof (integer_t) * 8,
00373                1, TRUE, FALSE, FALSE, 0 } };
00374 #endif
00375 
00376         if (m->reply.RetCode == EINTR &&
00377            m->header.msgh_size == sizeof m->reply &&
00378 #ifdef MACH_MSG_TYPE_BIT
00379            m->check.type == check.i &&
00380 #endif
00381            !(m->header.msgh_bits & MACH_MSGH_BITS_COMPLEX))
00382          {
00383            /* It is indeed EINTR.  Is the interrupt for us?  */
00384            if (ss->intr_port != MACH_PORT_NULL)
00385              {
00386               /* Nope; repeat the RPC.
00387                  XXX Resources moved? */
00388 
00389               assert (m->header.msgh_id == msgid + 100);
00390 
00391               /* We know we have a valid reply port, because we just
00392                  received the EINTR reply on it.  Restore it and the
00393                  other fields in the message header needed for send,
00394                  since the header now reflects receipt of the reply.  */
00395               m->header.msgh_local_port = rcv_name;
00396               m->header.msgh_remote_port = remote_port;
00397               m->header.msgh_id = msgid;
00398               m->header.msgh_bits = msgh_bits;
00399               /* Restore the two words clobbered by the reply data.  */
00400               m->request.data = save_data;
00401 
00402               /* Restore the original mach_msg options.
00403                  OPTION may have had MACH_RCV_TIMEOUT added,
00404                  and/or MACH_SEND_MSG removed.  */
00405               option = user_option;
00406               timeout = user_timeout;
00407 
00408               /* Now we are ready to repeat the original message send.  */
00409               goto message;
00410              }
00411            else
00412              /* The EINTR return indicates cancellation,
00413                so clear the flag.  */
00414              ss->cancel = 0;
00415          }
00416       }
00417       break;
00418 
00419     default:                /* Quiet -Wswitch-enum.  */
00420       break;
00421     }
00422 
00423   ss->intr_port = MACH_PORT_NULL;
00424 
00425   return err;
00426 }