OSDN Git Service

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