libthread_xu: add support for named POSIX semaphores.
authorJoris Giovannangeli <joris@giovannangeli.fr>
Wed, 4 Jun 2014 13:48:38 +0000 (15:48 +0200)
committerJoris Giovannangeli <joris@giovannangeli.fr>
Wed, 4 Jun 2014 15:23:12 +0000 (17:23 +0200)
 * this implementation uses files as rendezvous and simply map files
   into the process address space. It means one page per semaphore.

 * by default, semaphores are created in /tmp/sem. If the
   LIBTHREAD_SEM_PREFIX environment variable is defined, its value is
   used as prefix.

etc/mtree/BSD.var.dist
lib/libpthread/sem_open.3
lib/libthread_xu/thread/thr_sem.c
share/man/man7/hier.7
sys/sys/unistd.h

index 441a58f..1475133 100644 (file)
@@ -54,6 +54,8 @@
         ..
         wpa_supplicant
         ..
+        sem             mode=01777
+        ..
     ..
     rwho            gname=daemon mode=0775
     ..
index f329179..dd6d58c 100644 (file)
@@ -25,9 +25,9 @@
 .\" OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 .\" EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $FreeBSD: src/lib/libc_r/man/sem_open.3,v 1.1.2.6 2001/12/17 10:08:26 ru Exp $
-.\" $DragonFly: src/lib/libc_r/man/sem_open.3,v 1.2 2003/06/17 04:26:48 dillon Exp $
-.Dd April 15, 2009
+.\" $FreeBSD: head/lib/libc/gen/sem_open.3 202133 2010-01-12 01:30:05Z davidxu $ 
+.\"
+.Dd June 4, 2014
 .Dt SEM_OPEN 3
 .Os
 .Sh NAME
 .Lb libpthread
 .Sh SYNOPSIS
 .In semaphore.h
-.Ft sem_t *
-.Fn sem_open "const char *name" "int oflag" "..."
+.Ft "sem_t *"
+.Fn sem_open "const char *name" "int oflag" ...
 .Ft int
 .Fn sem_close "sem_t *sem"
 .Ft int
 .Fn sem_unlink "const char *name"
 .Sh DESCRIPTION
 The
-.Fn sem_open ,
-.Fn sem_close ,
+.Fn sem_open
+function creates or opens the named semaphore specified by
+.Fa name .
+The returned semaphore may be used in subsequent calls to
+.Xr sem_getvalue 3 ,
+.Xr sem_wait 3 ,
+.Xr sem_trywait 3 ,
+.Xr sem_post 3 ,
 and
-.Fn sem_unlink
-functions are not supported by this implementation.
-.Sh RETURN VALUES
+.Fn sem_close .
+.Pp
+The following bits may be set in the
+.Fa oflag
+argument:
+.Bl -tag -width ".Dv O_CREAT"
+.It Dv O_CREAT
+Create the semaphore if it does not already exist.
+.Pp
+The third argument to the call to
 .Fn sem_open
-returns SEM_FAILED and sets
-.Va errno
-to indicate an error.
-.Fn sem_close
+must be of type
+.Vt mode_t
+and specifies the mode for the semaphore.
+Only the
+.Dv S_IWUSR , S_IWGRP ,
 and
+.Dv S_IWOTH
+bits are examined.
+The mode is modified according to the process's file creation
+mask; see
+.Xr umask 2 .
+.Pp
+The fourth argument must be an
+.Vt "unsigned int"
+and specifies the initial value for the semaphore,
+and must be no greater than
+.Dv SEM_VALUE_MAX .
+.It Dv O_EXCL
+Create the semaphore if it does not exist.
+If the semaphore already exists,
+.Fn sem_open
+will fail.
+This flag is ignored unless
+.Dv O_CREAT
+is also specified.
+.El
+.Pp
+The
+.Fn sem_close
+function closes a named semaphore that was opened by a call to
+.Fn sem_open .
+.Pp
+The
 .Fn sem_unlink
-return -1 and set
+function removes the semaphore named
+.Fa name .
+Resources allocated to the semaphore are only deallocated when all
+processes that have the semaphore open close it.
+.Sh IMPLEMENTATION NOTES
+The current implementation uses shared memory mappings of files.
+The semaphore files are created at the path pointed to by
+.Fa name .
+If
+.Fa name
+is an absolute path,
+.Pa /var/run/sem
+is prepended to
+.Fa name .
+The environnment variable
+.Ev LIBTHREAD_SEM_PREFIX
+can be set to change this value.
+.Pp
+It is not possible to grant only
+.Dq read
+permission on a semaphore.
+.Sh RETURN VALUES
+If successful,
+the
+.Fn sem_open
+function returns the address of the opened semaphore.
+If the same
+.Fa name
+argument is given to multiple calls to
+.Fn sem_open
+by the same process without an intervening call to
+.Fn sem_close ,
+the same address is returned each time.
+If the semaphore cannot be opened,
+.Fn sem_open
+returns
+.Dv SEM_FAILED
+and the global variable
 .Va errno
-to indicate an error.
+is set to indicate the error.
+.Rv -std sem_close sem_unlink
 .Sh ERRORS
-.Fn sem_open ,
-.Fn sem_close ,
+The
+.Fn sem_open
+function will fail if:
+.Bl -tag -width Er
+.It Bq Er EACCES
+The semaphore exists and the permissions specified by
+.Fa oflag
+at the time it was created deny access to this process.
+.It Bq Er EACCES
+The semaphore does not exist and permission to create it is denied.
+.It Bq Er EEXIST
+.Dv O_CREAT
 and
+.Dv O_EXCL
+are set but the semaphore already exists.
+.It Bq Er EINTR
+The call was interrupted by a signal.
+.It Bq Er EINVAL
+The
+.Fn sem_open
+operation is not supported for the given
+.Fa name .
+.It Bq Er EINVAL
+The
+.Fa value
+argument is greater than
+.Dv SEM_VALUE_MAX .
+.It Bq Er EMFILE
+Too many filedescriptors are in use by this process.
+.It Bq Er ENAMETOOLONG
+The
+.Fa name
+argument is too long or a pathname component is too long.
+.It Bq Er ENFILE
+The system limit on semaphores or open files has been reached.
+.It Bq Er ENOENT
+.Dv O_CREAT
+is not set but the named semaphore does not exist.
+.It Bq Er ENOSPC
+There is not enough space to create the semaphore.
+.It Bq Er ENOMEM
+There is insufficient memory for the creation of the new named semaphore.
+.El
+.Pp
+The
+.Fn sem_close
+function will fail if:
+.Bl -tag -width Er
+.It Bq Er EINVAL
+The
+.Fa sem
+argument is not a valid semaphore.
+.El
+.Pp
+The
 .Fn sem_unlink
-will fail:
+function will fail if:
 .Bl -tag -width Er
-.It Bq Er ENOSYS
-Function not supported by this implementation.
+.It Bq Er EACCES
+Permission is denied to unlink the semaphore.
+.It Bq Er ENAMETOOLONG
+The specified
+.Fa name
+is too long or a pathname component is too long.
+.It Bq Er ENOENT
+The named semaphore does not exist.
 .El
+.Sh SEE ALSO
+.Xr close 2 ,
+.Xr open 2 ,
+.Xr umask 2 ,
+.Xr unlink 2 ,
+.Xr sem_getvalue 3 ,
+.Xr sem_post 3 ,
+.Xr sem_trywait 3 ,
+.Xr sem_wait 3 ,
 .Sh STANDARDS
+The
 .Fn sem_open ,
 .Fn sem_close ,
 and
 .Fn sem_unlink
-conform to
+functions conform to
 .St -p1003.1-96 .
+.Sh HISTORY
+Support for named semaphores first appeared in
+.Dx 3.9 .
index df64e07..b7adee4 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 2005 David Xu <davidxu@freebsd.org>.
  * Copyright (C) 2000 Jason Evans <jasone@freebsd.org>.
  * All rights reserved.
- * 
+ *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
@@ -14,7 +14,7 @@
  *    notice(s), this list of conditions and the following disclaimer in
  *    the documentation and/or other materials provided with the
  *    distribution.
- * 
+ *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 #include <machine/tls.h>
 #include <sys/semaphore.h>
 #include <sys/mman.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <pthread.h>
+#include <stdarg.h>
 #include <stdlib.h>
+#include <string.h>
 #include <time.h>
+#include <unistd.h>
 #include "un-namespace.h"
 #include "thr_private.h"
 
+#define container_of(ptr, type, member)                                \
+({                                                             \
+       __typeof(((type *)0)->member) *_p = (ptr);              \
+       (type *)((char *)_p - offsetof(type, member));          \
+})
+
 /*
  * Semaphore definitions.
  */
@@ -48,13 +61,83 @@ struct sem {
        u_int32_t               magic;
        volatile umtx_t         count;
        int                     semid;
-       int                     unused; /* pad */
+       int                     unused; /* pad */
 };
 
 #define        SEM_MAGIC       ((u_int32_t) 0x09fa4012)
 
+static char const *sem_prefix = "/var/run/sem";
+
+
+/*
+ * POSIX requires that two successive calls to sem_open return
+ * the same address if no call to unlink nor close have been
+ * done in the middle. For that, we keep a list of open semaphore
+ * and search for an existing one before remapping a semaphore.
+ * We have to keep the fd open to check for races.
+ *
+ * Example :
+ * sem_open("/test", O_CREAT | O_EXCL...) -> fork() ->
+ * parent :
+ *   sem_unlink("/test") -> sem_open("/test", O_CREAT | O_EXCl ...)
+ * child :
+ *   sem_open("/test", 0).
+ * We need to check that the cached mapping is the one of the most up
+ * to date file linked at this name, or child process will reopen the
+ * *old* version of the semaphore, which is wrong.
+ *
+ * fstat and nlink check is used to test for this race.
+ */
+
+struct sem_info {
+       int open_count;
+       ino_t inode;
+       dev_t dev;
+       int fd;
+       sem_t sem;
+       LIST_ENTRY(sem_info) next;
+};
+
+
+
+static pthread_once_t once = PTHREAD_ONCE_INIT;
+static pthread_mutex_t sem_lock;
+static LIST_HEAD(,sem_info) sem_list = LIST_HEAD_INITIALIZER(sem_list);
+
+
 #define SEMID_LWP      0
 #define SEMID_FORK     1
+#define SEMID_NAMED    2
+
+static void
+sem_prefork(void)
+{
+       _pthread_mutex_lock(&sem_lock);
+}
+
+static void
+sem_postfork(void)
+{
+       _pthread_mutex_unlock(&sem_lock);
+}
+
+static void
+sem_child_postfork(void)
+{
+       _pthread_mutex_unlock(&sem_lock);
+}
+
+static void
+sem_module_init(void)
+{
+       pthread_mutexattr_t ma;
+
+       _pthread_mutexattr_init(&ma);
+       _pthread_mutexattr_settype(&ma,  PTHREAD_MUTEX_RECURSIVE);
+       _pthread_mutex_init(&sem_lock, &ma);
+       _pthread_mutexattr_destroy(&ma);
+       _pthread_atfork(sem_prefork, sem_postfork, sem_child_postfork);
+}
 
 static inline int
 sem_check_validity(sem_t *sem)
@@ -110,8 +193,13 @@ sem_alloc(unsigned int value, int pshared)
 int
 _sem_init(sem_t *sem, int pshared, unsigned int value)
 {
-       (*sem) = sem_alloc(value, pshared);
-       if ((*sem) == NULL)
+       if (sem == NULL) {
+               errno = EINVAL;
+               return (-1);
+       }
+
+       *sem = sem_alloc(value, pshared);
+       if (*sem == NULL)
                return (-1);
        return (0);
 }
@@ -119,18 +207,23 @@ _sem_init(sem_t *sem, int pshared, unsigned int value)
 int
 _sem_destroy(sem_t *sem)
 {
-       if (sem_check_validity(sem) != 0)
+       if (sem_check_validity(sem) != 0) {
+               errno = EINVAL;
                return (-1);
+       }
 
        (*sem)->magic = 0;
 
        switch ((*sem)->semid) {
-       case SEMID_LWP:
-               free(*sem);
-               break;
-       case SEMID_FORK:
-               /* memory is left intact */
-               break;
+               case SEMID_LWP:
+                       free(*sem);
+                       break;
+               case SEMID_FORK:
+                       /* memory is left intact */
+                       break;
+               default:
+                       errno = EINVAL;
+                       return (-1);
        }
        return (0);
 }
@@ -208,7 +301,7 @@ _sem_timedwait(sem_t * __restrict sem, const struct timespec * __restrict abstim
                                return (0);
                }
                if (abstime == NULL ||
-                   abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0) {
+                               abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0) {
                        errno = EINVAL;
                        return (-1);
                }
@@ -216,7 +309,7 @@ _sem_timedwait(sem_t * __restrict sem, const struct timespec * __restrict abstim
                TIMESPEC_SUB(&ts2, abstime, &ts);
                oldcancel = _thr_cancel_enter(curthread);
                retval = _thr_umtx_wait(&(*sem)->count, 0, &ts2,
-                                       CLOCK_REALTIME);
+                               CLOCK_REALTIME);
                _thr_cancel_leave(curthread, oldcancel);
        } while (retval == 0);
        errno = retval;
@@ -227,7 +320,7 @@ int
 _sem_post(sem_t *sem)
 {
        int val;
-       
+
        if (sem_check_validity(sem) != 0)
                return (-1);
 
@@ -242,25 +335,349 @@ _sem_post(sem_t *sem)
        return (0);
 }
 
+static int
+get_path(const char *name, char *path, size_t len, char const **prefix)
+{
+       size_t path_len;
+
+       *prefix = NULL;
+       
+       if (name[0] == '/') {
+               *prefix = getenv("LIBTHREAD_SEM_PREFIX");
+
+               if (*prefix == NULL)
+                       *prefix = sem_prefix;
+
+               path_len = strlcpy(path, *prefix, len);
+
+               if (path_len > len) {
+                       return (ENAMETOOLONG);
+               }
+       }
+
+       path_len = strlcat(path, name, len);
+
+       if (path_len > len)
+               return (ENAMETOOLONG);
+
+       return (0);
+}
+
+
+static sem_t *
+sem_get_mapping(ino_t inode, dev_t dev)
+{
+       struct sem_info *ni;
+       struct stat sbuf;
+
+       LIST_FOREACH(ni, &sem_list, next) {
+               if (ni->inode == inode && ni->dev == dev) {
+                       /* Check for races */
+                       if(_fstat(ni->fd, &sbuf) == 0) {
+                               if (sbuf.st_nlink > 0) {
+                                       ni->open_count++;
+                                       return (&ni->sem);
+                               } else {
+                                       ni->inode = 0;
+                                       LIST_REMOVE(ni, next);
+                               }
+                       }
+                       return (SEM_FAILED);
+
+               }
+       }
+
+       return (SEM_FAILED);
+}
+
+
+static sem_t *
+sem_add_mapping(ino_t inode, dev_t dev, sem_t sem, int fd)
+{
+       struct sem_info *ni;
+
+       ni = malloc(sizeof(struct sem_info));
+       if (ni == NULL) {
+               errno = ENOSPC;
+               return (SEM_FAILED);
+       }
+
+       bzero(ni, sizeof(*ni));
+       ni->open_count = 1;
+       ni->sem = sem;
+       ni->fd = fd;
+       ni->inode = inode;
+       ni->dev = dev;
+
+       LIST_INSERT_HEAD(&sem_list, ni, next);
+
+       return (&ni->sem);
+}
+
+static int
+sem_close_mapping(sem_t *sem)
+{
+       struct sem_info *ni;
+
+       if ((*sem)->semid != SEMID_NAMED)
+               return (EINVAL);
+
+       ni = container_of(sem, struct sem_info, sem);
+
+       if ( --ni->open_count > 0) {
+               return (0);
+       } else {
+               if (ni->inode != 0) {
+                       LIST_REMOVE(ni, next);
+               }       
+               munmap(ni->sem, getpagesize());
+               __sys_close(ni->fd);
+               free(ni);
+               return (0);
+       }
+}
+
 sem_t *
-_sem_open(__unused const char *name, __unused int oflag, ...)
+_sem_open(const char *name, int oflag, ...)
 {
-       errno = ENOSYS;
+       char path[PATH_MAX];
+       char tmppath[PATH_MAX];
+       char const *prefix = NULL;
+       char *tmpname = NULL;
+       size_t path_len;
+       int error, fd, create;
+       sem_t *sem;
+       sem_t semtmp;
+       va_list ap;
+       mode_t mode;
+       struct stat sbuf;
+       unsigned int value = 0;
+       unsigned int retry_count;
+       
+       create = 0;
+       error = 0;
+       fd = -1;
+       sem = SEM_FAILED;
+
+       oflag = oflag & (O_CREAT | O_EXCL);
+       oflag |= O_RDWR;
+       oflag |= O_CLOEXEC;
+
+
+       if (name == NULL) {
+               errno = EINVAL;
+               return (SEM_FAILED);
+       }
+
+       _pthread_once(&once, sem_module_init);
+
+       _pthread_mutex_lock(&sem_lock);
+
+       error = get_path(name, path, PATH_MAX, &prefix);
+       if (error) {
+               errno = error;
+               goto error;
+       }
+
+retry:
+       tmpname = NULL;
+       retry_count = 10;
+
+       fd = __sys_open(path, O_RDWR | O_CLOEXEC);
+
+       if (fd > 0) {
+               
+               if ((oflag & O_EXCL) == O_EXCL) {
+                       __sys_close(fd);
+                       errno = EEXIST;
+                       goto error;
+               }
+
+               if (_fstat(fd, &sbuf) != 0) {
+                       /* Bad things happened, like another thread closing our descriptor */
+                       __sys_close(fd);
+                       errno = EINVAL;
+                       goto error;
+               }
+
+               sem = sem_get_mapping(sbuf.st_ino, sbuf.st_dev);
+
+               if (sem != SEM_FAILED) {
+                       __sys_close(fd);
+                       goto done;
+               }
+
+               if ((sbuf.st_mode & S_IFREG) == 0) {
+                       /* We only want regular files here */
+                       __sys_close(fd);
+                       errno = EINVAL;
+                       goto error;
+               }
+       } else if ((oflag & O_CREAT) && errno == ENOENT) {
+
+               va_start(ap, oflag);
+
+               mode = (mode_t) va_arg(ap, int);
+               value = (unsigned int) va_arg(ap, int);
+
+               va_end(ap);
+
+               if (value > SEM_VALUE_MAX) {
+                       errno = EINVAL;
+                       goto error;
+               }
+
+               strlcpy(tmppath, prefix, sizeof(tmppath));
+               path_len = strlcat(tmppath, "/sem.XXXXXX", sizeof(tmppath));
+
+               if (path_len > sizeof(tmppath)) {
+                       errno = ENAMETOOLONG;
+                       goto error;     
+               }
+
+               while (retry_count-- > 0) {
+                       tmpname = mktemp(tmppath);
+
+                       if ( tmpname == NULL) {
+                               errno = EINVAL;
+                               goto error;
+                       }
+
+                       fd = __sys_open(tmpname, O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, mode);
+
+                       if (fd > 0 || errno != EEXIST) {
+                               break;
+                       }
+
+               }
+
+               if (retry_count == 0) {
+                       __sys_close(fd);
+                       errno = ENOSPC; /* XXX POSIX does not allow for EAGAIN */
+                       goto error;
+               }
+
+               create = 1;
+       }
+
+       if (fd == -1) {
+               switch (errno) {
+                       case ENOTDIR:
+                       case EISDIR:
+                       case EMLINK:
+                       case ELOOP:
+                               errno = EINVAL;
+                               break;
+                       case EDQUOT:
+                       case EIO:
+                               errno = ENOSPC;
+                               break;
+                       case EROFS:
+                               errno = EACCES;
+               }
+               goto error;
+       }
+
+       semtmp = (sem_t) mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
+                       MAP_NOSYNC | MAP_SHARED, fd, 0);
+
+       if (semtmp == MAP_FAILED) {
+               if (errno != EACCES && errno != EMFILE)
+                       errno = ENOMEM;
+
+               if (create)
+                       unlink(tmpname);
+
+               __sys_close(fd);        
+               goto error;
+       }
+
+       if (create) {
+               ftruncate(fd, sizeof(struct sem));
+               semtmp->magic = SEM_MAGIC;
+               semtmp->count = (u_int32_t)value;
+               semtmp->semid = SEMID_NAMED;
+
+               if (link(tmpname, path) != 0) {
+                       munmap(semtmp, getpagesize());
+                       __sys_close(fd);
+                       unlink(tmpname);
+
+                       if (errno == EEXIST && (oflag & O_EXCL) == 0) {
+                               goto retry;
+                       }
+
+                       goto error;
+               }
+               unlink(tmpname);
+
+               if (_fstat(fd, &sbuf) != 0) {
+                       /* Bad things happened, like another thread closing our descriptor */
+                       munmap(semtmp, getpagesize());
+                       __sys_close(fd);
+                       errno = EINVAL;
+                       goto error;
+               }
+
+       }
+       sem = sem_add_mapping(sbuf.st_ino, sbuf.st_dev, semtmp, fd);
+
+done:
+       _pthread_mutex_unlock(&sem_lock);
+       return (sem);
+
+error:
+       _pthread_mutex_unlock(&sem_lock);
        return (SEM_FAILED);
+
 }
 
 int
-_sem_close(__unused sem_t *sem)
+_sem_close(sem_t *sem)
 {
-       errno = ENOSYS;
-       return (-1);
+       _pthread_once(&once, sem_module_init);
+
+       _pthread_mutex_lock(&sem_lock);
+
+       if (sem_check_validity(sem)) {
+               _pthread_mutex_unlock(&sem_lock);
+               errno = EINVAL;
+               return (-1);
+       }
+
+       if (sem_close_mapping(sem)) {
+               _pthread_mutex_unlock(&sem_lock);
+               errno = EINVAL;
+               return (-1);
+       }
+       _pthread_mutex_unlock(&sem_lock);
+
+       return (0);
 }
 
 int
-_sem_unlink(__unused const char *name)
+_sem_unlink(const char *name)
 {
-       errno = ENOSYS;
-       return (-1);
+       char path[PATH_MAX];
+       const char *prefix;
+       int error;
+
+       error = get_path(name, path, PATH_MAX, &prefix);
+       if (error) {
+               errno = error;
+               return (-1);
+       }
+
+       error = unlink(path);
+
+       if(error) {
+               if (errno != ENAMETOOLONG && errno != ENOENT)
+                       errno = EACCES;
+
+               return (-1);
+       }
+
+       return (0);
 }
 
 __strong_reference(_sem_destroy, sem_destroy);
index 67eff5b..eb27269 100644 (file)
@@ -669,6 +669,10 @@ group for command connection sockets; see
 database of current users;
 see
 .Xr utmp 5
+.It Pa sem/
+rendez-vous files for posix named semaphores;
+see
+.Xr sem_open 3
 .El
 .Pp
 .It Pa rwho/
index a2dd688..5be7eee 100644 (file)
@@ -71,7 +71,7 @@
 #define        _POSIX_PRIORITY_SCHEDULING      200112L
 #define        _POSIX_RAW_SOCKETS              200112L
 #define        _POSIX_REALTIME_SIGNALS         200112L
-#define        _POSIX_SEMAPHORES               -1
+#define        _POSIX_SEMAPHORES               200112L
 #define        _POSIX_SHARED_MEMORY_OBJECTS    200112L
 #define        _POSIX_SPIN_LOCKS               200112L
 #define        _POSIX_SPORADIC_SERVER          -1