/****************************************************************************/
/* Copyright (c) 2000 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  :                                                               */
/* Filename : SharedData.cc                                                 */
/* Author   :                                                               */
/* Project  :                                                               */
/* Version  : 1.0                                                           */
/* Created  : 02/07/2000                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include "SharedData.h"
#include "Syslog.h"

SharedData::SharedData(const char *name, 
		       int nDataBytes,
		       SharedData::Access access,
		       Boolean init)
{
  // Appears to be some problems with access to QNX shared 
  // memory. So we always set it to ReadWrite for now.
  access = SharedData::ReadWrite;

  _name = _protectSemName = _notifySemName = 0;
  Boolean debug = False;

  _name = strdup(name);


  if (init) {
    // Remove old shared memory segment if it exists
    shm_unlink(_name);
  }

  dprintf("sem_init()\n");

  _access = NoAccess;
  _mapAddr = 0;
  _protectSem = _notifySem = 0;
  _mapSize = nDataBytes;

  // Initialize shared semaphore
  initSemaphore();

  // Set access and map shared data
  setAccess(access);

  dprintf("_mapAddr = x%0x\n", _mapAddr);

  signal(SIGHUP, SIG_IGN);
}


SharedData::~SharedData()
{
  Boolean debug = True;
  dprintf("**** SharedData destructor for object %s ****\n", name());
  cleanup();
}


void SharedData::cleanup()
{
  free((void *)_name);
  free((void *)_protectSemName);
  free((void *)_notifySemName);
  sem_destroy(_protectSem);
  sem_destroy(_notifySem);

  munmap(_protectSem, sizeof(sem_t));
  shm_unlink(_protectSemName);
  munmap(_notifySem, sizeof(sem_t));
  shm_unlink(_notifySemName);
  munmap(_mapAddr, _mapSize);
  shm_unlink(_name);
}


void SharedData::initSemaphore()
{
  char semName[MaxSharedObjNameLen];

  sprintf(semName, "%s_prot", name());
  _protectSemName = strdup(semName);
  map(semName, (void **)&_protectSem, sizeof(sem_t), ReadWrite);
  if (sem_init(_protectSem, 1, 1) == -1) {
    perror("SharedData::initSemaphore() - sem_init() of _protectSem"); 
    exit(1);
  }

  sprintf(semName, "%s_notify", name());
  _notifySemName = strdup(semName);
  map(semName, (void **)&_notifySem, sizeof(sem_t), ReadWrite);
  if (sem_init(_notifySem, 1, 1) == -1) {
    perror("SharedData::initSemaphore() - sem_init() of _notifySem"); 
    exit(1);
  }
}


void SharedData::setAccess(Access access)
{
  Boolean debug = False;
  _access = access;

  // Re-map with specified access
  map(_name, &_mapAddr, _mapSize, _access);

  dprintf("done.\n");
}


void SharedData::map(const char *mapName, void **mapAddr, int mapSize,
		     Access access)
{
  Boolean debug = False;

  if (*mapAddr) {
    // Unmap existing mapping
    dprintf("ShareData::map() - munmap()\n");
    munmap(*mapAddr, mapSize);
  }

  int shmFlags = O_CREAT;

  int mmapProtect;

  switch (access) {
  case Read:
    shmFlags |= O_RDONLY;
    mmapProtect = PROT_READ;
    break;

  case Write:
    shmFlags |= O_RDWR;
    mmapProtect = PROT_READ | PROT_WRITE;
    break;

  case ReadWrite:
    shmFlags |= O_RDWR;
    mmapProtect = PROT_READ | PROT_WRITE;
    break;

  case NoAccess:
    mmapProtect = PROT_NONE;
    break;
  }

  // Get shared-memory file descriptor

  // TEST TEST TEST
  umask(0);

  int fd;
  dprintf("shm_open(): %s, flags=%d\n", mapName, shmFlags);
  if ((fd = shm_open(mapName, shmFlags, 0777)) == -1) {
    perror("SharedData::map() - shm_open()");
    cleanup();
    exit(1);
  }

  // Set size of shared memory segment
  if (ltrunc(fd, mapSize, SEEK_SET) == -1) {

    /* If errno is EBUSY, it means someone else has already set the
       size, and that's fine. Otherwise, there's a serious problem. */
    if (errno != EBUSY) {
      perror("SharedData::map() - ltrunc()");
      cleanup();
      exit(1);
    }
  }

  dprintf("mmap(): mapSize=%d, mmapProtect=%d, fd=%d\n",
	  mapSize, mmapProtect, fd);

  // Map the shared memory
  *mapAddr = mmap(0, mapSize, mmapProtect, MAP_SHARED, fd, 0);

  if (*mapAddr == (void *)-1) {
    Syslog::write("SharedData::map() - errno=%d for map %s\n", 
		  errno, mapName);

    perror("SharedData::map() - mmap()");
    cleanup();
    exit(1);
  }
  else {
    dprintf("mmap() successful for %s\n", mapName);
  }

  // Shared memory object remains after closing file descriptor
  close(fd);
}



void SharedData::read(void *data)
{
  Boolean debug = False;

  if (_access != Read && _access != ReadWrite) {
    // No permission to read; throw an exception here
    throw AccessError("SharedData::read()");
  }

  // Get semaphore (wait for other readers/writers)
  sem_wait(_protectSem);

  dprintf("SharedData::read() - memcpy()\n");
  // Read data from shared memory
  memcpy(data, _mapAddr, _mapSize);
  dprintf("SharedData::read() - done with memcpy()\n");

  // Release semaphore
  sem_post(_protectSem);
}


void SharedData::write(void *data)
{
  if (_access != Write && _access != ReadWrite) {
    // No permission to write; throw an exception here
    throw AccessError("SharedData::write()");
  }

  // Get semaphore (wait for other readers/writers)
  sem_wait(_protectSem);

  // Copy data to shared memory
  memcpy(_mapAddr, data, _mapSize);

  // Release semaphore
  sem_post(_protectSem);
}



int SharedData::mmapProtectFlags(Access access)
{
  int mmapProtect = 0;

  switch (access) {
  case Read:
    mmapProtect = PROT_READ;
    break;

  case Write:
    mmapProtect = PROT_READ | PROT_WRITE;
    break;

  case ReadWrite:
    mmapProtect = PROT_READ | PROT_WRITE;
    break;

  default:
    mmapProtect = PROT_NONE;
  }

  return mmapProtect;
}
