1 /* Copyright (C) 2004, 2005, 2008 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contribute by Ulrich Drepper <drepper@redhat.com>, 2004.
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    <http://www.gnu.org/licenses/>.  */
18 
19 #include <assert.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <mqueue.h>
23 #include <pthread.h>
24 #include <signal.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sysdep.h>
28 #include <unistd.h>
29 #include <sys/socket.h>
30 #include <not-cancel.h>
31 #include <bits/kernel-features.h>
32 
33 
34 #ifdef __NR_mq_notify
35 
36 /* Defined in the kernel headers: */
37 #define NOTIFY_COOKIE_LEN	32	/* Length of the cookie used.  */
38 #define NOTIFY_WOKENUP		1	/* Code for notifcation.  */
39 #define NOTIFY_REMOVED		2	/* Code for closed message queue
40 					   of de-notifcation.  */
41 
42 
43 /* Data structure for the queued notification requests.  */
44 union notify_data
45 {
46   struct
47   {
48     void (*fct) (union sigval);	/* The function to run.  */
49     union sigval param;		/* The parameter to pass.  */
50     pthread_attr_t *attr;	/* Attributes to create the thread with.  */
51     /* NB: on 64-bit machines the struct as a size of 24 bytes.  Which means
52        byte 31 can still be used for returning the status.  */
53   };
54   char raw[NOTIFY_COOKIE_LEN];
55 };
56 
57 
58 /* Keep track of the initialization.  */
59 static pthread_once_t once = PTHREAD_ONCE_INIT;
60 
61 
62 /* The netlink socket.  */
63 static int netlink_socket = -1;
64 
65 
66 /* Barrier used to make sure data passed to the new thread is not
67    resused by the parent.  */
68 static pthread_barrier_t notify_barrier;
69 
70 
71 /* Modify the signal mask.  We move this into a separate function so
72    that the stack space needed for sigset_t is not deducted from what
73    the thread can use.  */
74 static int
75 __attribute__ ((noinline))
change_sigmask(int how,sigset_t * oss)76 change_sigmask (int how, sigset_t *oss)
77 {
78   sigset_t ss;
79   sigfillset (&ss);
80   return pthread_sigmask (how, &ss, oss);
81 }
82 
83 
84 /* The function used for the notification.  */
85 static void *
notification_function(void * arg)86 notification_function (void *arg)
87 {
88   /* Copy the function and parameter so that the parent thread can go
89      on with its life.  */
90   volatile union notify_data *data = (volatile union notify_data *) arg;
91   void (*fct) (union sigval) = data->fct;
92   union sigval param = data->param;
93 
94   /* Let the parent go.  */
95   (void) pthread_barrier_wait (&notify_barrier);
96 
97   /* Make the thread detached.  */
98   (void) pthread_detach (pthread_self ());
99 
100   /* The parent thread has all signals blocked.  This is probably a
101      bit surprising for this thread.  So we unblock all of them.  */
102   (void) change_sigmask (SIG_UNBLOCK, NULL);
103 
104   /* Now run the user code.  */
105   fct (param);
106 
107   /* And we are done.  */
108   return NULL;
109 }
110 
111 
112 /* Helper thread.  */
113 static void *
helper_thread(void * arg)114 helper_thread (void *arg)
115 {
116   while (1)
117     {
118       union notify_data data;
119 
120       ssize_t n = recv (netlink_socket, &data, sizeof (data),
121 			MSG_NOSIGNAL | MSG_WAITALL);
122       if (n < NOTIFY_COOKIE_LEN)
123 	continue;
124 
125       if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_WOKENUP)
126 	{
127 	  /* Just create the thread as instructed.  There is no way to
128 	     report a problem with creating a thread.  */
129 	  pthread_t th;
130 	  if (__builtin_expect (pthread_create (&th, data.attr,
131 						notification_function, &data)
132 				== 0, 0))
133 	    /* Since we passed a pointer to DATA to the new thread we have
134 	       to wait until it is done with it.  */
135 	    (void) pthread_barrier_wait (&notify_barrier);
136 	}
137       else if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_REMOVED)
138 	/* The only state we keep is the copy of the thread attributes.  */
139 	free (data.attr);
140     }
141   return NULL;
142 }
143 
144 
145 static void
reset_once(void)146 reset_once (void)
147 {
148   once = PTHREAD_ONCE_INIT;
149 }
150 
151 
152 static void
init_mq_netlink(void)153 init_mq_netlink (void)
154 {
155   /* This code might be called a second time after fork().  The file
156      descriptor is inherited from the parent.  */
157   if (netlink_socket == -1)
158     {
159       /* Just a normal netlink socket, not bound.  */
160 	  netlink_socket = socket (AF_NETLINK, SOCK_RAW, 0);
161       /* No need to do more if we have no socket.  */
162       if (netlink_socket == -1)
163 	return;
164 
165       /* Make sure the descriptor is closed on exec.  */
166       if (fcntl (netlink_socket, F_SETFD, FD_CLOEXEC) != 0)
167 	goto errout;
168     }
169 
170   int err = 1;
171 
172   /* Initialize the barrier.  */
173   if (__builtin_expect (pthread_barrier_init (&notify_barrier, NULL, 2) == 0,
174 			0))
175     {
176       /* Create the helper thread.  */
177       pthread_attr_t attr;
178       (void) pthread_attr_init (&attr);
179       (void) pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
180       /* We do not need much stack space, the bare minimum will be enough.  */
181       (void) pthread_attr_setstacksize (&attr, PTHREAD_STACK_MIN);
182 
183       /* Temporarily block all signals so that the newly created
184 	 thread inherits the mask.  */
185       sigset_t oss;
186       int have_no_oss = change_sigmask (SIG_BLOCK, &oss);
187 
188       pthread_t th;
189       err = pthread_create (&th, &attr, helper_thread, NULL);
190 
191       /* Reset the signal mask.  */
192       if (!have_no_oss)
193 	pthread_sigmask (SIG_SETMASK, &oss, NULL);
194 
195       (void) pthread_attr_destroy (&attr);
196 
197       if (err == 0)
198 	{
199 	  static int added_atfork;
200 
201 	  if (added_atfork == 0
202 	      && pthread_atfork (NULL, NULL, reset_once) != 0)
203 	    {
204 	      /* The child thread will call recv() which is a
205 		 cancellation point.  */
206 	      (void) pthread_cancel (th);
207 	      err = 1;
208 	    }
209 	  else
210 	    added_atfork = 1;
211 	}
212     }
213 
214   if (err != 0)
215     {
216     errout:
217       close_not_cancel_no_status (netlink_socket);
218       netlink_socket = -1;
219     }
220 }
221 
222 
223 /* Register notification upon message arrival to an empty message queue
224    MQDES.  */
225 int
mq_notify(mqd_t mqdes,const struct sigevent * notification)226 mq_notify (mqd_t mqdes, const struct sigevent *notification)
227 {
228   /* Make sure the type is correctly defined.  */
229   assert (sizeof (union notify_data) == NOTIFY_COOKIE_LEN);
230 
231   /* Special treatment needed for SIGEV_THREAD.  */
232   if (notification == NULL || notification->sigev_notify != SIGEV_THREAD)
233     return INLINE_SYSCALL (mq_notify, 2, mqdes, notification);
234 
235   /* The kernel cannot directly start threads.  This will have to be
236      done at userlevel.  Since we cannot start threads from signal
237      handlers we have to create a dedicated thread which waits for
238      notifications for arriving messages and creates threads in
239      response.  */
240 
241   /* Initialize only once.  */
242   pthread_once (&once, init_mq_netlink);
243 
244   /* If we cannot create the netlink socket we cannot provide
245      SIGEV_THREAD support.  */
246   if (__builtin_expect (netlink_socket == -1, 0))
247     {
248       __set_errno (ENOSYS);
249       return -1;
250     }
251 
252   /* Create the cookie.  It will hold almost all the state.  */
253   union notify_data data;
254   memset (&data, '\0', sizeof (data));
255   data.fct = notification->sigev_notify_function;
256   data.param = notification->sigev_value;
257 
258   if (notification->sigev_notify_attributes != NULL)
259     {
260       /* The thread attribute has to be allocated separately.  */
261       data.attr = (pthread_attr_t *) malloc (sizeof (pthread_attr_t));
262       if (data.attr == NULL)
263 	return -1;
264 
265       memcpy (data.attr, notification->sigev_notify_attributes,
266 	      sizeof (pthread_attr_t));
267     }
268 
269   /* Construct the new request.  */
270   struct sigevent se;
271   se.sigev_notify = SIGEV_THREAD;
272   se.sigev_signo = netlink_socket;
273   se.sigev_value.sival_ptr = &data;
274 
275   /* Tell the kernel.  */
276   int retval = INLINE_SYSCALL (mq_notify, 2, mqdes, &se);
277 
278   /* If it failed, free the allocated memory.  */
279   if (__builtin_expect (retval != 0, 0))
280     free (data.attr);
281 
282   return retval;
283 }
284 
285 #endif
286