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