[BZ #5760]
[glibc.git] / nptl / sem_open.c
blobe58dde947225ddd04fa87b9631ee178f02269ac7
1 /* Copyright (C) 2002, 2003, 2006, 2007 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
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.
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.
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. */
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"
40 /* Information about the mount point. */
41 struct mountpoint_info mountpoint attribute_hidden;
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.";
48 /* Protect the `mountpoint' variable above. */
49 pthread_once_t __namedsem_once attribute_hidden = PTHREAD_ONCE_INIT;
52 /* Determine where the shmfs is mounted (if at all). */
53 void
54 attribute_hidden
55 __where_is_shmfs (void)
57 char buf[512];
58 struct statfs f;
59 struct mntent resmem;
60 struct mntent *mp;
61 FILE *fp;
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)
67 /* It is in the normal place. */
68 mountpoint.dir = (char *) defaultdir;
69 mountpoint.dirlen = sizeof (defaultdir) - 1;
71 return;
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))
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;
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)
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;
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;
102 namelen = strlen (mp->mnt_dir);
104 if (namelen == 0)
105 /* Hum, maybe some crippled entry. Keep on searching. */
106 continue;
108 mountpoint.dir = (char *) malloc (namelen + 4 + 2);
109 if (mountpoint.dir != NULL)
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;
118 break;
121 /* Close the stream. */
122 __endmntent (fp);
126 /* Comparison function for search of existing mapping. */
128 attribute_hidden
129 __sem_search (const void *a, const void *b)
131 const struct inuse_sem *as = (const struct inuse_sem *) a;
132 const struct inuse_sem *bs = (const struct inuse_sem *) b;
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);
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);
142 return strcmp (as->name, bs->name);
146 /* The search tree for existing mappings. */
147 void *__sem_mappings attribute_hidden;
149 /* Lock to protect the search tree. */
150 int __sem_mappings_lock attribute_hidden = LLL_LOCK_INITIALIZER;
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)
157 sem_t *result = SEM_FAILED;
159 /* Get the information about the file. */
160 struct stat64 st;
161 if (__fxstat64 (_STAT_VER, fd, &st) == 0)
163 /* Get the lock. */
164 lll_lock (__sem_mappings_lock, LLL_PRIVATE);
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;
173 struct inuse_sem **foundp = tfind (fake, &__sem_mappings, __sem_search);
174 if (foundp != NULL)
176 /* There is already a mapping. Use it. */
177 result = (*foundp)->sem;
178 ++(*foundp)->refcnt;
180 else
182 /* We haven't found a mapping. Install ione. */
183 struct inuse_sem *newp;
185 newp = (struct inuse_sem *) malloc (sizeof (*newp) + namelen);
186 if (newp != NULL)
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);
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);
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);
212 /* Release the lock. */
213 lll_unlock (__sem_mappings_lock, LLL_PRIVATE);
216 if (result != existing && existing != SEM_FAILED && existing != MAP_FAILED)
218 /* Do not disturb errno. */
219 INTERNAL_SYSCALL_DECL (err);
220 INTERNAL_SYSCALL (munmap, err, 2, existing, sizeof (sem_t));
223 return result;
227 sem_t *
228 sem_open (const char *name, int oflag, ...)
230 char *finalname;
231 sem_t *result = SEM_FAILED;
232 int fd;
234 /* Determine where the shmfs is mounted. */
235 INTUSE(__pthread_once) (&__namedsem_once, __where_is_shmfs);
237 /* If we don't know the mount points there is nothing we can do. Ever. */
238 if (mountpoint.dir == NULL)
240 __set_errno (ENOSYS);
241 return SEM_FAILED;
244 /* Construct the filename. */
245 while (name[0] == '/')
246 ++name;
248 if (name[0] == '\0')
250 /* The name "/" is not supported. */
251 __set_errno (EINVAL);
252 return SEM_FAILED;
254 size_t namelen = strlen (name) + 1;
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);
261 /* If the semaphore object has to exist simply open it. */
262 if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
264 try_again:
265 fd = __libc_open (finalname,
266 (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);
268 if (fd == -1)
270 /* If we are supposed to create the file try this next. */
271 if ((oflag & O_CREAT) != 0 && errno == ENOENT)
272 goto try_create;
274 /* Return. errno is already set. */
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);
281 else
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;
290 try_create:
291 va_start (ap, oflag);
293 mode = va_arg (ap, mode_t);
294 value = va_arg (ap, unsigned int);
296 va_end (ap);
298 if (value > SEM_VALUE_MAX)
300 __set_errno (EINVAL);
301 return SEM_FAILED;
304 /* Create the initial file content. */
305 sem_t initsem;
307 struct new_sem *iinitsem = (struct new_sem *) &initsem;
308 iinitsem->value = value;
309 iinitsem->private = 0;
310 iinitsem->nwaiters = 0;
312 /* Initialize the remaining bytes as well. */
313 memset ((char *) &initsem + sizeof (struct new_sem), '\0',
314 sizeof (sem_t) - sizeof (struct new_sem));
316 tmpfname = (char *) alloca (mountpoint.dirlen + 6 + 1);
317 char *xxxxxx = __mempcpy (tmpfname, mountpoint.dir, mountpoint.dirlen);
319 int retries = 0;
320 #define NRETRIES 50
321 while (1)
323 /* Add the suffix for mktemp. */
324 strcpy (xxxxxx, "XXXXXX");
326 /* We really want to use mktemp here. We cannot use mkstemp
327 since the file must be opened with a specific mode. The
328 mode cannot later be set since then we cannot apply the
329 file create mask. */
330 if (mktemp (tmpfname) == NULL)
331 return SEM_FAILED;
333 /* Open the file. Make sure we do not overwrite anything. */
334 fd = __libc_open (tmpfname, O_RDWR | O_CREAT | O_EXCL, mode);
335 if (fd == -1)
337 if (errno == EEXIST)
339 if (++retries < NRETRIES)
340 continue;
342 __set_errno (EAGAIN);
345 return SEM_FAILED;
348 /* We got a file. */
349 break;
352 if (TEMP_FAILURE_RETRY (__libc_write (fd, &initsem, sizeof (sem_t)))
353 == sizeof (sem_t)
354 /* Map the sem_t structure from the file. */
355 && (result = (sem_t *) mmap (NULL, sizeof (sem_t),
356 PROT_READ | PROT_WRITE, MAP_SHARED,
357 fd, 0)) != MAP_FAILED)
359 /* Create the file. Don't overwrite an existing file. */
360 if (link (tmpfname, finalname) != 0)
362 /* Undo the mapping. */
363 (void) munmap (result, sizeof (sem_t));
365 /* Reinitialize 'result'. */
366 result = SEM_FAILED;
368 /* This failed. If O_EXCL is not set and the problem was
369 that the file exists, try again. */
370 if ((oflag & O_EXCL) == 0 && errno == EEXIST)
372 /* Remove the file. */
373 (void) unlink (tmpfname);
375 /* Close the file. */
376 (void) __libc_close (fd);
378 goto try_again;
381 else
382 /* Insert the mapping into the search tree. This also
383 determines whether another thread sneaked by and already
384 added such a mapping despite the fact that we created it. */
385 result = check_add_mapping (name, namelen, fd, result);
388 /* Now remove the temporary name. This should never fail. If
389 it fails we leak a file name. Better fix the kernel. */
390 (void) unlink (tmpfname);
393 /* Map the mmap error to the error we need. */
394 if (MAP_FAILED != (void *) SEM_FAILED && result == MAP_FAILED)
395 result = SEM_FAILED;
397 /* We don't need the file descriptor anymore. */
398 if (fd != -1)
400 /* Do not disturb errno. */
401 INTERNAL_SYSCALL_DECL (err);
402 INTERNAL_SYSCALL (close, err, 1, fd);
405 return result;