66bcb13aec4339377d60544caf7b95d1b7f1f2b7
[kopensolaris-gnu/glibc.git] / nptl / sem_open.c
1 /* Copyright (C) 2002, 2003, 2006 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
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 <errno.h>
21 #include <fcntl.h>
22 #include <mntent.h>
23 #include <paths.h>
24 #include <pthread.h>
25 #include <search.h>
26 #include <semaphore.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/mman.h>
33 #include <sys/stat.h>
34 #include <sys/statfs.h>
35 #include <linux_fsinfo.h>
36 #include "semaphoreP.h"
37
38
39
40 /* Information about the mount point.  */
41 struct mountpoint_info mountpoint attribute_hidden;
42
43 /* This is the default mount point.  */
44 static const char defaultmount[] = "/dev/shm";
45 /* This is the default directory.  */
46 static const char defaultdir[] = "/dev/shm/sem.";
47
48 /* Protect the `mountpoint' variable above.  */
49 pthread_once_t __namedsem_once attribute_hidden = PTHREAD_ONCE_INIT;
50
51
52 /* Determine where the shmfs is mounted (if at all).  */
53 void
54 attribute_hidden
55 __where_is_shmfs (void)
56 {
57   char buf[512];
58   struct statfs f;
59   struct mntent resmem;
60   struct mntent *mp;
61   FILE *fp;
62
63   /* The canonical place is /dev/shm.  This is at least what the
64      documentation tells everybody to do.  */
65   if (__statfs (defaultmount, &f) == 0 && f.f_type == SHMFS_SUPER_MAGIC)
66     {
67       /* It is in the normal place.  */
68       mountpoint.dir = (char *) defaultdir;
69       mountpoint.dirlen = sizeof (defaultdir) - 1;
70
71       return;
72     }
73
74   /* OK, do it the hard way.  Look through the /proc/mounts file and if
75      this does not exist through /etc/fstab to find the mount point.  */
76   fp = __setmntent ("/proc/mounts", "r");
77   if (__builtin_expect (fp == NULL, 0))
78     {
79       fp = __setmntent (_PATH_MNTTAB, "r");
80       if (__builtin_expect (fp == NULL, 0))
81         /* There is nothing we can do.  Blind guesses are not helpful.  */
82         return;
83     }
84
85   /* Now read the entries.  */
86   while ((mp = __getmntent_r (fp, &resmem, buf, sizeof buf)) != NULL)
87     /* The original name is "shm" but this got changed in early Linux
88        2.4.x to "tmpfs".  */
89     if (strcmp (mp->mnt_type, "tmpfs") == 0
90         || strcmp (mp->mnt_type, "shm") == 0)
91       {
92         /* Found it.  There might be more than one place where the
93            filesystem is mounted but one is enough for us.  */
94         size_t namelen;
95
96         /* First make sure this really is the correct entry.  At least
97            some versions of the kernel give wrong information because
98            of the implicit mount of the shmfs for SysV IPC.  */
99         if (__statfs (mp->mnt_dir, &f) != 0 || f.f_type != SHMFS_SUPER_MAGIC)
100           continue;
101
102         namelen = strlen (mp->mnt_dir);
103
104         if (namelen == 0)
105           /* Hum, maybe some crippled entry.  Keep on searching.  */
106           continue;
107
108         mountpoint.dir = (char *) malloc (namelen + 4 + 2);
109         if (mountpoint.dir != NULL)
110           {
111             char *cp = __mempcpy (mountpoint.dir, mp->mnt_dir, namelen);
112             if (cp[-1] != '/')
113               *cp++ = '/';
114             cp = stpcpy (cp, "sem.");
115             mountpoint.dirlen = cp - mountpoint.dir;
116           }
117
118         break;
119       }
120
121   /* Close the stream.  */
122   __endmntent (fp);
123 }
124
125
126 /* Comparison function for search of existing mapping.  */
127 int
128 attribute_hidden
129 __sem_search (const void *a, const void *b)
130 {
131   const struct inuse_sem *as = (const struct inuse_sem *) a;
132   const struct inuse_sem *bs = (const struct inuse_sem *) b;
133
134   if (as->ino != bs->ino)
135     /* Cannot return the difference the type is larger than int.  */
136     return as->ino < bs->ino ? -1 : (as->ino == bs->ino ? 0 : 1);
137
138   if (as->dev != bs->dev)
139     /* Cannot return the difference the type is larger than int.  */
140     return as->dev < bs->dev ? -1 : (as->dev == bs->dev ? 0 : 1);
141
142   return strcmp (as->name, bs->name);
143 }
144
145
146 /* The search tree for existing mappings.  */
147 void *__sem_mappings attribute_hidden;
148
149 /* Lock to protect the search tree.  */
150 lll_lock_t __sem_mappings_lock attribute_hidden = LLL_LOCK_INITIALIZER;
151
152
153 /* Search for existing mapping and if possible add the one provided.  */
154 static sem_t *
155 check_add_mapping (const char *name, size_t namelen, int fd, sem_t *existing)
156 {
157   sem_t *result = SEM_FAILED;
158
159   /* Get the information about the file.  */
160   struct stat64 st;
161   if (__fxstat64 (_STAT_VER, fd, &st) == 0)
162     {
163       /* Get the lock.  */
164       lll_lock (__sem_mappings_lock);
165
166       /* Search for an existing mapping given the information we have.  */
167       struct inuse_sem *fake;
168       fake = (struct inuse_sem *) alloca (sizeof (*fake) + namelen);
169       memcpy (fake->name, name, namelen);
170       fake->dev = st.st_dev;
171       fake->ino = st.st_ino;
172
173       struct inuse_sem **foundp = tfind (fake, &__sem_mappings, __sem_search);
174       if (foundp != NULL)
175         {
176           /* There is already a mapping.  Use it.  */
177           result = (*foundp)->sem;
178           ++(*foundp)->refcnt;
179         }
180       else
181         {
182           /* We haven't found a mapping.  Install ione.  */
183           struct inuse_sem *newp;
184
185           newp = (struct inuse_sem *) malloc (sizeof (*newp) + namelen);
186           if (newp != NULL)
187             {
188               /* If the caller hasn't provided any map it now.  */
189               if (existing == SEM_FAILED)
190                 existing = (sem_t *) mmap (NULL, sizeof (sem_t),
191                                            PROT_READ | PROT_WRITE, MAP_SHARED,
192                                            fd, 0);
193
194               newp->dev = st.st_dev;
195               newp->ino = st.st_ino;
196               newp->refcnt = 1;
197               newp->sem = existing;
198               memcpy (newp->name, name, namelen);
199
200               /* Insert the new value.  */
201               if (existing != MAP_FAILED
202                   && tsearch (newp, &__sem_mappings, __sem_search) != NULL)
203                 /* Successful.  */
204                 result = existing;
205               else
206                 /* Something went wrong while inserting the new
207                    value.  We fail completely.  */
208                 free (newp);
209             }
210         }
211
212       /* Release the lock.  */
213       lll_unlock (__sem_mappings_lock);
214     }
215
216   if (result != existing && existing != SEM_FAILED && existing != MAP_FAILED)
217     {
218       /* Do not disturb errno.  */
219       INTERNAL_SYSCALL_DECL (err);
220       INTERNAL_SYSCALL (munmap, err, 2, existing, sizeof (sem_t));
221     }
222
223   return result;
224 }
225
226
227 sem_t *
228 sem_open (const char *name, int oflag, ...)
229 {
230   char *finalname;
231   sem_t *result = SEM_FAILED;
232   int fd;
233
234   /* Determine where the shmfs is mounted.  */
235   INTUSE(__pthread_once) (&__namedsem_once, __where_is_shmfs);
236
237   /* If we don't know the mount points there is nothing we can do.  Ever.  */
238   if (mountpoint.dir == NULL)
239     {
240       __set_errno (ENOSYS);
241       return SEM_FAILED;
242     }
243
244   /* Construct the filename.  */
245   while (name[0] == '/')
246     ++name;
247
248   if (name[0] == '\0')
249     {
250       /* The name "/" is not supported.  */
251       __set_errno (EINVAL);
252       return SEM_FAILED;
253     }
254   size_t namelen = strlen (name) + 1;
255
256   /* Create the name of the final file.  */
257   finalname = (char *) alloca (mountpoint.dirlen + namelen);
258   __mempcpy (__mempcpy (finalname, mountpoint.dir, mountpoint.dirlen),
259              name, namelen);
260
261   /* If the semaphore object has to exist simply open it.  */
262   if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
263     {
264     try_again:
265       fd = __libc_open (finalname,
266                         (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);
267
268       if (fd == -1)
269         {
270           /* If we are supposed to create the file try this next.  */
271           if ((oflag & O_CREAT) != 0 && errno == ENOENT)
272             goto try_create;
273
274           /* Return.  errno is already set.  */
275         }
276       else
277         /* Check whether we already have this semaphore mapped and
278            create one if necessary.  */
279         result = check_add_mapping (name, namelen, fd, SEM_FAILED);
280     }
281   else
282     {
283       /* We have to open a temporary file first since it must have the
284          correct form before we can start using it.  */
285       char *tmpfname;
286       mode_t mode;
287       unsigned int value;
288       va_list ap;
289
290     try_create:
291       va_start (ap, oflag);
292
293       mode = va_arg (ap, mode_t);
294       value = va_arg (ap, unsigned int);
295
296       va_end (ap);
297
298       if (value > SEM_VALUE_MAX)
299         {
300           __set_errno (EINVAL);
301           return SEM_FAILED;
302         }
303
304       /* Create the initial file content.  */
305       sem_t initsem;
306
307       struct sem *iinitsem = (struct sem *) &initsem;
308       iinitsem->count = value;
309
310       /* Initialize the remaining bytes as well.  */
311       memset ((char *) &initsem + sizeof (struct sem), '\0',
312               sizeof (sem_t) - sizeof (struct sem));
313
314       tmpfname = (char *) alloca (mountpoint.dirlen + 6 + 1);
315       char *xxxxxx = __mempcpy (tmpfname, mountpoint.dir, mountpoint.dirlen);
316
317       int retries = 0;
318 #define NRETRIES 50
319       while (1)
320         {
321           /* Add the suffix for mktemp.  */
322           strcpy (xxxxxx, "XXXXXX");
323
324           /* We really want to use mktemp here.  We cannot use mkstemp
325              since the file must be opened with a specific mode.  The
326              mode cannot later be set since then we cannot apply the
327              file create mask.  */
328           if (mktemp (tmpfname) == NULL)
329             return SEM_FAILED;
330
331           /* Open the file.  Make sure we do not overwrite anything.  */
332           fd = __libc_open (tmpfname, O_RDWR | O_CREAT | O_EXCL, mode);
333           if (fd == -1)
334             {
335               if (errno == EEXIST)
336                 {
337                   if (++retries < NRETRIES)
338                     continue;
339
340                   __set_errno (EAGAIN);
341                 }
342
343               return SEM_FAILED;
344             }
345
346           /* We got a file.  */
347           break;
348         }
349
350       if (TEMP_FAILURE_RETRY (__libc_write (fd, &initsem, sizeof (sem_t)))
351           == sizeof (sem_t)
352           /* Map the sem_t structure from the file.  */
353           && (result = (sem_t *) mmap (NULL, sizeof (sem_t),
354                                        PROT_READ | PROT_WRITE, MAP_SHARED,
355                                        fd, 0)) != MAP_FAILED)
356         {
357           /* Create the file.  Don't overwrite an existing file.  */
358           if (link (tmpfname, finalname) != 0)
359             {
360               /* Undo the mapping.  */
361               (void) munmap (result, sizeof (sem_t));
362
363               /* Reinitialize 'result'.  */
364               result = SEM_FAILED;
365
366               /* This failed.  If O_EXCL is not set and the problem was
367                  that the file exists, try again.  */
368               if ((oflag & O_EXCL) == 0 && errno == EEXIST)
369                 {
370                   /* Remove the file.  */
371                   (void) unlink (tmpfname);
372
373                   /* Close the file.  */
374                   (void) __libc_close (fd);
375
376                   goto try_again;
377                 }
378             }
379           else
380             /* Insert the mapping into the search tree.  This also
381                determines whether another thread sneaked by and already
382                added such a mapping despite the fact that we created it.  */
383             result = check_add_mapping (name, namelen, fd, result);
384         }
385
386       /* Now remove the temporary name.  This should never fail.  If
387          it fails we leak a file name.  Better fix the kernel.  */
388       (void) unlink (tmpfname);
389     }
390
391   /* Map the mmap error to the error we need.  */
392   if (MAP_FAILED != (void *) SEM_FAILED && result == MAP_FAILED)
393     result = SEM_FAILED;
394
395   /* We don't need the file descriptor anymore.  */
396   if (fd != -1)
397     {
398       /* Do not disturb errno.  */
399       INTERNAL_SYSCALL_DECL (err);
400       INTERNAL_SYSCALL (close, err, 1, fd);
401     }
402
403   return result;
404 }