Adjust for sem_open change.
[kopensolaris-gnu/glibc.git] / nptl / sem_open.c
1 /* Copyright (C) 2002, 2003 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 = 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 if (existing != SEM_FAILED)
181         {
182           /* We haven't found a mapping but the caller has a mapping.
183              Install it.  */
184           struct inuse_sem *newp;
185
186           newp = (struct inuse_sem *) malloc (sizeof (*newp) + namelen);
187           if (newp != NULL)
188             {
189               newp->dev = st.st_dev;
190               newp->ino = st.st_ino;
191               newp->refcnt = 1;
192               newp->sem = existing;
193               memcpy (newp->name, name, namelen);
194
195               /* Insert the new value.  */
196               if (tsearch (newp, &__sem_mappings, __sem_search) != NULL)
197                 /* Successful.  */
198                 result = existing;
199               else
200                 /* Something went wrong while inserting the new
201                    value.  We fail completely.  */
202                 free (newp);
203             }
204         }
205
206       /* Release the lock.  */
207       lll_unlock (__sem_mappings_lock);
208     }
209
210   if (result != existing && existing != SEM_FAILED)
211     {
212       /* Do not disturb errno.  */
213       INTERNAL_SYSCALL_DECL (err);
214       INTERNAL_SYSCALL (munmap, err, 2, existing, sizeof (sem_t));
215     }
216
217   return result;
218 }
219
220
221 sem_t *
222 sem_open (const char *name, int oflag, ...)
223 {
224   char *finalname;
225   sem_t *result = SEM_FAILED;
226   int fd;
227
228   /* Determine where the shmfs is mounted.  */
229   INTUSE(__pthread_once) (&__namedsem_once, __where_is_shmfs);
230
231   /* If we don't know the mount points there is nothing we can do.  Ever.  */
232   if (mountpoint.dir == NULL)
233     {
234       __set_errno (ENOSYS);
235       return SEM_FAILED;
236     }
237
238   /* Construct the filename.  */
239   while (name[0] == '/')
240     ++name;
241
242   if (name[0] == '\0')
243     {
244       /* The name "/" is not supported.  */
245       __set_errno (EINVAL);
246       return SEM_FAILED;
247     }
248   size_t namelen = strlen (name) + 1;
249
250   /* Create the name of the final file.  */
251   finalname = (char *) alloca (mountpoint.dirlen + namelen);
252   __mempcpy (__mempcpy (finalname, mountpoint.dir, mountpoint.dirlen),
253              name, namelen);
254
255   /* If the semaphore object has to exist simply open it.  */
256   if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
257     {
258     try_again:
259       fd = __libc_open (finalname,
260                         (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);
261
262       if (fd == -1)
263         {
264           /* If we are supposed to create the file try this next.  */
265           if ((oflag & O_CREAT) != 0 && errno == ENOENT)
266             goto try_create;
267
268           /* Return.  errno is already set.  */
269         }
270       else
271         {
272           /* Check whether we already have this semaphore mapped.  */
273           result = check_add_mapping (name, namelen, fd, SEM_FAILED);
274
275           /* Map the sem_t structure from the file.  */
276           if (result == SEM_FAILED)
277             result = (sem_t *) mmap (NULL, sizeof (sem_t),
278                                      PROT_READ | PROT_WRITE, MAP_SHARED,
279                                      fd, 0);
280         }
281     }
282   else
283     {
284       /* We have to open a temporary file first since it must have the
285          correct form before we can start using it.  */
286       char *tmpfname;
287       mode_t mode;
288       unsigned int value;
289       va_list ap;
290
291     try_create:
292       va_start (ap, oflag);
293
294       mode = va_arg (ap, mode_t);
295       value = va_arg (ap, unsigned int);
296
297       va_end (ap);
298
299       if (value > SEM_VALUE_MAX)
300         {
301           __set_errno (EINVAL);
302           return SEM_FAILED;
303         }
304
305       /* Create the initial file content.  */
306       sem_t initsem;
307
308       struct sem *iinitsem = (struct sem *) &initsem;
309       iinitsem->count = value;
310
311       /* Initialize the remaining bytes as well.  */
312       memset ((char *) &initsem + sizeof (struct sem), '\0',
313               sizeof (sem_t) - sizeof (struct sem));
314
315       tmpfname = (char *) alloca (mountpoint.dirlen + 6 + 1);
316       char *xxxxxx = __mempcpy (tmpfname, mountpoint.dir, mountpoint.dirlen);
317
318       int retries = 0;
319 #define NRETRIES 50
320       while (1)
321         {
322           /* Add the suffix for mktemp.  */
323           strcpy (xxxxxx, "XXXXXX");
324
325           /* We really want to use mktemp here.  We cannot use mkstemp
326              since the file must be opened with a specific mode.  The
327              mode cannot later be set since then we cannot apply the
328              file create mask.  */
329           if (mktemp (tmpfname) == NULL)
330             return SEM_FAILED;
331
332           /* Open the file.  Make sure we do not overwrite anything.  */
333           fd = __libc_open (tmpfname, O_RDWR | O_CREAT | O_EXCL, mode);
334           if (fd == -1)
335             {
336               if (errno == EEXIST)
337                 {
338                   if (++retries < NRETRIES)
339                     continue;
340
341                   __set_errno (EAGAIN);
342                 }
343
344               return SEM_FAILED;
345             }
346
347           /* We got a file.  */
348           break;
349         }
350
351       if (TEMP_FAILURE_RETRY (__libc_write (fd, &initsem, sizeof (sem_t)))
352           == sizeof (sem_t)
353           /* Map the sem_t structure from the file.  */
354           && (result = (sem_t *) mmap (NULL, sizeof (sem_t),
355                                        PROT_READ | PROT_WRITE, MAP_SHARED,
356                                        fd, 0)) != MAP_FAILED)
357         {
358           /* Create the file.  Don't overwrite an existing file.  */
359           if (link (tmpfname, finalname) != 0)
360             {
361               /* Undo the mapping.  */
362               (void) munmap (result, sizeof (sem_t));
363
364               /* Reinitialize 'result'.  */
365               result = SEM_FAILED;
366
367               /* This failed.  If O_EXCL is not set and the problem was
368                  that the file exists, try again.  */
369               if ((oflag & O_EXCL) == 0 && errno == EEXIST)
370                 {
371                   /* Remove the file.  */
372                   (void) unlink (tmpfname);
373
374                   /* Close the file.  */
375                   (void) __libc_close (fd);
376
377                   goto try_again;
378                 }
379             }
380           else
381             /* Insert the mapping into the search tree.  This also
382                determines whether another thread sneaked by and already
383                added such a mapping despite the fact that we created it.  */
384             result = check_add_mapping (name, namelen, fd, result);
385         }
386
387       /* Now remove the temporary name.  This should never fail.  If
388          it fails we leak a file name.  Better fix the kernel.  */
389       (void) unlink (tmpfname);
390     }
391
392   /* Map the mmap error to the error we need.  */
393   if (MAP_FAILED != (void *) SEM_FAILED && result == MAP_FAILED)
394     result = SEM_FAILED;
395
396   /* We don't need the file descriptor anymore.  */
397   if (fd != -1)
398     {
399       /* Do not disturb errno.  */
400       INTERNAL_SYSCALL_DECL (err);
401       INTERNAL_SYSCALL (close, err, 1, fd);
402     }
403
404   return result;
405 }