1 /* Copyright (C) 2002, 2003, 2006, 2007, 2009 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, see
17 <http://www.gnu.org/licenses/>. */
18
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <mntent.h>
22 #include <paths.h>
23 #include <pthread.h>
24 #include <search.h>
25 #include <semaphore.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/mman.h>
32 #include <sys/stat.h>
33 #include <sys/statfs.h>
34 #include <linux_fsinfo.h>
35 #include "semaphoreP.h"
36 #include "../../libc/misc/internals/tempname.h"
37
38
39 /* Compatibility defines. */
40 #define __endmntent endmntent
41 #define __getmntent_r getmntent_r
42 #define __setmntent setmntent
43 #define __statfs statfs
44 #define __libc_close close
45 #define __libc_open open64
46 #define __fxstat64(vers, fd, buf) fstat64(fd, buf)
47 #define __libc_write write
48
49
50 /* Information about the mount point. */
51 struct mountpoint_info mountpoint attribute_hidden;
52
53 /* This is the default mount point. */
54 static const char defaultmount[] = "/dev/shm";
55 /* This is the default directory. */
56 static const char defaultdir[] = "/dev/shm/sem.";
57
58 /* Protect the `mountpoint' variable above. */
59 pthread_once_t __namedsem_once attribute_hidden = PTHREAD_ONCE_INIT;
60
61
62 /* Determine where the shmfs is mounted (if at all). */
63 void
64 attribute_hidden
__where_is_shmfs(void)65 __where_is_shmfs (void)
66 {
67 char buf[512];
68 struct statfs f;
69 struct mntent resmem;
70 struct mntent *mp;
71 FILE *fp;
72
73 /* The canonical place is /dev/shm. This is at least what the
74 documentation tells everybody to do. */
75 if (__statfs (defaultmount, &f) == 0
76 && (f.f_type == SHMFS_SUPER_MAGIC_WITH_MMU
77 || f.f_type == SHMFS_SUPER_MAGIC_WITHOUT_MMU))
78 {
79 /* It is in the normal place. */
80 mountpoint.dir = (char *) defaultdir;
81 mountpoint.dirlen = sizeof (defaultdir) - 1;
82
83 return;
84 }
85
86 /* OK, do it the hard way. Look through the /proc/mounts file and if
87 this does not exist through /etc/fstab to find the mount point. */
88 fp = __setmntent ("/proc/mounts", "r");
89 if (unlikely (fp == NULL))
90 {
91 fp = __setmntent (_PATH_MNTTAB, "r");
92 if (unlikely (fp == NULL))
93 /* There is nothing we can do. Blind guesses are not helpful. */
94 return;
95 }
96
97 /* Now read the entries. */
98 while ((mp = __getmntent_r (fp, &resmem, buf, sizeof buf)) != NULL)
99 /* The original name is "shm" but this got changed in early Linux
100 2.4.x to "tmpfs". */
101 if (strcmp (mp->mnt_type, "tmpfs") == 0
102 || strcmp (mp->mnt_type, "shm") == 0)
103 {
104 /* Found it. There might be more than one place where the
105 filesystem is mounted but one is enough for us. */
106 size_t namelen;
107
108 /* First make sure this really is the correct entry. At least
109 some versions of the kernel give wrong information because
110 of the implicit mount of the shmfs for SysV IPC. */
111 if (__statfs (mp->mnt_dir, &f) != 0
112 || (f.f_type != SHMFS_SUPER_MAGIC_WITH_MMU
113 && f.f_type != SHMFS_SUPER_MAGIC_WITHOUT_MMU))
114 continue;
115
116 namelen = strlen (mp->mnt_dir);
117
118 if (namelen == 0)
119 /* Hum, maybe some crippled entry. Keep on searching. */
120 continue;
121
122 mountpoint.dir = (char *) malloc (namelen + 4 + 2);
123 if (mountpoint.dir != NULL)
124 {
125 char *cp = mempcpy (mountpoint.dir, mp->mnt_dir, namelen);
126 if (cp[-1] != '/')
127 *cp++ = '/';
128 cp = stpcpy (cp, "sem.");
129 mountpoint.dirlen = cp - mountpoint.dir;
130 }
131
132 break;
133 }
134
135 /* Close the stream. */
136 __endmntent (fp);
137 }
138
139
140 /* Comparison function for search of existing mapping. */
141 int
142 attribute_hidden
__sem_search(const void * a,const void * b)143 __sem_search (const void *a, const void *b)
144 {
145 const struct inuse_sem *as = (const struct inuse_sem *) a;
146 const struct inuse_sem *bs = (const struct inuse_sem *) b;
147
148 if (as->ino != bs->ino)
149 /* Cannot return the difference the type is larger than int. */
150 return as->ino < bs->ino ? -1 : 1;
151
152 if (as->dev != bs->dev)
153 /* Cannot return the difference the type is larger than int. */
154 return as->dev < bs->dev ? -1 : 1;
155
156 return strcmp (as->name, bs->name);
157 }
158
159
160 /* The search tree for existing mappings. */
161 void *__sem_mappings attribute_hidden;
162
163 /* Lock to protect the search tree. */
164 int __sem_mappings_lock attribute_hidden = LLL_LOCK_INITIALIZER;
165
166
167 /* Search for existing mapping and if possible add the one provided. */
168 static sem_t *
check_add_mapping(const char * name,size_t namelen,int fd,sem_t * existing)169 check_add_mapping (const char *name, size_t namelen, int fd, sem_t *existing)
170 {
171 sem_t *result = SEM_FAILED;
172
173 /* Get the information about the file. */
174 struct stat64 st;
175 if (__fxstat64 (_STAT_VER, fd, &st) == 0)
176 {
177 /* Get the lock. */
178 lll_lock (__sem_mappings_lock, LLL_PRIVATE);
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, LLL_PRIVATE);
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 *
sem_open(const char * name,int oflag,...)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 INTUSE(__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 union
320 {
321 sem_t initsem;
322 struct new_sem newsem;
323 } sem;
324
325 sem.newsem.value = value;
326 sem.newsem.private = 0;
327 sem.newsem.nwaiters = 0;
328
329 /* Initialize the remaining bytes as well. */
330 memset ((char *) &sem.initsem + sizeof (struct new_sem), '\0',
331 sizeof (sem_t) - sizeof (struct new_sem));
332
333 tmpfname = (char *) alloca (mountpoint.dirlen + 6 + 1);
334 mempcpy (mempcpy (tmpfname, mountpoint.dir, mountpoint.dirlen),
335 "XXXXXX", 7);
336
337 fd = __gen_tempname (tmpfname, __GT_FILE, 0, 0, mode);
338 if (fd == -1)
339 return SEM_FAILED;
340
341 if (TEMP_FAILURE_RETRY (__libc_write (fd, &sem.initsem, sizeof (sem_t)))
342 == sizeof (sem_t)
343 /* Map the sem_t structure from the file. */
344 && (result = (sem_t *) mmap (NULL, sizeof (sem_t),
345 PROT_READ | PROT_WRITE, MAP_SHARED,
346 fd, 0)) != MAP_FAILED)
347 {
348 /* Create the file. Don't overwrite an existing file. */
349 if (link (tmpfname, finalname) != 0)
350 {
351 /* Undo the mapping. */
352 (void) munmap (result, sizeof (sem_t));
353
354 /* Reinitialize 'result'. */
355 result = SEM_FAILED;
356
357 /* This failed. If O_EXCL is not set and the problem was
358 that the file exists, try again. */
359 if ((oflag & O_EXCL) == 0 && errno == EEXIST)
360 {
361 /* Remove the file. */
362 (void) unlink (tmpfname);
363
364 /* Close the file. */
365 (void) __libc_close (fd);
366
367 goto try_again;
368 }
369 }
370 else
371 /* Insert the mapping into the search tree. This also
372 determines whether another thread sneaked by and already
373 added such a mapping despite the fact that we created it. */
374 result = check_add_mapping (name, namelen, fd, result);
375 }
376
377 /* Now remove the temporary name. This should never fail. If
378 it fails we leak a file name. Better fix the kernel. */
379 (void) unlink (tmpfname);
380 }
381
382 /* Map the mmap error to the error we need. */
383 if (MAP_FAILED != (void *) SEM_FAILED && result == MAP_FAILED)
384 result = SEM_FAILED;
385
386 /* We don't need the file descriptor anymore. */
387 if (fd != -1)
388 {
389 /* Do not disturb errno. */
390 INTERNAL_SYSCALL_DECL (err);
391 INTERNAL_SYSCALL (close, err, 1, fd);
392 }
393
394 return result;
395 }
396