Back to index

glibc  2.9
sem_open.c
Go to the documentation of this file.
00001 /* Copyright (C) 2002, 2003, 2006, 2007 Free Software Foundation, Inc.
00002    This file is part of the GNU C Library.
00003    Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
00004 
00005    The GNU C Library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Lesser General Public
00007    License as published by the Free Software Foundation; either
00008    version 2.1 of the License, or (at your option) any later version.
00009 
00010    The GNU C Library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Lesser General Public License for more details.
00014 
00015    You should have received a copy of the GNU Lesser General Public
00016    License along with the GNU C Library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
00018    02111-1307 USA.  */
00019 
00020 #include <errno.h>
00021 #include <fcntl.h>
00022 #include <mntent.h>
00023 #include <paths.h>
00024 #include <pthread.h>
00025 #include <search.h>
00026 #include <semaphore.h>
00027 #include <stdarg.h>
00028 #include <stdio.h>
00029 #include <stdlib.h>
00030 #include <string.h>
00031 #include <unistd.h>
00032 #include <sys/mman.h>
00033 #include <sys/stat.h>
00034 #include <sys/statfs.h>
00035 #include <linux_fsinfo.h>
00036 #include "semaphoreP.h"
00037 
00038 
00039 
00040 /* Information about the mount point.  */
00041 struct mountpoint_info mountpoint attribute_hidden;
00042 
00043 /* This is the default mount point.  */
00044 static const char defaultmount[] = "/dev/shm";
00045 /* This is the default directory.  */
00046 static const char defaultdir[] = "/dev/shm/sem.";
00047 
00048 /* Protect the `mountpoint' variable above.  */
00049 pthread_once_t __namedsem_once attribute_hidden = PTHREAD_ONCE_INIT;
00050 
00051 
00052 /* Determine where the shmfs is mounted (if at all).  */
00053 void
00054 attribute_hidden
00055 __where_is_shmfs (void)
00056 {
00057   char buf[512];
00058   struct statfs f;
00059   struct mntent resmem;
00060   struct mntent *mp;
00061   FILE *fp;
00062 
00063   /* The canonical place is /dev/shm.  This is at least what the
00064      documentation tells everybody to do.  */
00065   if (__statfs (defaultmount, &f) == 0 && f.f_type == SHMFS_SUPER_MAGIC)
00066     {
00067       /* It is in the normal place.  */
00068       mountpoint.dir = (char *) defaultdir;
00069       mountpoint.dirlen = sizeof (defaultdir) - 1;
00070 
00071       return;
00072     }
00073 
00074   /* OK, do it the hard way.  Look through the /proc/mounts file and if
00075      this does not exist through /etc/fstab to find the mount point.  */
00076   fp = __setmntent ("/proc/mounts", "r");
00077   if (__builtin_expect (fp == NULL, 0))
00078     {
00079       fp = __setmntent (_PATH_MNTTAB, "r");
00080       if (__builtin_expect (fp == NULL, 0))
00081        /* There is nothing we can do.  Blind guesses are not helpful.  */
00082        return;
00083     }
00084 
00085   /* Now read the entries.  */
00086   while ((mp = __getmntent_r (fp, &resmem, buf, sizeof buf)) != NULL)
00087     /* The original name is "shm" but this got changed in early Linux
00088        2.4.x to "tmpfs".  */
00089     if (strcmp (mp->mnt_type, "tmpfs") == 0
00090        || strcmp (mp->mnt_type, "shm") == 0)
00091       {
00092        /* Found it.  There might be more than one place where the
00093            filesystem is mounted but one is enough for us.  */
00094        size_t namelen;
00095 
00096        /* First make sure this really is the correct entry.  At least
00097           some versions of the kernel give wrong information because
00098           of the implicit mount of the shmfs for SysV IPC.  */
00099        if (__statfs (mp->mnt_dir, &f) != 0 || f.f_type != SHMFS_SUPER_MAGIC)
00100          continue;
00101 
00102        namelen = strlen (mp->mnt_dir);
00103 
00104        if (namelen == 0)
00105          /* Hum, maybe some crippled entry.  Keep on searching.  */
00106          continue;
00107 
00108        mountpoint.dir = (char *) malloc (namelen + 4 + 2);
00109        if (mountpoint.dir != NULL)
00110          {
00111            char *cp = __mempcpy (mountpoint.dir, mp->mnt_dir, namelen);
00112            if (cp[-1] != '/')
00113              *cp++ = '/';
00114            cp = stpcpy (cp, "sem.");
00115            mountpoint.dirlen = cp - mountpoint.dir;
00116          }
00117 
00118        break;
00119       }
00120 
00121   /* Close the stream.  */
00122   __endmntent (fp);
00123 }
00124 
00125 
00126 /* Comparison function for search of existing mapping.  */
00127 int
00128 attribute_hidden
00129 __sem_search (const void *a, const void *b)
00130 {
00131   const struct inuse_sem *as = (const struct inuse_sem *) a;
00132   const struct inuse_sem *bs = (const struct inuse_sem *) b;
00133 
00134   if (as->ino != bs->ino)
00135     /* Cannot return the difference the type is larger than int.  */
00136     return as->ino < bs->ino ? -1 : (as->ino == bs->ino ? 0 : 1);
00137 
00138   if (as->dev != bs->dev)
00139     /* Cannot return the difference the type is larger than int.  */
00140     return as->dev < bs->dev ? -1 : (as->dev == bs->dev ? 0 : 1);
00141 
00142   return strcmp (as->name, bs->name);
00143 }
00144 
00145 
00146 /* The search tree for existing mappings.  */
00147 void *__sem_mappings attribute_hidden;
00148 
00149 /* Lock to protect the search tree.  */
00150 int __sem_mappings_lock attribute_hidden = LLL_LOCK_INITIALIZER;
00151 
00152 
00153 /* Search for existing mapping and if possible add the one provided.  */
00154 static sem_t *
00155 check_add_mapping (const char *name, size_t namelen, int fd, sem_t *existing)
00156 {
00157   sem_t *result = SEM_FAILED;
00158 
00159   /* Get the information about the file.  */
00160   struct stat64 st;
00161   if (__fxstat64 (_STAT_VER, fd, &st) == 0)
00162     {
00163       /* Get the lock.  */
00164       lll_lock (__sem_mappings_lock, LLL_PRIVATE);
00165 
00166       /* Search for an existing mapping given the information we have.  */
00167       struct inuse_sem *fake;
00168       fake = (struct inuse_sem *) alloca (sizeof (*fake) + namelen);
00169       memcpy (fake->name, name, namelen);
00170       fake->dev = st.st_dev;
00171       fake->ino = st.st_ino;
00172 
00173       struct inuse_sem **foundp = tfind (fake, &__sem_mappings, __sem_search);
00174       if (foundp != NULL)
00175        {
00176          /* There is already a mapping.  Use it.  */
00177          result = (*foundp)->sem;
00178          ++(*foundp)->refcnt;
00179        }
00180       else
00181        {
00182          /* We haven't found a mapping.  Install ione.  */
00183          struct inuse_sem *newp;
00184 
00185          newp = (struct inuse_sem *) malloc (sizeof (*newp) + namelen);
00186          if (newp != NULL)
00187            {
00188              /* If the caller hasn't provided any map it now.  */
00189              if (existing == SEM_FAILED)
00190               existing = (sem_t *) mmap (NULL, sizeof (sem_t),
00191                                       PROT_READ | PROT_WRITE, MAP_SHARED,
00192                                       fd, 0);
00193 
00194              newp->dev = st.st_dev;
00195              newp->ino = st.st_ino;
00196              newp->refcnt = 1;
00197              newp->sem = existing;
00198              memcpy (newp->name, name, namelen);
00199 
00200              /* Insert the new value.  */
00201              if (existing != MAP_FAILED
00202                 && tsearch (newp, &__sem_mappings, __sem_search) != NULL)
00203               /* Successful.  */
00204               result = existing;
00205              else
00206               /* Something went wrong while inserting the new
00207                  value.  We fail completely.  */
00208               free (newp);
00209            }
00210        }
00211 
00212       /* Release the lock.  */
00213       lll_unlock (__sem_mappings_lock, LLL_PRIVATE);
00214     }
00215 
00216   if (result != existing && existing != SEM_FAILED && existing != MAP_FAILED)
00217     {
00218       /* Do not disturb errno.  */
00219       INTERNAL_SYSCALL_DECL (err);
00220       INTERNAL_SYSCALL (munmap, err, 2, existing, sizeof (sem_t));
00221     }
00222 
00223   return result;
00224 }
00225 
00226 
00227 sem_t *
00228 sem_open (const char *name, int oflag, ...)
00229 {
00230   char *finalname;
00231   sem_t *result = SEM_FAILED;
00232   int fd;
00233 
00234   /* Determine where the shmfs is mounted.  */
00235   INTUSE(__pthread_once) (&__namedsem_once, __where_is_shmfs);
00236 
00237   /* If we don't know the mount points there is nothing we can do.  Ever.  */
00238   if (mountpoint.dir == NULL)
00239     {
00240       __set_errno (ENOSYS);
00241       return SEM_FAILED;
00242     }
00243 
00244   /* Construct the filename.  */
00245   while (name[0] == '/')
00246     ++name;
00247 
00248   if (name[0] == '\0')
00249     {
00250       /* The name "/" is not supported.  */
00251       __set_errno (EINVAL);
00252       return SEM_FAILED;
00253     }
00254   size_t namelen = strlen (name) + 1;
00255 
00256   /* Create the name of the final file.  */
00257   finalname = (char *) alloca (mountpoint.dirlen + namelen);
00258   __mempcpy (__mempcpy (finalname, mountpoint.dir, mountpoint.dirlen),
00259             name, namelen);
00260 
00261   /* If the semaphore object has to exist simply open it.  */
00262   if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
00263     {
00264     try_again:
00265       fd = __libc_open (finalname,
00266                      (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);
00267 
00268       if (fd == -1)
00269        {
00270          /* If we are supposed to create the file try this next.  */
00271          if ((oflag & O_CREAT) != 0 && errno == ENOENT)
00272            goto try_create;
00273 
00274          /* Return.  errno is already set.  */
00275        }
00276       else
00277        /* Check whether we already have this semaphore mapped and
00278           create one if necessary.  */
00279        result = check_add_mapping (name, namelen, fd, SEM_FAILED);
00280     }
00281   else
00282     {
00283       /* We have to open a temporary file first since it must have the
00284         correct form before we can start using it.  */
00285       char *tmpfname;
00286       mode_t mode;
00287       unsigned int value;
00288       va_list ap;
00289 
00290     try_create:
00291       va_start (ap, oflag);
00292 
00293       mode = va_arg (ap, mode_t);
00294       value = va_arg (ap, unsigned int);
00295 
00296       va_end (ap);
00297 
00298       if (value > SEM_VALUE_MAX)
00299        {
00300          __set_errno (EINVAL);
00301          return SEM_FAILED;
00302        }
00303 
00304       /* Create the initial file content.  */
00305       sem_t initsem;
00306 
00307       struct new_sem *iinitsem = (struct new_sem *) &initsem;
00308       iinitsem->value = value;
00309       iinitsem->private = 0;
00310       iinitsem->nwaiters = 0;
00311 
00312       /* Initialize the remaining bytes as well.  */
00313       memset ((char *) &initsem + sizeof (struct new_sem), '\0',
00314              sizeof (sem_t) - sizeof (struct new_sem));
00315 
00316       tmpfname = (char *) alloca (mountpoint.dirlen + 6 + 1);
00317       char *xxxxxx = __mempcpy (tmpfname, mountpoint.dir, mountpoint.dirlen);
00318 
00319       int retries = 0;
00320 #define NRETRIES 50
00321       while (1)
00322        {
00323          /* Add the suffix for mktemp.  */
00324          strcpy (xxxxxx, "XXXXXX");
00325 
00326          /* We really want to use mktemp here.  We cannot use mkstemp
00327             since the file must be opened with a specific mode.  The
00328             mode cannot later be set since then we cannot apply the
00329             file create mask.  */
00330          if (mktemp (tmpfname) == NULL)
00331            return SEM_FAILED;
00332 
00333          /* Open the file.  Make sure we do not overwrite anything.  */
00334          fd = __libc_open (tmpfname, O_RDWR | O_CREAT | O_EXCL, mode);
00335          if (fd == -1)
00336            {
00337              if (errno == EEXIST)
00338               {
00339                 if (++retries < NRETRIES)
00340                   continue;
00341 
00342                 __set_errno (EAGAIN);
00343               }
00344 
00345              return SEM_FAILED;
00346            }
00347 
00348          /* We got a file.  */
00349          break;
00350        }
00351 
00352       if (TEMP_FAILURE_RETRY (__libc_write (fd, &initsem, sizeof (sem_t)))
00353          == sizeof (sem_t)
00354          /* Map the sem_t structure from the file.  */
00355          && (result = (sem_t *) mmap (NULL, sizeof (sem_t),
00356                                    PROT_READ | PROT_WRITE, MAP_SHARED,
00357                                    fd, 0)) != MAP_FAILED)
00358        {
00359          /* Create the file.  Don't overwrite an existing file.  */
00360          if (link (tmpfname, finalname) != 0)
00361            {
00362              /* Undo the mapping.  */
00363              (void) munmap (result, sizeof (sem_t));
00364 
00365              /* Reinitialize 'result'.  */
00366              result = SEM_FAILED;
00367 
00368              /* This failed.  If O_EXCL is not set and the problem was
00369                that the file exists, try again.  */
00370              if ((oflag & O_EXCL) == 0 && errno == EEXIST)
00371               {
00372                 /* Remove the file.  */
00373                 (void) unlink (tmpfname);
00374 
00375                 /* Close the file.  */
00376                 (void) __libc_close (fd);
00377 
00378                 goto try_again;
00379               }
00380            }
00381          else
00382            /* Insert the mapping into the search tree.  This also
00383               determines whether another thread sneaked by and already
00384               added such a mapping despite the fact that we created it.  */
00385            result = check_add_mapping (name, namelen, fd, result);
00386        }
00387 
00388       /* Now remove the temporary name.  This should never fail.  If
00389         it fails we leak a file name.  Better fix the kernel.  */
00390       (void) unlink (tmpfname);
00391     }
00392 
00393   /* Map the mmap error to the error we need.  */
00394   if (MAP_FAILED != (void *) SEM_FAILED && result == MAP_FAILED)
00395     result = SEM_FAILED;
00396 
00397   /* We don't need the file descriptor anymore.  */
00398   if (fd != -1)
00399     {
00400       /* Do not disturb errno.  */
00401       INTERNAL_SYSCALL_DECL (err);
00402       INTERNAL_SYSCALL (close, err, 1, fd);
00403     }
00404 
00405   return result;
00406 }