OSDN Git Service

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