1 /* Copyright (C) 2004-2022 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 notifcation.  */
36 #define NOTIFY_REMOVED		2	/* Code for closed message queue
37 					   of de-notifcation.  */
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    resused 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))
change_sigmask(int how,sigset_t * oss)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 *
notification_function(void * arg)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 (&notify_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 *
helper_thread(void * arg)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 (&notify_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
__mq_notify_fork_subprocess(void)145 __mq_notify_fork_subprocess (void)
146 {
147   once = PTHREAD_ONCE_INIT;
148 }
149 
150 
151 static void
init_mq_netlink(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 (&notify_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
__mq_notify(mqd_t mqdes,const struct sigevent * notification)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
275