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