521 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			521 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* posix_ipc.cc: POSIX IPC API for Cygwin.
 | |
| 
 | |
| This file is part of Cygwin.
 | |
| 
 | |
| This software is a copyrighted work licensed under the terms of the
 | |
| Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | |
| details. */
 | |
| 
 | |
| #include "winsup.h"
 | |
| #include "shared_info.h"
 | |
| #include "thread.h"
 | |
| #include "path.h"
 | |
| #include "cygtls.h"
 | |
| #include "fhandler.h"
 | |
| #include "dtable.h"
 | |
| #include "cygheap.h"
 | |
| #include "sigproc.h"
 | |
| #include "ntdll.h"
 | |
| #include "tls_pbuf.h"
 | |
| #include <io.h>
 | |
| #include <sys/mman.h>
 | |
| #include <sys/param.h>
 | |
| #include <stdlib.h>
 | |
| #include <unistd.h>
 | |
| #include <mqueue.h>
 | |
| #include <semaphore.h>
 | |
| 
 | |
| /* The prefix_len is the length of the path prefix including trailing "/"
 | |
|    (or "/sem." for semaphores) as well as the trailing NUL. */
 | |
| static struct
 | |
| {
 | |
|   const char *prefix;
 | |
|   const size_t prefix_len;
 | |
|   const char *description;
 | |
| } ipc_names[] = {
 | |
|   { "/dev/shm", 10, "POSIX shared memory object" },
 | |
|   { "/dev/mqueue", 13, "POSIX message queue" },
 | |
|   { "/dev/shm", 14, "POSIX semaphore" }
 | |
| };
 | |
| 
 | |
| enum ipc_type_t
 | |
| {
 | |
|   shmem,
 | |
|   mqueue,
 | |
|   semaphore
 | |
| };
 | |
| 
 | |
| static bool
 | |
| check_path (char *res_name, ipc_type_t type, const char *name, size_t len)
 | |
| {
 | |
|   /* Note that we require the existance of the appropriate /dev subdirectories
 | |
|      for POSIX IPC object support, similar to Linux (which supports the
 | |
|      directories, but doesn't require to mount them).  We don't create
 | |
|      these directory here, that's the task of the installer.  But we check
 | |
|      for existance and give ample warning. */
 | |
|   path_conv path (ipc_names[type].prefix, PC_SYM_NOFOLLOW);
 | |
|   if (path.error || !path.exists () || !path.isdir ())
 | |
|     {
 | |
|       small_printf (
 | |
| 	"Warning: '%s' does not exists or is not a directory.\n\n"
 | |
| 	"%ss require the existance of this directory.\n"
 | |
| 	"Create the directory '%s' and set the permissions to 01777.\n"
 | |
| 	"For instance on the command line: mkdir -m 01777 %s\n",
 | |
| 	ipc_names[type].prefix, ipc_names[type].description,
 | |
| 	ipc_names[type].prefix, ipc_names[type].prefix);
 | |
|       set_errno (EINVAL);
 | |
|       return false;
 | |
|     }
 | |
|   /* Apart from handling backslash like slash, the naming rules are identical
 | |
|      to Linux, including the names and requirements for subdirectories, if
 | |
|      the name contains further slashes. */
 | |
|   /* Name must not be empty and has to start with a slash (or backslash) */
 | |
|   if (!name || !strchr ("/\\", name[0]))
 | |
|     {
 | |
|       debug_printf ("Invalid %s name '%s'", ipc_names[type].description, name);
 | |
|       set_errno (EINVAL);
 | |
|       return false;
 | |
|     }
 | |
|   /* Name must not consist of just a single slash (or backslash) */
 | |
|   if (!name[1])
 | |
|     {
 | |
|       debug_printf ("Invalid %s name '%s'", ipc_names[type].description, name);
 | |
|       set_errno (ENOENT);
 | |
|       return false;
 | |
|     }
 | |
|   /* Name must not contain slashes after the leading one */
 | |
|   if (strpbrk (name + 1, "/\\"))
 | |
|     {
 | |
|       debug_printf ("Invalid %s name '%s'", ipc_names[type].description, name);
 | |
|       set_errno (EACCES);
 | |
|       return false;
 | |
|     }
 | |
|   /* Length must be less than or equal to NAME_MAX, or NAME_MAX - 4 in
 | |
|      case of semaphores, due to the leading "sem." prefix */
 | |
|   if (len > NAME_MAX - (type == semaphore ? strlen ("sem.") : 0))
 | |
|     {
 | |
|       debug_printf ("%s name '%s' too long", ipc_names[type].description, name);
 | |
|       set_errno (ENAMETOOLONG);
 | |
|       return false;
 | |
|     }
 | |
|   __small_sprintf (res_name, "%s/%s%s", ipc_names[type].prefix,
 | |
| 					type == semaphore ? "sem." : "",
 | |
| 					name + 1);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| class ipc_flock
 | |
| {
 | |
|   struct flock fl;
 | |
| 
 | |
| public:
 | |
|   ipc_flock () { memset (&fl, 0, sizeof fl); }
 | |
| 
 | |
|   int lock (int fd, size_t size)
 | |
|   {
 | |
|     fl.l_type = F_WRLCK;
 | |
|     fl.l_whence = SEEK_SET;
 | |
|     fl.l_start = 0;
 | |
|     fl.l_len = size;
 | |
|     return fcntl64 (fd, F_SETLKW, &fl);
 | |
|   }
 | |
|   int unlock (int fd)
 | |
|   {
 | |
|     if (!fl.l_len)
 | |
|       return 0;
 | |
|     fl.l_type = F_UNLCK;
 | |
|     return fcntl64 (fd, F_SETLKW, &fl);
 | |
|   }
 | |
| };
 | |
| 
 | |
| /* POSIX shared memory object implementation. */
 | |
| 
 | |
| extern "C" int
 | |
| shm_open (const char *name, int oflag, mode_t mode)
 | |
| {
 | |
|   size_t len = strlen (name);
 | |
|   char shmname[ipc_names[shmem].prefix_len + len];
 | |
| 
 | |
|   if (!check_path (shmname, shmem, name, len))
 | |
|     return -1;
 | |
| 
 | |
|   /* Check for valid flags. */
 | |
|   if (((oflag & O_ACCMODE) != O_RDONLY && (oflag & O_ACCMODE) != O_RDWR)
 | |
|       || (oflag & ~(O_ACCMODE | O_CREAT | O_EXCL | O_TRUNC)))
 | |
|     {
 | |
|       debug_printf ("Invalid oflag 0%o", oflag);
 | |
|       set_errno (EINVAL);
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|   return open (shmname, oflag | O_CLOEXEC, mode & 0777);
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| shm_unlink (const char *name)
 | |
| {
 | |
|   size_t len = strlen (name);
 | |
|   char shmname[ipc_names[shmem].prefix_len + len];
 | |
| 
 | |
|   if (!check_path (shmname, shmem, name, len))
 | |
|     return -1;
 | |
| 
 | |
|   return unlink (shmname);
 | |
| }
 | |
| 
 | |
| /* The POSIX message queue implementation is based on W. Richard STEVENS
 | |
|    implementation, just tweaked for Cygwin.  The main change is
 | |
|    the usage of Windows mutexes and events instead of using the pthread
 | |
|    synchronization objects.  The pathname is massaged so that the
 | |
|    files are created under /dev/mqueue.  mq_timedsend and mq_timedreceive
 | |
|    are implemented additionally. */
 | |
| 
 | |
| extern "C" mqd_t
 | |
| mq_open (const char *name, int oflag, ...)
 | |
| {
 | |
|   va_list ap;
 | |
|   mode_t mode = 0;
 | |
|   fhandler_mqueue *fh = NULL;
 | |
|   struct mq_attr *attr = NULL;
 | |
| 
 | |
|   size_t len = strlen (name);
 | |
|   char mqname[ipc_names[mqueue].prefix_len + len];
 | |
| 
 | |
|   if (!check_path (mqname, mqueue, name, len))
 | |
|     return (mqd_t) -1;
 | |
| 
 | |
|   __try
 | |
|     {
 | |
|       if (oflag & O_CREAT)
 | |
| 	{
 | |
| 	  va_start (ap, oflag);		/* init ap to final named argument */
 | |
| 	  mode = va_arg (ap, mode_t) & ~S_IXUSR;
 | |
| 	  attr = va_arg (ap, struct mq_attr *);
 | |
| 	  va_end (ap);
 | |
| 	}
 | |
| 
 | |
|       /* Create file descriptor for mqueue */
 | |
|       cygheap_fdnew fd;
 | |
| 
 | |
|       if (fd < 0)
 | |
| 	__leave;
 | |
|       fh = (fhandler_mqueue *) build_fh_name (mqname,
 | |
| 					      PC_OPEN | PC_POSIX
 | |
| 					      | PC_SYM_NOFOLLOW | PC_NULLEMPTY,
 | |
| 					      NULL);
 | |
|       if (!fh)
 | |
| 	__leave;
 | |
| 
 | |
|       if (fh->mq_open (oflag, mode, attr))
 | |
| 	{
 | |
| 	  fd = fh;
 | |
| 	  return (mqd_t) fd;
 | |
| 	}
 | |
|     }
 | |
|   __except (EFAULT) {}
 | |
|   __endtry
 | |
|   if (fh)
 | |
|     delete fh;
 | |
|   return (mqd_t) -1;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_getattr (mqd_t mqd, struct mq_attr *mqstat)
 | |
| {
 | |
|   int ret = -1;
 | |
| 
 | |
|   cygheap_fdget fd ((int) mqd, true);
 | |
|   fhandler_mqueue *fh = fd->is_mqueue ();
 | |
|   if (!fh)
 | |
|     set_errno (EBADF);
 | |
|   else
 | |
|     ret = fh->mq_getattr (mqstat);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_setattr (mqd_t mqd, const struct mq_attr *mqstat, struct mq_attr *omqstat)
 | |
| {
 | |
|   int ret = -1;
 | |
| 
 | |
|   cygheap_fdget fd ((int) mqd, true);
 | |
|   fhandler_mqueue *fh = fd->is_mqueue ();
 | |
|   if (!fh)
 | |
|     set_errno (EBADF);
 | |
|   else
 | |
|     ret = fh->mq_setattr (mqstat, omqstat);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_notify (mqd_t mqd, const struct sigevent *notification)
 | |
| {
 | |
|   int ret = -1;
 | |
| 
 | |
|   cygheap_fdget fd ((int) mqd, true);
 | |
|   fhandler_mqueue *fh = fd->is_mqueue ();
 | |
|   if (!fh)
 | |
|     set_errno (EBADF);
 | |
|   else
 | |
|     ret = fh->mq_notify (notification);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_timedsend (mqd_t mqd, const char *ptr, size_t len, unsigned int prio,
 | |
| 	      const struct timespec *abstime)
 | |
| {
 | |
|   int ret = -1;
 | |
| 
 | |
|   cygheap_fdget fd ((int) mqd, true);
 | |
|   fhandler_mqueue *fh = fd->is_mqueue ();
 | |
|   if (!fh)
 | |
|     set_errno (EBADF);
 | |
|   else
 | |
|     ret = fh->mq_timedsend (ptr, len, prio, abstime);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_send (mqd_t mqd, const char *ptr, size_t len, unsigned int prio)
 | |
| {
 | |
|   return mq_timedsend (mqd, ptr, len, prio, NULL);
 | |
| }
 | |
| 
 | |
| extern "C" ssize_t
 | |
| mq_timedreceive (mqd_t mqd, char *ptr, size_t maxlen, unsigned int *priop,
 | |
| 	     const struct timespec *abstime)
 | |
| {
 | |
|   int ret = -1;
 | |
| 
 | |
|   cygheap_fdget fd ((int) mqd, true);
 | |
|   fhandler_mqueue *fh = fd->is_mqueue ();
 | |
|   if (!fh)
 | |
|     set_errno (EBADF);
 | |
|   else
 | |
|     ret = fh->mq_timedrecv (ptr, maxlen, priop, abstime);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" ssize_t
 | |
| mq_receive (mqd_t mqd, char *ptr, size_t maxlen, unsigned int *priop)
 | |
| {
 | |
|   return mq_timedreceive (mqd, ptr, maxlen, priop, NULL);
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_close (mqd_t mqd)
 | |
| {
 | |
|   __try
 | |
|     {
 | |
|       cygheap_fdget fd ((int) mqd, true);
 | |
|       if (!fd->is_mqueue ())
 | |
| 	{
 | |
| 	  set_errno (EBADF);
 | |
| 	  __leave;
 | |
| 	}
 | |
| 
 | |
|       if (mq_notify (mqd, NULL))	/* unregister calling process */
 | |
| 	__leave;
 | |
| 
 | |
|       fd->isclosed (true);
 | |
|       fd->close ();
 | |
|       fd.release ();
 | |
|       return 0;
 | |
|     }
 | |
|   __except (EBADF) {}
 | |
|   __endtry
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| mq_unlink (const char *name)
 | |
| {
 | |
|   size_t len = strlen (name);
 | |
|   char mqname[ipc_names[mqueue].prefix_len + len];
 | |
| 
 | |
|   if (!check_path (mqname, mqueue, name, len))
 | |
|     return -1;
 | |
|   if (unlink (mqname) == -1)
 | |
|     return -1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* POSIX named semaphore implementation.  Loosely based on Richard W. STEPHENS
 | |
|    implementation as far as sem_open is concerned, but under the hood using
 | |
|    the already existing semaphore class in thread.cc.  Using a file backed
 | |
|    solution allows to implement kernel persistent named semaphores.  */
 | |
| 
 | |
| #define	 MAX_TRIES	10	/* for waiting for initialization */
 | |
| 
 | |
| struct sem_finfo
 | |
| {
 | |
|   unsigned int       value;
 | |
|   unsigned long long hash;
 | |
|   LUID               luid;
 | |
| };
 | |
| 
 | |
| extern "C" sem_t *
 | |
| sem_open (const char *name, int oflag, ...)
 | |
| {
 | |
|   int i, fd = -1, created = 0;
 | |
|   va_list ap;
 | |
|   mode_t mode = 0;
 | |
|   unsigned int value = 0;
 | |
|   struct stat statbuff;
 | |
|   sem_t *sem = SEM_FAILED;
 | |
|   sem_finfo sf;
 | |
|   bool wasopen = false;
 | |
|   ipc_flock file;
 | |
| 
 | |
|   size_t len = strlen (name);
 | |
|   char semname[ipc_names[semaphore].prefix_len + len];
 | |
| 
 | |
|   if (!check_path (semname, semaphore, name, len))
 | |
|     return SEM_FAILED;
 | |
| 
 | |
|   __try
 | |
|     {
 | |
|       oflag &= (O_CREAT | O_EXCL);
 | |
| 
 | |
|     again:
 | |
|       if (oflag & O_CREAT)
 | |
| 	{
 | |
| 	  va_start (ap, oflag);		/* init ap to final named argument */
 | |
| 	  mode = va_arg (ap, mode_t) & ~S_IXUSR;
 | |
| 	  value = va_arg (ap, unsigned int);
 | |
| 	  va_end (ap);
 | |
| 
 | |
| 	  /* Open and specify O_EXCL and user-execute */
 | |
| 	  fd = open (semname, oflag | O_EXCL | O_RDWR | O_CLOEXEC,
 | |
| 		     mode | S_IXUSR);
 | |
| 	  if (fd < 0)
 | |
| 	    {
 | |
| 	      if (errno == EEXIST && (oflag & O_EXCL) == 0)
 | |
| 		goto exists;		/* already exists, OK */
 | |
| 	      return SEM_FAILED;
 | |
| 	    }
 | |
| 	  created = 1;
 | |
| 	  /* First one to create the file initializes it. */
 | |
| 	  NtAllocateLocallyUniqueId (&sf.luid);
 | |
| 	  sf.value = value;
 | |
| 	  sf.hash = hash_path_name (0, semname);
 | |
| 	  if (write (fd, &sf, sizeof sf) != sizeof sf)
 | |
| 	    __leave;
 | |
| 	  sem = semaphore::open (sf.hash, sf.luid, fd, oflag, mode, value,
 | |
| 				 wasopen);
 | |
| 	  if (sem == SEM_FAILED)
 | |
| 	    __leave;
 | |
| 	  /* Initialization complete, turn off user-execute bit */
 | |
| 	  if (fchmod (fd, mode) == -1)
 | |
| 	    __leave;
 | |
| 	  /* Don't close (fd); */
 | |
| 	  return sem;
 | |
| 	}
 | |
| 
 | |
|     exists:
 | |
|       /* Open the file and fetch the semaphore name. */
 | |
|       if ((fd = open (semname, O_RDWR | O_CLOEXEC)) < 0)
 | |
| 	{
 | |
| 	  if (errno == ENOENT && (oflag & O_CREAT))
 | |
| 	    goto again;
 | |
| 	  __leave;
 | |
| 	}
 | |
|       /* Make certain initialization is complete */
 | |
|       for (i = 0; i < MAX_TRIES; i++)
 | |
| 	{
 | |
| 	  if (stat64 (semname, &statbuff) == -1)
 | |
| 	    {
 | |
| 	      if (errno == ENOENT && (oflag & O_CREAT))
 | |
| 		{
 | |
| 		  close (fd);
 | |
| 		  fd = -1;
 | |
| 		  goto again;
 | |
| 		}
 | |
| 	      __leave;
 | |
| 	    }
 | |
| 	  if ((statbuff.st_mode & S_IXUSR) == 0)
 | |
| 	    break;
 | |
| 	  sleep (1);
 | |
| 	}
 | |
|       if (i == MAX_TRIES)
 | |
| 	{
 | |
| 	  set_errno (ETIMEDOUT);
 | |
| 	  __leave;
 | |
| 	}
 | |
|       if (file.lock (fd, sizeof sf))
 | |
| 	__leave;
 | |
|       if (read (fd, &sf, sizeof sf) != sizeof sf)
 | |
| 	__leave;
 | |
|       sem = semaphore::open (sf.hash, sf.luid, fd, oflag, mode, sf.value,
 | |
| 			     wasopen);
 | |
|       file.unlock (fd);
 | |
|       if (sem == SEM_FAILED)
 | |
| 	__leave;
 | |
|       /* If wasopen is set, the semaphore was already opened and we already have
 | |
| 	 an open file descriptor pointing to the file.  This means, we have to
 | |
| 	 close the file descriptor created in this call.  It won't be stored
 | |
| 	 anywhere anyway. */
 | |
|       if (wasopen)
 | |
| 	close (fd);
 | |
|       return sem;
 | |
|     }
 | |
|   __except (EFAULT) {}
 | |
|   __endtry
 | |
|   /* Don't let following function calls change errno */
 | |
|   save_errno save;
 | |
| 
 | |
|   if (fd >= 0)
 | |
|     file.unlock (fd);
 | |
|   if (created)
 | |
|     unlink (semname);
 | |
|   if (sem != SEM_FAILED)
 | |
|     semaphore::close (sem);
 | |
|   if (fd >= 0)
 | |
|     close (fd);
 | |
|   return SEM_FAILED;
 | |
| }
 | |
| 
 | |
| extern "C" off_t lseek64 (int, off_t, int);
 | |
| 
 | |
| int
 | |
| _sem_close (sem_t *sem, bool do_close)
 | |
| {
 | |
|   sem_finfo sf;
 | |
|   int fd, ret = -1;
 | |
|   ipc_flock file;
 | |
| 
 | |
|   if (semaphore::getinternal (sem, &fd, &sf.hash, &sf.luid, &sf.value) == -1)
 | |
|     return -1;
 | |
|   if (!file.lock (fd, sizeof sf)
 | |
|       && lseek64 (fd, 0LL, SEEK_SET) != (off_t) -1
 | |
|       && write (fd, &sf, sizeof sf) == sizeof sf)
 | |
|     ret = do_close ? semaphore::close (sem) : 0;
 | |
| 
 | |
|   /* Don't let following function calls change errno */
 | |
|   save_errno save;
 | |
|   file.unlock (fd);
 | |
|   close (fd);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| sem_close (sem_t *sem)
 | |
| {
 | |
|   return _sem_close (sem, true);
 | |
| }
 | |
| 
 | |
| extern "C" int
 | |
| sem_unlink (const char *name)
 | |
| {
 | |
|   size_t len = strlen (name);
 | |
|   char semname[ipc_names[semaphore].prefix_len + len];
 | |
| 
 | |
|   if (!check_path (semname, semaphore, name, len))
 | |
|     return -1;
 | |
|   if (unlink (semname) == -1)
 | |
|     return -1;
 | |
|   return 0;
 | |
| }
 |