Blame sysdeps/unix/sysv/linux/mq_notify.c

Packit 6c4009
/* Copyright (C) 2004-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
   Contribute by Ulrich Drepper <drepper@redhat.com>, 2004.
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 <assert.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <fcntl.h>
Packit 6c4009
#include <mqueue.h>
Packit 6c4009
#include <pthread.h>
Packit 6c4009
#include <signal.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <sysdep.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <sys/socket.h>
Packit 6c4009
#include <not-cancel.h>
Packit 6c4009
#include <nptl/pthreadP.h>
Packit 6c4009
Packit 6c4009
Packit 6c4009
#ifdef __NR_mq_notify
Packit 6c4009
Packit 6c4009
/* Defined in the kernel headers: */
Packit 6c4009
#define NOTIFY_COOKIE_LEN	32	/* Length of the cookie used.  */
Packit 6c4009
#define NOTIFY_WOKENUP		1	/* Code for notifcation.  */
Packit 6c4009
#define NOTIFY_REMOVED		2	/* Code for closed message queue
Packit 6c4009
					   of de-notifcation.  */
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Data structure for the queued notification requests.  */
Packit 6c4009
union notify_data
Packit 6c4009
{
Packit 6c4009
  struct
Packit 6c4009
  {
Packit 6c4009
    void (*fct) (union sigval);	/* The function to run.  */
Packit 6c4009
    union sigval param;		/* The parameter to pass.  */
Packit 6c4009
    pthread_attr_t *attr;	/* Attributes to create the thread with.  */
Packit 6c4009
    /* NB: on 64-bit machines the struct as a size of 24 bytes.  Which means
Packit 6c4009
       byte 31 can still be used for returning the status.  */
Packit 6c4009
  };
Packit 6c4009
  char raw[NOTIFY_COOKIE_LEN];
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Keep track of the initialization.  */
Packit 6c4009
static pthread_once_t once = PTHREAD_ONCE_INIT;
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* The netlink socket.  */
Packit 6c4009
static int netlink_socket = -1;
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Barrier used to make sure data passed to the new thread is not
Packit 6c4009
   resused by the parent.  */
Packit 6c4009
static pthread_barrier_t notify_barrier;
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Modify the signal mask.  We move this into a separate function so
Packit 6c4009
   that the stack space needed for sigset_t is not deducted from what
Packit 6c4009
   the thread can use.  */
Packit 6c4009
static int
Packit 6c4009
__attribute__ ((noinline))
Packit 6c4009
change_sigmask (int how, sigset_t *oss)
Packit 6c4009
{
Packit 6c4009
  sigset_t ss;
Packit 6c4009
  sigfillset (&ss);
Packit 6c4009
  return pthread_sigmask (how, &ss, oss);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* The function used for the notification.  */
Packit 6c4009
static void *
Packit 6c4009
notification_function (void *arg)
Packit 6c4009
{
Packit 6c4009
  /* Copy the function and parameter so that the parent thread can go
Packit 6c4009
     on with its life.  */
Packit 6c4009
  volatile union notify_data *data = (volatile union notify_data *) arg;
Packit 6c4009
  void (*fct) (union sigval) = data->fct;
Packit 6c4009
  union sigval param = data->param;
Packit 6c4009
Packit 6c4009
  /* Let the parent go.  */
Packit 6c4009
  (void) __pthread_barrier_wait (&notify_barrier);
Packit 6c4009
Packit 6c4009
  /* Make the thread detached.  */
Packit 6c4009
  (void) pthread_detach (pthread_self ());
Packit 6c4009
Packit 6c4009
  /* The parent thread has all signals blocked.  This is probably a
Packit 6c4009
     bit surprising for this thread.  So we unblock all of them.  */
Packit 6c4009
  (void) change_sigmask (SIG_UNBLOCK, NULL);
Packit 6c4009
Packit 6c4009
  /* Now run the user code.  */
Packit 6c4009
  fct (param);
Packit 6c4009
Packit 6c4009
  /* And we are done.  */
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Helper thread.  */
Packit 6c4009
static void *
Packit 6c4009
helper_thread (void *arg)
Packit 6c4009
{
Packit 6c4009
  while (1)
Packit 6c4009
    {
Packit 6c4009
      union notify_data data;
Packit 6c4009
Packit 6c4009
      ssize_t n = __recv (netlink_socket, &data, sizeof (data),
Packit 6c4009
			  MSG_NOSIGNAL | MSG_WAITALL);
Packit 6c4009
      if (n < NOTIFY_COOKIE_LEN)
Packit 6c4009
	continue;
Packit 6c4009
Packit 6c4009
      if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_WOKENUP)
Packit 6c4009
	{
Packit 6c4009
	  /* Just create the thread as instructed.  There is no way to
Packit 6c4009
	     report a problem with creating a thread.  */
Packit 6c4009
	  pthread_t th;
Packit 6c4009
	  if (__builtin_expect (pthread_create (&th, data.attr,
Packit 6c4009
						notification_function, &data)
Packit 6c4009
				== 0, 0))
Packit 6c4009
	    /* Since we passed a pointer to DATA to the new thread we have
Packit 6c4009
	       to wait until it is done with it.  */
Packit 6c4009
	    (void) __pthread_barrier_wait (&notify_barrier);
Packit 6c4009
	}
Packit 6c4009
      else if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_REMOVED)
Packit 6c4009
	/* The only state we keep is the copy of the thread attributes.  */
Packit 6c4009
	free (data.attr);
Packit 6c4009
    }
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
reset_once (void)
Packit 6c4009
{
Packit 6c4009
  once = PTHREAD_ONCE_INIT;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
static void
Packit 6c4009
init_mq_netlink (void)
Packit 6c4009
{
Packit 6c4009
  /* This code might be called a second time after fork().  The file
Packit 6c4009
     descriptor is inherited from the parent.  */
Packit 6c4009
  if (netlink_socket == -1)
Packit 6c4009
    {
Packit 6c4009
      /* Just a normal netlink socket, not bound.  */
Packit 6c4009
      netlink_socket = __socket (AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, 0);
Packit 6c4009
      /* No need to do more if we have no socket.  */
Packit 6c4009
      if (netlink_socket == -1)
Packit 6c4009
	return;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  int err = 1;
Packit 6c4009
Packit 6c4009
  /* Initialize the barrier.  */
Packit 6c4009
  if (__builtin_expect (__pthread_barrier_init (&notify_barrier, NULL, 2) == 0,
Packit 6c4009
			0))
Packit 6c4009
    {
Packit 6c4009
      /* Create the helper thread.  */
Packit 6c4009
      pthread_attr_t attr;
Packit 6c4009
      (void) pthread_attr_init (&attr);
Packit 6c4009
      (void) pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
Packit 6c4009
      /* We do not need much stack space, the bare minimum will be enough.  */
Packit 6c4009
      (void) pthread_attr_setstacksize (&attr, __pthread_get_minstack (&attr));
Packit 6c4009
Packit 6c4009
      /* Temporarily block all signals so that the newly created
Packit 6c4009
	 thread inherits the mask.  */
Packit 6c4009
      sigset_t oss;
Packit 6c4009
      int have_no_oss = change_sigmask (SIG_BLOCK, &oss;;
Packit 6c4009
Packit 6c4009
      pthread_t th;
Packit 6c4009
      err = pthread_create (&th, &attr, helper_thread, NULL);
Packit 6c4009
Packit 6c4009
      /* Reset the signal mask.  */
Packit 6c4009
      if (!have_no_oss)
Packit 6c4009
	pthread_sigmask (SIG_SETMASK, &oss, NULL);
Packit 6c4009
Packit 6c4009
      (void) pthread_attr_destroy (&attr);
Packit 6c4009
Packit 6c4009
      if (err == 0)
Packit 6c4009
	{
Packit 6c4009
	  static int added_atfork;
Packit 6c4009
Packit 6c4009
	  if (added_atfork == 0
Packit 6c4009
	      && pthread_atfork (NULL, NULL, reset_once) != 0)
Packit 6c4009
	    {
Packit 6c4009
	      /* The child thread will call recv() which is a
Packit 6c4009
		 cancellation point.  */
Packit 6c4009
	      (void) pthread_cancel (th);
Packit 6c4009
	      err = 1;
Packit 6c4009
	    }
Packit 6c4009
	  else
Packit 6c4009
	    added_atfork = 1;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (err != 0)
Packit 6c4009
    {
Packit 6c4009
      __close_nocancel_nostatus (netlink_socket);
Packit 6c4009
      netlink_socket = -1;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
/* Register notification upon message arrival to an empty message queue
Packit 6c4009
   MQDES.  */
Packit 6c4009
int
Packit 6c4009
mq_notify (mqd_t mqdes, const struct sigevent *notification)
Packit 6c4009
{
Packit 6c4009
  /* Make sure the type is correctly defined.  */
Packit 6c4009
  assert (sizeof (union notify_data) == NOTIFY_COOKIE_LEN);
Packit 6c4009
Packit 6c4009
  /* Special treatment needed for SIGEV_THREAD.  */
Packit 6c4009
  if (notification == NULL || notification->sigev_notify != SIGEV_THREAD)
Packit 6c4009
    return INLINE_SYSCALL (mq_notify, 2, mqdes, notification);
Packit 6c4009
Packit 6c4009
  /* The kernel cannot directly start threads.  This will have to be
Packit 6c4009
     done at userlevel.  Since we cannot start threads from signal
Packit 6c4009
     handlers we have to create a dedicated thread which waits for
Packit 6c4009
     notifications for arriving messages and creates threads in
Packit 6c4009
     response.  */
Packit 6c4009
Packit 6c4009
  /* Initialize only once.  */
Packit 6c4009
  pthread_once (&once, init_mq_netlink);
Packit 6c4009
Packit 6c4009
  /* If we cannot create the netlink socket we cannot provide
Packit 6c4009
     SIGEV_THREAD support.  */
Packit 6c4009
  if (__glibc_unlikely (netlink_socket == -1))
Packit 6c4009
    {
Packit 6c4009
      __set_errno (ENOSYS);
Packit 6c4009
      return -1;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Create the cookie.  It will hold almost all the state.  */
Packit 6c4009
  union notify_data data;
Packit 6c4009
  memset (&data, '\0', sizeof (data));
Packit 6c4009
  data.fct = notification->sigev_notify_function;
Packit 6c4009
  data.param = notification->sigev_value;
Packit 6c4009
Packit 6c4009
  if (notification->sigev_notify_attributes != NULL)
Packit 6c4009
    {
Packit 6c4009
      /* The thread attribute has to be allocated separately.  */
Packit 6c4009
      data.attr = (pthread_attr_t *) malloc (sizeof (pthread_attr_t));
Packit 6c4009
      if (data.attr == NULL)
Packit 6c4009
	return -1;
Packit 6c4009
Packit 6c4009
      memcpy (data.attr, notification->sigev_notify_attributes,
Packit 6c4009
	      sizeof (pthread_attr_t));
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Construct the new request.  */
Packit 6c4009
  struct sigevent se;
Packit 6c4009
  se.sigev_notify = SIGEV_THREAD;
Packit 6c4009
  se.sigev_signo = netlink_socket;
Packit 6c4009
  se.sigev_value.sival_ptr = &dat;;
Packit 6c4009
Packit 6c4009
  /* Tell the kernel.  */
Packit 6c4009
  int retval = INLINE_SYSCALL (mq_notify, 2, mqdes, &se);
Packit 6c4009
Packit 6c4009
  /* If it failed, free the allocated memory.  */
Packit 6c4009
  if (__glibc_unlikely (retval != 0))
Packit 6c4009
    free (data.attr);
Packit 6c4009
Packit 6c4009
  return retval;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#else
Packit 6c4009
# include <rt/mq_notify.c>
Packit 6c4009
#endif