1  /* Copyright (C) 2004-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     The GNU C Library is free software; you can redistribute it and/or
       5     modify it under the terms of the GNU Lesser General Public
       6     License as published by the Free Software Foundation; either
       7     version 2.1 of the License, or (at your option) any later version.
       8  
       9     The GNU C Library is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12     Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public
      15     License along with the GNU C Library; if not, see
      16     <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <assert.h>
      19  #include <errno.h>
      20  #include <fcntl.h>
      21  #include <mqueue.h>
      22  #include <pthread.h>
      23  #include <signal.h>
      24  #include <stdlib.h>
      25  #include <string.h>
      26  #include <sysdep.h>
      27  #include <unistd.h>
      28  #include <sys/socket.h>
      29  #include <not-cancel.h>
      30  #include <pthreadP.h>
      31  #include <shlib-compat.h>
      32  
      33  /* Defined in the kernel headers: */
      34  #define NOTIFY_COOKIE_LEN	32	/* Length of the cookie used.  */
      35  #define NOTIFY_WOKENUP		1	/* Code for notification.  */
      36  #define NOTIFY_REMOVED		2	/* Code for closed message queue
      37  					   of de-notification.  */
      38  
      39  
      40  /* Data structure for the queued notification requests.  */
      41  union notify_data
      42  {
      43    struct
      44    {
      45      void (*fct) (union sigval);	/* The function to run.  */
      46      union sigval param;		/* The parameter to pass.  */
      47      pthread_attr_t *attr;	/* Attributes to create the thread with.  */
      48      /* NB: on 64-bit machines the struct as a size of 24 bytes.  Which means
      49         byte 31 can still be used for returning the status.  */
      50    };
      51    char raw[NOTIFY_COOKIE_LEN];
      52  };
      53  
      54  
      55  /* Keep track of the initialization.  */
      56  static pthread_once_t once = PTHREAD_ONCE_INIT;
      57  
      58  
      59  /* The netlink socket.  */
      60  static int netlink_socket = -1;
      61  
      62  
      63  /* Barrier used to make sure data passed to the new thread is not
      64     reused by the parent.  */
      65  static pthread_barrier_t notify_barrier;
      66  
      67  
      68  /* Modify the signal mask.  We move this into a separate function so
      69     that the stack space needed for sigset_t is not deducted from what
      70     the thread can use.  */
      71  static int
      72  __attribute__ ((noinline))
      73  change_sigmask (int how, sigset_t *oss)
      74  {
      75    sigset_t ss;
      76    sigfillset (&ss);
      77    return __pthread_sigmask (how, &ss, oss);
      78  }
      79  
      80  
      81  /* The function used for the notification.  */
      82  static void *
      83  notification_function (void *arg)
      84  {
      85    /* Copy the function and parameter so that the parent thread can go
      86       on with its life.  */
      87    volatile union notify_data *data = (volatile union notify_data *) arg;
      88    void (*fct) (union sigval) = data->fct;
      89    union sigval param = data->param;
      90  
      91    /* Let the parent go.  */
      92    (void) __pthread_barrier_wait (¬ify_barrier);
      93  
      94    /* Make the thread detached.  */
      95    __pthread_detach (__pthread_self ());
      96  
      97    /* The parent thread has all signals blocked.  This is probably a
      98       bit surprising for this thread.  So we unblock all of them.  */
      99    (void) change_sigmask (SIG_UNBLOCK, NULL);
     100  
     101    /* Now run the user code.  */
     102    fct (param);
     103  
     104    /* And we are done.  */
     105    return NULL;
     106  }
     107  
     108  
     109  /* Helper thread.  */
     110  static void *
     111  helper_thread (void *arg)
     112  {
     113    while (1)
     114      {
     115        union notify_data data;
     116  
     117        ssize_t n = __recv (netlink_socket, &data, sizeof (data),
     118  			  MSG_NOSIGNAL | MSG_WAITALL);
     119        if (n < NOTIFY_COOKIE_LEN)
     120  	continue;
     121  
     122        if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_WOKENUP)
     123  	{
     124  	  /* Just create the thread as instructed.  There is no way to
     125  	     report a problem with creating a thread.  */
     126  	  pthread_t th;
     127  	  if (__pthread_create (&th, data.attr, notification_function, &data)
     128  	      == 0)
     129  	    /* Since we passed a pointer to DATA to the new thread we have
     130  	       to wait until it is done with it.  */
     131  	    (void) __pthread_barrier_wait (¬ify_barrier);
     132  	}
     133        else if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_REMOVED && data.attr != NULL)
     134  	{
     135  	  /* The only state we keep is the copy of the thread attributes.  */
     136  	  __pthread_attr_destroy (data.attr);
     137  	  free (data.attr);
     138  	}
     139      }
     140    return NULL;
     141  }
     142  
     143  
     144  void
     145  __mq_notify_fork_subprocess (void)
     146  {
     147    once = PTHREAD_ONCE_INIT;
     148  }
     149  
     150  
     151  static void
     152  init_mq_netlink (void)
     153  {
     154    /* This code might be called a second time after fork().  The file
     155       descriptor is inherited from the parent.  */
     156    if (netlink_socket == -1)
     157      {
     158        /* Just a normal netlink socket, not bound.  */
     159        netlink_socket = __socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, 0);
     160        /* No need to do more if we have no socket.  */
     161        if (netlink_socket == -1)
     162  	return;
     163      }
     164  
     165    int err = 1;
     166  
     167    /* Initialize the barrier.  */
     168    if (__pthread_barrier_init (¬ify_barrier, NULL, 2) == 0)
     169      {
     170        /* Create the helper thread.  */
     171        pthread_attr_t attr;
     172        __pthread_attr_init (&attr);
     173        __pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
     174        /* We do not need much stack space, the bare minimum will be enough.  */
     175        __pthread_attr_setstacksize (&attr, __pthread_get_minstack (&attr));
     176  
     177        /* Temporarily block all signals so that the newly created
     178  	 thread inherits the mask.  */
     179        sigset_t oss;
     180        int have_no_oss = change_sigmask (SIG_BLOCK, &oss);
     181  
     182        pthread_t th;
     183        err = __pthread_create (&th, &attr, helper_thread, NULL);
     184  
     185        /* Reset the signal mask.  */
     186        if (!have_no_oss)
     187  	__pthread_sigmask (SIG_SETMASK, &oss, NULL);
     188  
     189        __pthread_attr_destroy (&attr);
     190      }
     191  
     192    if (err != 0)
     193      {
     194        __close_nocancel_nostatus (netlink_socket);
     195        netlink_socket = -1;
     196      }
     197  }
     198  
     199  
     200  /* Register notification upon message arrival to an empty message queue
     201     MQDES.  */
     202  int
     203  __mq_notify (mqd_t mqdes, const struct sigevent *notification)
     204  {
     205    /* Make sure the type is correctly defined.  */
     206    assert (sizeof (union notify_data) == NOTIFY_COOKIE_LEN);
     207  
     208    /* Special treatment needed for SIGEV_THREAD.  */
     209    if (notification == NULL || notification->sigev_notify != SIGEV_THREAD)
     210      return INLINE_SYSCALL (mq_notify, 2, mqdes, notification);
     211  
     212    /* The kernel cannot directly start threads.  This will have to be
     213       done at userlevel.  Since we cannot start threads from signal
     214       handlers we have to create a dedicated thread which waits for
     215       notifications for arriving messages and creates threads in
     216       response.  */
     217  
     218    /* Initialize only once.  */
     219    __pthread_once (&once, init_mq_netlink);
     220  
     221    /* If we cannot create the netlink socket we cannot provide
     222       SIGEV_THREAD support.  */
     223    if (__glibc_unlikely (netlink_socket == -1))
     224      {
     225        __set_errno (ENOSYS);
     226        return -1;
     227      }
     228  
     229    /* Create the cookie.  It will hold almost all the state.  */
     230    union notify_data data;
     231    memset (&data, '\0', sizeof (data));
     232    data.fct = notification->sigev_notify_function;
     233    data.param = notification->sigev_value;
     234  
     235    if (notification->sigev_notify_attributes != NULL)
     236      {
     237        /* The thread attribute has to be allocated separately.  */
     238        data.attr = (pthread_attr_t *) malloc (sizeof (pthread_attr_t));
     239        if (data.attr == NULL)
     240  	return -1;
     241  
     242        int ret = __pthread_attr_copy (data.attr,
     243  				     notification->sigev_notify_attributes);
     244        if (ret != 0)
     245  	{
     246  	  free (data.attr);
     247  	  __set_errno (ret);
     248  	  return -1;
     249  	}
     250      }
     251  
     252    /* Construct the new request.  */
     253    struct sigevent se;
     254    se.sigev_notify = SIGEV_THREAD;
     255    se.sigev_signo = netlink_socket;
     256    se.sigev_value.sival_ptr = &data;
     257  
     258    /* Tell the kernel.  */
     259    int retval = INLINE_SYSCALL (mq_notify, 2, mqdes, &se);
     260  
     261    /* If it failed, free the allocated memory.  */
     262    if (retval != 0 && data.attr != NULL)
     263      {
     264        __pthread_attr_destroy (data.attr);
     265        free (data.attr);
     266      }
     267  
     268    return retval;
     269  }
     270  versioned_symbol (libc, __mq_notify, mq_notify, GLIBC_2_34);
     271  libc_hidden_ver (__mq_notify, mq_notify)
     272  #if OTHER_SHLIB_COMPAT (librt, GLIBC_2_3_4, GLIBC_2_34)
     273  compat_symbol (librt, __mq_notify, mq_notify, GLIBC_2_3_4);
     274  #endif