/* This module provides a set of abstract shared memory interfaces
 * with support using both System V and POSIX shared memory
 * implementations.  The code is divided into three sections:
 *
 *	- common (interface-independent) code
 *	- POSIX implementation
 *	- System V implementation
 *  - Windows implementation
 *
 * The implementation used is determined by whether USE_POSIX_SHM was
 * set in the ./configure step.
 */

/*
 Copyright (C) 2001-2003 Paul Davis
 Copyright (C) 2005-2012 Grame
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

 */

#include "JackConstants.h"

#ifdef WIN32
#include <process.h>
#include <stdio.h>
#else

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <limits.h>
#include <errno.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdlib.h>
#include "promiscuous.h"

#endif

#include "shm.h"
#include "JackError.h"

static int GetUID()
{
#ifdef WIN32
    return  _getpid();
    //#error "No getuid function available"
#else
    return geteuid();
#endif
}

static int GetPID()
{
#ifdef WIN32
    return  _getpid();
#else
    return getpid();
#endif
}

#ifdef USE_POSIX_SHM
static const jack_shmtype_t jack_shmtype = shm_POSIX;
#elif WIN32
static const jack_shmtype_t jack_shmtype = shm_WIN32;
#else
static const jack_shmtype_t jack_shmtype = shm_SYSV;
#endif

/* interface-dependent forward declarations */
static int	jack_access_registry (jack_shm_info_t *ri);
static int	jack_create_registry (jack_shm_info_t *ri);
static void	jack_remove_shm (const jack_shm_id_t id);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * common interface-independent section
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* The JACK SHM registry is a chunk of memory for keeping track of the
 * shared memory used by each active JACK server.  This allows the
 * server to clean up shared memory when it exits.  To avoid memory
 * leakage due to kill -9, crashes or debugger-driven exits, this
 * cleanup is also done when a new instance of that server starts.
 */

/* per-process global data for the SHM interfaces */
#ifdef USE_POSIX_SHM
static const jack_shm_id_t registry_id = "/jack-shm-registry";
#elif WIN32
static const jack_shm_id_t registry_id = "jack-shm-registry";
#else
static jack_shm_id_t   registry_id;	/* SHM id for the registry */
#endif

#ifdef WIN32
static jack_shm_info_t registry_info = {/* SHM info for the registry */
	JACK_SHM_NULL_INDEX,
	0,
	{ NULL }
};
#else
static jack_shm_info_t registry_info = { /* SHM info for the registry */
	.index = JACK_SHM_NULL_INDEX,
	.ptr.attached_at = MAP_FAILED
};
#endif

/* pointers to registry header and array */
static jack_shm_header_t   *jack_shm_header = NULL;
static jack_shm_registry_t *jack_shm_registry = NULL;

/* jack_shm_lock_registry() serializes updates to the shared memory
 * segment JACK uses to keep track of the SHM segments allocated to
 * all its processes, including multiple servers.
 *
 * This is not a high-contention lock, but it does need to work across
 * multiple processes. High transaction rates and realtime safety are
 * not required. Any solution needs to at least be portable to POSIX
 * and POSIX-like systems.
 *
 * We must be particularly careful to ensure that the lock be released
 * if the owning process terminates abnormally. Otherwise, a segfault
 * or kill -9 at the wrong moment could prevent JACK from ever running
 * again on that machine until after a reboot.
 */

#define JACK_SEMAPHORE_KEY 0x282929
#ifndef USE_POSIX_SHM
#define JACK_SHM_REGISTRY_KEY JACK_SEMAPHORE_KEY
#endif

static int semid = -1;

#ifdef WIN32

#include <psapi.h>
#include <lmcons.h>

static BOOL check_process_running(DWORD process_id)
{
    DWORD aProcesses[2048], cbNeeded, cProcesses;
    unsigned int i;

    // Enumerate all processes
    if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) {
        return FALSE;
    }

    // Calculate how many process identifiers were returned.
    cProcesses = cbNeeded / sizeof(DWORD);

    for (i = 0; i < cProcesses; i++) {
         if (aProcesses[i] == process_id) {
            // Process process_id is running...
            return TRUE;
        }
    }
    return FALSE;
}

static int
semaphore_init () {return 0;}

static  int
semaphore_add (int value) {return 0;}

#else
/* all semaphore errors are fatal -- issue message, but do not return */
static void
semaphore_error (char *msg)
{
	jack_error ("JACK semaphore error: %s (%s)",
		    msg, strerror (errno));
}

static int
semaphore_init ()
{
	key_t semkey = JACK_SEMAPHORE_KEY;
	struct sembuf sbuf;
	int create_flags = IPC_CREAT | IPC_EXCL
		| S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

	/* Get semaphore ID associated with this key. */
	if ((semid = semget(semkey, 0, 0)) == -1) {

		/* Semaphore does not exist - Create. */
		if ((semid = semget(semkey, 1, create_flags)) != -1) {

			/* Initialize the semaphore, allow one owner. */
			sbuf.sem_num = 0;
			sbuf.sem_op = 1;
			sbuf.sem_flg = 0;
			if (semop(semid, &sbuf, 1) == -1) {
                semaphore_error ("semop");
                return -1;
			}

		} else if (errno == EEXIST) {
			if ((semid = semget(semkey, 0, 0)) == -1) {
                semaphore_error ("semget");
                return -1;
			}

		} else {
            semaphore_error ("semget creation");
            return -1;
		}
	}

    return 0;
}

static inline int
semaphore_add (int value)
{
	struct sembuf sbuf;

	sbuf.sem_num = 0;
	sbuf.sem_op = value;
	sbuf.sem_flg = SEM_UNDO;

	if (semop(semid, &sbuf, 1) == -1) {
		semaphore_error ("semop");
        return -1;
	}

    return 0;
}

#endif

static int
jack_shm_lock_registry (void)
{
	if (semid == -1) {
        if (semaphore_init () < 0)
            return -1;
    }

	return semaphore_add (-1);
}

static void
jack_shm_unlock_registry (void)
{
	semaphore_add (1);
}

static void
jack_shm_init_registry ()
{
	/* registry must be locked */
	int i;

	memset (jack_shm_header, 0, JACK_SHM_REGISTRY_SIZE);

	jack_shm_header->magic = JACK_SHM_MAGIC;
	//jack_shm_header->protocol = JACK_PROTOCOL_VERSION;
	jack_shm_header->type = jack_shmtype;
	jack_shm_header->size = JACK_SHM_REGISTRY_SIZE;
	jack_shm_header->hdr_len = sizeof (jack_shm_header_t);
	jack_shm_header->entry_len = sizeof (jack_shm_registry_t);

	for (i = 0; i < MAX_SHM_ID; ++i) {
		jack_shm_registry[i].index = i;
	}
}

static int
jack_shm_validate_registry ()
{
	/* registry must be locked */

	if ((jack_shm_header->magic == JACK_SHM_MAGIC)
	    //&& (jack_shm_header->protocol == JACK_PROTOCOL_VERSION)
	    && (jack_shm_header->type == jack_shmtype)
	    && (jack_shm_header->size == JACK_SHM_REGISTRY_SIZE)
	    && (jack_shm_header->hdr_len == sizeof (jack_shm_header_t))
	    && (jack_shm_header->entry_len == sizeof (jack_shm_registry_t))) {

		return 0;		/* registry OK */
	}

	return -1;
}

/* set a unique per-user, per-server shm prefix string
 *
 * According to the POSIX standard:
 *
 *   "The name argument conforms to the construction rules for a
 *   pathname. If name begins with the slash character, then processes
 *   calling shm_open() with the same value of name refer to the same
 *   shared memory object, as long as that name has not been
 *   removed. If name does not begin with the slash character, the
 *   effect is implementation-defined. The interpretation of slash
 *   characters other than the leading slash character in name is
 *   implementation-defined."
 *
 * Since the Linux implementation does not allow slashes *within* the
 * name, in the interest of portability we use colons instead.
 */
static void
jack_get_server_prefix (const char* const server_name, char* const prefix, const size_t size)
{
#ifdef WIN32
    char buffer[UNLEN+1]={0};
    DWORD len = UNLEN+1;
    GetUserName(buffer, &len);
    snprintf (prefix, size,
		  "jack-%s:%s:", buffer, server_name);
#else
    snprintf (prefix, size,
		  "jack-%d:%s:", GetUID(), server_name);
#endif
}

/* gain server addressability to shared memory registration segment
 *
 * returns: 0 if successful
 */
static int
jack_server_initialize_shm (int new_registry)
{
	int rc;

	if (jack_shm_header)
		return 0;		/* already initialized */

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	rc = jack_access_registry (&registry_info);

	if (new_registry) {
		jack_remove_shm (registry_id);
		rc = ENOENT;
	}

	switch (rc) {
	case ENOENT:		/* registry does not exist */
		rc = jack_create_registry (&registry_info);
		break;
	case 0:				/* existing registry */
		if (jack_shm_validate_registry () == 0)
			break;
		/* else it was invalid, so fall through */
	case EINVAL:			/* bad registry */
		/* Apparently, this registry was created by an older
		 * JACK version.  Delete it so we can try again. */
		jack_release_shm (&registry_info);
		jack_remove_shm (registry_id);
		if ((rc = jack_create_registry (&registry_info)) != 0) {
			jack_error ("incompatible shm registry (%s)",
				    strerror (errno));
#ifndef USE_POSIX_SHM
			jack_error ("to delete, use `ipcrm -M 0x%0.8x'",
				    JACK_SHM_REGISTRY_KEY);
#endif
		}
		break;
	default:			/* failure return code */
		break;
	}

	jack_shm_unlock_registry ();
	return rc;
}

/* gain client addressability to shared memory registration segment
 *
 * NOTE: this function is no longer used for server initialization,
 * instead it calls jack_register_server().
 *
 * returns: 0 if successful
 */
int
jack_initialize_shm (const char *server_name)
{
	int rc;
	char server_prefix[JACK_SERVER_NAME_SIZE+1];

	if (jack_shm_header)
		return 0;		/* already initialized */

	jack_get_server_prefix (server_name, server_prefix, sizeof(server_prefix));

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	if ((rc = jack_access_registry (&registry_info)) == 0) {
		if ((rc = jack_shm_validate_registry ()) != 0) {
			jack_error ("Incompatible shm registry, "
				    "are jackd and libjack in sync?");
		}
	}
	jack_shm_unlock_registry ();

	return rc;
}


char* jack_shm_addr (jack_shm_info_t* si)
{
	return (char*)si->ptr.attached_at;
}

void
jack_destroy_shm (jack_shm_info_t* si)
{
	/* must NOT have the registry locked */
	if (si->index == JACK_SHM_NULL_INDEX)
		return;			/* segment not allocated */

	jack_remove_shm (jack_shm_registry[si->index].id);
	jack_release_shm_info (si->index);
}

jack_shm_registry_t *
jack_get_free_shm_info ()
{
	/* registry must be locked */
	jack_shm_registry_t* si = NULL;
	int i;

	for (i = 0; i < MAX_SHM_ID; ++i) {
		if (jack_shm_registry[i].size == 0) {
			break;
		}
	}

	if (i < MAX_SHM_ID) {
		si = &jack_shm_registry[i];
	}

	return si;
}

static void
jack_release_shm_entry (jack_shm_registry_index_t index)
{
	/* the registry must be locked */
	jack_shm_registry[index].size = 0;
	jack_shm_registry[index].allocator = 0;
	memset (&jack_shm_registry[index].id, 0,
		sizeof (jack_shm_registry[index].id));
}

int
jack_release_shm_info (jack_shm_registry_index_t index)
{
	/* must NOT have the registry locked */
	if (jack_shm_registry[index].allocator == GetPID()) {
		if (jack_shm_lock_registry () < 0) {
            jack_error ("jack_shm_lock_registry fails...");
            return -1;
        }
		jack_release_shm_entry (index);
		jack_shm_unlock_registry ();
	}

    return 0;
}

/* Claim server_name for this process.
 *
 * returns 0 if successful
 *	   EEXIST if server_name was already active for this user
 *	   ENOSPC if server registration limit reached
 *	   ENOMEM if unable to access shared memory registry
 */
int
jack_register_server (const char *server_name, int new_registry)
{
	int i, res = 0;
	char server_prefix[JACK_SERVER_NAME_SIZE+1];

	jack_get_server_prefix (server_name, server_prefix, sizeof(server_prefix));

	if (jack_server_initialize_shm (new_registry))
		return ENOMEM;

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	/* See if server_name already registered.  Since server names
	 * are per-user, we register the unique server prefix string.
	 */
	for (i = 0; i < MAX_SERVERS; i++) {

		if (strncmp (jack_shm_header->server[i].name,
			     server_prefix,
			     sizeof(server_prefix)) != 0)
			continue;	/* no match */

		if (jack_shm_header->server[i].pid == GetPID()){
			res = 0; /* it's me */
			goto unlock;
		}

		/* see if server still exists */
    #ifdef WIN32
        if (check_process_running(jack_shm_header->server[i].pid)) {
            res = EEXIST;	/* other server running */
			goto unlock;
        }
	#else
		if (kill (jack_shm_header->server[i].pid, 0) == 0)  {
			res = EEXIST;	/* other server running */
			goto unlock;
		}
	#endif

		/* it's gone, reclaim this entry */
		memset (&jack_shm_header->server[i], 0,
			sizeof (jack_shm_server_t));
	}

	/* find a free entry */
	for (i = 0; i < MAX_SERVERS; i++) {
		if (jack_shm_header->server[i].pid == 0)
			break;
	}

	if (i >= MAX_SERVERS){
		res = ENOSPC;		/* out of space */
		goto unlock;
	}

	/* claim it */
	jack_shm_header->server[i].pid = GetPID();
	strncpy (jack_shm_header->server[i].name,
		 server_prefix,
		 sizeof(server_prefix));

 unlock:
	jack_shm_unlock_registry ();
	return res;
}

/* release server_name registration */
int
jack_unregister_server (const char *server_name /* unused */)
{
	int i;
	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	for (i = 0; i < MAX_SERVERS; i++) {
		if (jack_shm_header->server[i].pid == GetPID()) {
			memset (&jack_shm_header->server[i], 0,
				sizeof (jack_shm_server_t));
		}
	}

	jack_shm_unlock_registry ();
	return 0;
}

/* called for server startup and termination */
int
jack_cleanup_shm ()
{
	int i;
	int destroy;
	jack_shm_info_t copy;

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	for (i = 0; i < MAX_SHM_ID; i++) {
		jack_shm_registry_t* r;

		r = &jack_shm_registry[i];
		memcpy (&copy, r, sizeof (jack_shm_info_t));
		destroy = FALSE;

		/* ignore unused entries */
		if (r->allocator == 0)
			continue;

		/* is this my shm segment? */
		if (r->allocator == GetPID()) {

			/* allocated by this process, so unattach
			   and destroy. */
			jack_release_shm (&copy);
			destroy = TRUE;

		} else {

			/* see if allocator still exists */
		#ifdef WIN32 
			//jack_info("TODO: kill API not available !!");
		#else
			if (kill (r->allocator, 0)) {
				if (errno == ESRCH) {
					/* allocator no longer exists,
					 * so destroy */
					destroy = TRUE;
				}
			}
		#endif
		}

		if (destroy) {

			int index = copy.index;

			if ((index >= 0)  && (index < MAX_SHM_ID)) {
				jack_remove_shm (jack_shm_registry[index].id);
				jack_release_shm_entry (index);
			}
			r->size = 0;
			r->allocator = 0;
		}
	}

	jack_shm_unlock_registry ();
	return TRUE;
}

/* resize a shared memory segment
 *
 * There is no way to resize a System V shm segment.  Resizing is
 * possible with POSIX shm, but not with the non-conformant Mac OS X
 * implementation.  Since POSIX shm is mainly used on that platform,
 * it's simpler to treat them both the same.
 *
 * So, we always resize by deleting and reallocating.  This is
 * tricky, because the old segment will not disappear until
 * all the clients have released it.  We only do what we can
 * from here.
 *
 * This is not done under a single lock.  I don't even want to think
 * about all the things that could possibly go wrong if multiple
 * processes tried to resize the same segment concurrently.  That
 * probably doesn't happen.
 */
int
jack_resize_shm (jack_shm_info_t* si, jack_shmsize_t size)
{
	jack_shm_id_t id;

	/* The underlying type of `id' differs for SYSV and POSIX */
	memcpy (&id, &jack_shm_registry[si->index].id, sizeof (id));

	jack_release_shm (si);
	jack_destroy_shm (si);

	if (jack_shmalloc ((char *) id, size, si)) {
		return -1;
	}

	return jack_attach_shm (si);
}

int
jack_attach_lib_shm (jack_shm_info_t* si)
{
    int res = jack_attach_shm(si);
    if (res == 0)
        si->size = jack_shm_registry[si->index].size; // Keep size in si struct
    return res;
}

int
jack_attach_lib_shm_read (jack_shm_info_t* si)
{
    int res = jack_attach_shm_read(si);
    if (res == 0)
        si->size = jack_shm_registry[si->index].size; // Keep size in si struct
    return res;
}

#ifdef USE_POSIX_SHM

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * POSIX interface-dependent functions
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* gain addressability to existing SHM registry segment
 *
 * sets up global registry pointers, if successful
 *
 * returns: 0 if existing registry accessed successfully
 *          ENOENT if registry does not exist
 *          EINVAL if registry exists, but has the wrong size
 */
static int
jack_access_registry (jack_shm_info_t *ri)
{
	/* registry must be locked */
	int shm_fd;

	/* try to open an existing segment */
	if ((shm_fd = shm_open (registry_id, O_RDWR, 0666)) < 0) {
		int rc = errno;
		if (errno != ENOENT) {
			jack_error ("Cannot open existing shm registry segment"
				    " (%s)", strerror (errno));
		}
		close (shm_fd);
		return rc;
	}

	if ((ri->ptr.attached_at = mmap (0, JACK_SHM_REGISTRY_SIZE,
				     PROT_READ|PROT_WRITE,
				     MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
		jack_error ("Cannot mmap shm registry segment (%s)",
			    strerror (errno));
		close (shm_fd);
		return EINVAL;
	}

	/* set up global pointers */
	ri->index = JACK_SHM_REGISTRY_INDEX;
	jack_shm_header = ri->ptr.attached_at;
	jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);

	close (shm_fd); 
	return 0;
}

/* create a new SHM registry segment
 *
 * sets up global registry pointers, if successful
 *
 * returns: 0 if registry created successfully
 *          nonzero error code if unable to allocate a new registry
 */
static int
jack_create_registry (jack_shm_info_t *ri)
{
	/* registry must be locked */
	int shm_fd;

	if ((shm_fd = shm_open (registry_id, O_RDWR|O_CREAT, 0666)) < 0) {
		int rc = errno;
		jack_error ("Cannot create shm registry segment (%s)",
			    strerror (errno));
		return rc;
	}

    /* Previous shm_open result depends of the actual value of umask, force correct file permission here */
    if (fchmod(shm_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) {
	    jack_log("Cannot chmod jack-shm-registry (%s) %d %d", strerror (errno));
    }

	/* Set the desired segment size.  NOTE: the non-conformant Mac
	 * OS X POSIX shm only allows ftruncate() on segment creation.
	 */
	if (ftruncate (shm_fd, JACK_SHM_REGISTRY_SIZE) < 0) {
		int rc = errno;
		jack_error ("Cannot set registry size (%s)", strerror (errno));
		jack_remove_shm (registry_id);
		close (shm_fd);
		return rc;
	}

	if ((ri->ptr.attached_at = mmap (0, JACK_SHM_REGISTRY_SIZE,
				     PROT_READ|PROT_WRITE,
				     MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
		jack_error ("Cannot mmap shm registry segment (%s)",
			    strerror (errno));
		jack_remove_shm (registry_id);
		close (shm_fd);
		return EINVAL;
	}

	/* set up global pointers */
	ri->index = JACK_SHM_REGISTRY_INDEX;
	jack_shm_header = ri->ptr.attached_at;
	jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);

	/* initialize registry contents */
	jack_shm_init_registry ();
	close (shm_fd); 
	return 0;
}

static void
jack_remove_shm (const jack_shm_id_t id)
{
	/* registry may or may not be locked */
	shm_unlink (id);
}

void
jack_release_shm (jack_shm_info_t* si)
{
	/* registry may or may not be locked */
	if (si->ptr.attached_at != MAP_FAILED) {
		munmap (si->ptr.attached_at, jack_shm_registry[si->index].size);
  	}
}

void
jack_release_lib_shm (jack_shm_info_t* si)
{
	/* registry may or may not be locked */
	if (si->ptr.attached_at != MAP_FAILED) {
       munmap (si->ptr.attached_at, si->size);
	}
}

/* allocate a POSIX shared memory segment */
int
jack_shmalloc (const char *shm_name, jack_shmsize_t size, jack_shm_info_t* si)
{
	jack_shm_registry_t* registry;
	int shm_fd;
	int rc = -1;
	char name[SHM_NAME_MAX+1];
	const char* promiscuous;

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	if ((registry = jack_get_free_shm_info ()) == NULL) {
		jack_error ("shm registry full");
		goto unlock;
	}

	/* On Mac OS X, the maximum length of a shared memory segment
	 * name is SHM_NAME_MAX (instead of NAME_MAX or PATH_MAX as
	 * defined by the standard).  Unfortunately, Apple sets this
	 * value so small (about 31 bytes) that it is useless for
	 * actual names.  So, we construct a short name from the
	 * registry index for uniqueness and ignore the shm_name
	 * parameter.  Bah!
	 */
	snprintf (name, sizeof (name), "/jack-%d-%d", GetUID(), registry->index);

	if (strlen (name) >= sizeof (registry->id)) {
		jack_error ("shm segment name too long %s", name);
		goto unlock;
	}

	if ((shm_fd = shm_open (name, O_RDWR|O_CREAT, 0666)) < 0) {
		jack_error ("Cannot create shm segment %s (%s)",
			    name, strerror (errno));
		goto unlock;
	}

	if (ftruncate (shm_fd, size) < 0) {
		jack_error ("Cannot set size of engine shm "
			    "registry 0 (%s)",
			    strerror (errno));
		close (shm_fd);
		goto unlock;
	}

	promiscuous = getenv("JACK_PROMISCUOUS_SERVER");
	if ((promiscuous != NULL) && (jack_promiscuous_perms(shm_fd, name, jack_group2gid(promiscuous)) < 0))
		goto unlock;

	close (shm_fd);
	registry->size = size;
	strncpy (registry->id, name, sizeof (registry->id));
	registry->allocator = GetPID();
	si->index = registry->index;
	si->ptr.attached_at = MAP_FAILED;	/* not attached */
	rc = 0;				/* success */

 unlock:
	jack_shm_unlock_registry ();
	return rc;
}

int
jack_attach_shm (jack_shm_info_t* si)
{
	int shm_fd;
	jack_shm_registry_t *registry = &jack_shm_registry[si->index];

	if ((shm_fd = shm_open (registry->id,
				O_RDWR, 0666)) < 0) {
		jack_error ("Cannot open shm segment %s (%s)", registry->id,
			    strerror (errno));
		return -1;
	}

	if ((si->ptr.attached_at = mmap (0, registry->size, PROT_READ|PROT_WRITE,
				     MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
		jack_error ("Cannot mmap shm segment %s (%s)",
			    registry->id,
			    strerror (errno));
		close (shm_fd);
		return -1;
	}

	close (shm_fd);
	return 0;
}

int
jack_attach_shm_read (jack_shm_info_t* si)
{
	int shm_fd;
	jack_shm_registry_t *registry = &jack_shm_registry[si->index];

	if ((shm_fd = shm_open (registry->id,
				O_RDONLY, 0666)) < 0) {
		jack_error ("Cannot open shm segment %s (%s)", registry->id,
			    strerror (errno));
		return -1;
	}

	if ((si->ptr.attached_at = mmap (0, registry->size, PROT_READ,
				     MAP_SHARED, shm_fd, 0)) == MAP_FAILED) {
		jack_error ("Cannot mmap shm segment %s (%s)",
			    registry->id,
			    strerror (errno));
		close (shm_fd);
		return -1;
	}

	close (shm_fd);
	return 0;
}

#elif WIN32

static int
jack_access_registry (jack_shm_info_t *ri)
{
	/* registry must be locked */
	HANDLE shm_fd;

	/* try to open an existing segment */

	if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry_id)) == NULL) {
		int rc = GetLastError();
		if (rc != ERROR_FILE_NOT_FOUND) {
			jack_error ("Cannot open existing shm registry segment (%ld)", rc);
		}
		return rc;
	}

	if ((ri->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, JACK_SHM_REGISTRY_SIZE)) == NULL) {
		jack_error ("Cannot mmap shm registry segment (%ld)", GetLastError());
		jack_remove_shm (registry_id);
		CloseHandle (shm_fd);
		return EINVAL;
	}

	/* set up global pointers */
	ri->index = JACK_SHM_REGISTRY_INDEX;
	jack_shm_header = ri->ptr.attached_at;
	jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);

	//CloseHandle(shm_fd);  // TO CHECK
	return 0;
}

static int
jack_create_registry (jack_shm_info_t *ri)
{
	/* registry must be locked */
	HANDLE shm_fd;

	if ((shm_fd = CreateFileMapping(INVALID_HANDLE_VALUE,
									0, PAGE_READWRITE,
									0, JACK_SHM_REGISTRY_SIZE,
									registry_id)) == NULL || (shm_fd == INVALID_HANDLE_VALUE)) {
		int rc = GetLastError();
		jack_error ("Cannot create shm registry segment (%ld)", rc);
		return rc;
	}

	if ((ri->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, JACK_SHM_REGISTRY_SIZE)) == NULL) {
		jack_error ("Cannot mmap shm registry segment (%ld)", GetLastError());
		jack_remove_shm (registry_id);
		CloseHandle (shm_fd);
		return EINVAL;
	}

	/* set up global pointers */
	ri->index = JACK_SHM_REGISTRY_INDEX;
	jack_shm_header = ri->ptr.attached_at;
	jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);

	/* initialize registry contents */
	jack_shm_init_registry ();

	//CloseHandle(shm_fd); // TO CHECK
	return 0;
}

static void
jack_remove_shm (const jack_shm_id_t id)
{
	/* nothing to do */
}

void
jack_release_shm (jack_shm_info_t* si)
{
	/* registry may or may not be locked */
	if (si->ptr.attached_at != NULL) {
		UnmapViewOfFile (si->ptr.attached_at);
	}
}

void
jack_release_lib_shm (jack_shm_info_t* si)
{
	jack_release_shm(si);
}

int
jack_shmalloc (const char *shm_name, jack_shmsize_t size, jack_shm_info_t* si)
{
	jack_shm_registry_t* registry;
	HANDLE shm_fd;
	int rc = -1;
	char name[SHM_NAME_MAX+1];

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	if ((registry = jack_get_free_shm_info ()) == NULL) {
		jack_error ("shm registry full");
		goto unlock;
	}

	snprintf (name, sizeof (name), "jack-%d-%d", GetUID(), registry->index);

	if (strlen (name) >= sizeof (registry->id)) {
		jack_error ("shm segment name too long %s", name);
		goto unlock;
	}

	if ((shm_fd = CreateFileMapping(INVALID_HANDLE_VALUE,
									0, PAGE_READWRITE,
									0, size,
									name)) == NULL || (shm_fd == INVALID_HANDLE_VALUE)) {
		int rc = GetLastError();
		jack_error ("Cannot create shm segment (%ld)",rc);
		goto unlock;
	}

	//CloseHandle (shm_fd); // TO CHECK

	registry->size = size;
	strncpy (registry->id, name, sizeof (registry->id));
	registry->allocator = _getpid();
	si->index = registry->index;
	si->ptr.attached_at = NULL;	/* not attached */
	rc = 0;				/* success */

 unlock:
	jack_shm_unlock_registry ();
	return rc;
}

int
jack_attach_shm (jack_shm_info_t* si)
{
	HANDLE shm_fd;
	jack_shm_registry_t *registry = &jack_shm_registry[si->index];

	if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry->id)) == NULL) {
		int rc = GetLastError();
		jack_error ("Cannot open shm segment (%ld)",rc);
		return -1;
	}

	if ((si->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, registry->size)) == NULL) {
		jack_error ("Cannot mmap shm segment (%ld)", GetLastError());
		jack_remove_shm (registry_id);
		CloseHandle (shm_fd);
		return -1;
	}

	//CloseHandle (shm_fd);  // TO CHECK
	return 0;
}

int
jack_attach_shm_read (jack_shm_info_t* si)
{
	HANDLE shm_fd;
	jack_shm_registry_t *registry = &jack_shm_registry[si->index];

	if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry->id)) == NULL) {
		int rc = GetLastError();
		jack_error ("Cannot open shm segment (%ld)",rc);
		return -1;
	}

	if ((si->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_READ, 0, 0, registry->size)) == NULL) {
		jack_error("Cannot mmap shm segment (%ld)", GetLastError());
		jack_remove_shm(registry_id);
		CloseHandle(shm_fd);
		return -1;
	}

	//CloseHandle (shm_fd); // TO CHECK
	return 0;
}

#else

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * System V interface-dependent functions
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* gain addressability to existing SHM registry segment
 *
 * sets up global registry pointers, if successful
 *
 * returns: 0 if existing registry accessed successfully
 *          ENOENT if registry does not exist
 *          EINVAL if registry exists, but has the wrong size
 *          other nonzero error code if unable to access registry
 */
static int
jack_access_registry (jack_shm_info_t *ri)
{
	/* registry must be locked */

	/* try without IPC_CREAT to get existing segment */
	if ((registry_id = shmget (JACK_SHM_REGISTRY_KEY,
				   JACK_SHM_REGISTRY_SIZE, 0666)) < 0) {

		switch (errno) {

		case ENOENT:		/* segment does not exist */
			return ENOENT;

		case EINVAL:		/* segment exists, but too small */
			/* attempt minimum size access */
			registry_id = shmget (JACK_SHM_REGISTRY_KEY, 1, 0666);
			return EINVAL;

		default:		/* or other error */
			jack_error ("unable to access shm registry (%s)",
				    strerror (errno));
			return errno;
		}
	}

	if ((ri->ptr.attached_at = shmat (registry_id, 0, 0)) < 0) {
		jack_error ("Cannot attach shm registry segment (%s)",
			    strerror (errno));
		return EINVAL;
	}

	/* set up global pointers */
	ri->index = JACK_SHM_REGISTRY_INDEX;
	jack_shm_header = ri->ptr.attached_at;
	jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);
	return 0;
}

/* create a new SHM registry segment
 *
 * sets up global registry pointers, if successful
 *
 * returns: 0 if registry created successfully
 *          nonzero error code if unable to allocate a new registry
 */
static int
jack_create_registry (jack_shm_info_t *ri)
{
	/* registry must be locked */
	if ((registry_id = shmget (JACK_SHM_REGISTRY_KEY,
				   JACK_SHM_REGISTRY_SIZE,
				   0666|IPC_CREAT)) < 0) {
		jack_error ("Cannot create shm registry segment (%s)",
			    strerror (errno));
		return errno;
	}

	if ((ri->ptr.attached_at = shmat (registry_id, 0, 0)) < 0) {
		jack_error ("Cannot attach shm registry segment (%s)",
			    strerror (errno));
		return EINVAL;
	}

	/* set up global pointers */
	ri->index = JACK_SHM_REGISTRY_INDEX;
	jack_shm_header = ri->ptr.attached_at;
	jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1);

	/* initialize registry contents */
	jack_shm_init_registry ();
	return 0;
}

static void
jack_remove_shm (jack_shm_id_t id)
{
	/* registry may or may not be locked */
	shmctl (id, IPC_RMID, NULL);
}

void
jack_release_shm (jack_shm_info_t* si)
{
	/* registry may or may not be locked */
	if (si->ptr.attached_at != MAP_FAILED) {
		shmdt (si->ptr.attached_at);
	}
}

void
jack_release_lib_shm (jack_shm_info_t* si)
{
	jack_release_shm(si);
}

int
jack_shmalloc (const char* name_not_used, jack_shmsize_t size,
	       jack_shm_info_t* si)
{
	int shmflags;
	int shmid;
	int rc = -1;
	jack_shm_registry_t* registry;

	if (jack_shm_lock_registry () < 0) {
        jack_error ("jack_shm_lock_registry fails...");
        return -1;
    }

	if ((registry = jack_get_free_shm_info ())) {

		shmflags = 0666 | IPC_CREAT | IPC_EXCL;

		if ((shmid = shmget (IPC_PRIVATE, size, shmflags)) >= 0) {

			registry->size = size;
			registry->id = shmid;
			registry->allocator = getpid();
			si->index = registry->index;
			si->ptr.attached_at = MAP_FAILED; /* not attached */
			rc = 0;

		} else {
			jack_error ("Cannot create shm segment %s (%s)",
				    name_not_used, strerror (errno));
		}
	}

	jack_shm_unlock_registry ();
	return rc;
}

int
jack_attach_shm (jack_shm_info_t* si)
{
	if ((si->ptr.attached_at = shmat (jack_shm_registry[si->index].id, 0, 0)) < 0) {
		jack_error ("Cannot attach shm segment (%s)",
			    strerror (errno));
		jack_release_shm_info (si->index);
		return -1;
	}
	return 0;
}

int
jack_attach_shm_read (jack_shm_info_t* si)
{
	if ((si->ptr.attached_at = shmat (jack_shm_registry[si->index].id, 0, SHM_RDONLY)) < 0) {
		jack_error ("Cannot attach shm segment (%s)",
			    strerror (errno));
		jack_release_shm_info (si->index);
		return -1;
	}
	return 0;
}

#endif /* !USE_POSIX_SHM */

