/* **********************************************************************
 * *	                                                                *
 * *             Routines to Create/Manage Shared Memory                *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/shmem.c
 *
 *           COPYRIGHT (c) 1994, 1996, 1998, 1999, 2002  BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 *
 *   sig_shmem_map()       Create (or map to existing) shared segment
 *   sig_shmem_unmap()     Delete a shared segment
 *   lshmem_inuse()        Verify that a shared segment is unused
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>

#include "sigtypes.h"
#include "user_lib.h"

/* ======================================================================
 * Create (or map to an existing) shared memory segment.
 *
 * Entry Parameters
 *   DESSIZE_A   Desired size in bytes, or NULL for attaching to the full
 *               size of a currently mapped section.
 *   SNAME_A     Null-terminated unique name for the area.  On UNIX
 *               platforms there must exist a file: IRIS_KEYS//SNAME_A
 *               in order for the shared section to be created.
 *   WRITE_A     Flag to permit writing
 *
 * Exit Parameters
 *   Return Status
 *   ACTADDR_A   Actual address that was mapped
 *   ACTSIZE_A   Actual size in bytes
 *   IDEN_A      Identifier for the segmemt
 *  LCREATED_A   Non zero if the segment was just now created
 */
MESSAGE sig_shmem_map
  (UINT4   dessize_a,
   const char*   sname_a,
   UINT2   lwrite_a,
   void**  actaddr_a,
   SINT4*  actsize_a,
   SINT4*  iden_a,
   UINT2*  lcreated_a)
{
  MESSAGE istatus ;

  key_t key ; int shmid, shmflag ; void *addr ;

#define ACCESS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP )

  *lcreated_a = FALSE;
  istatus = sig_ftok( sname_a, (SINT4 *)&key ) ;

  if( istatus == SS_NORMAL ) {
    shmid = shmget( key, dessize_a, IPC_CREAT | ACCESS ) ;
    if( shmid == -1 ) {
      istatus = errno_to_message_va( errno, 3, "shmget %s (%d bytes)", sname_a,
				     dessize_a );

    } else {
      if( lwrite_a ) shmflag = 0 ; else shmflag = SHM_RDONLY ;
      addr = (void *)shmat( shmid, NULL, shmflag) ;
      if( addr == (void *)-1 ) {
	istatus = errno_to_message_va( errno, 2, "sig_shmem_map(%s)", sname_a ) ;

      } else {
	struct shmid_ds shmds ; memset( &shmds, 0, sizeof(shmds) ) ;
	if( shmctl( shmid, IPC_STAT, &shmds )  == -1 ) {
	  istatus = errno_to_message_va( errno, 2, "sig_shmem_map(%s)", sname_a ) ;

	} else {
	  *actaddr_a = addr ;
	  *actsize_a = shmds.shm_segsz ;
	  *iden_a    = shmid ;
	  if( shmds.shm_nattch < 2 ) {
	    *lcreated_a = TRUE;
	    if( lwrite_a ) memset( addr, 0, shmds.shm_segsz ) ;
	  }
	  istatus = SS_NORMAL;
	}
      }
    }
  }
  
  return istatus ;
}

/* ======================================================================
 * Delete a shared memory segment.  If we are the last process using the
 * segment, then the memory itself will be deallocated and all contents
 * lost.
 *
 * Entry Parameters
 *   ADDR_A   Address     \
 *   SIZE_A   Size         > From sig_shm_create()
 *   IDEN_A   Identifier  /
 *
 * Exit Parameters
 *   Return Status
*/
MESSAGE sig_shmem_unmap
  (volatile void*   addr_a,
   SINT4 size_a,
   SINT4 iden_a )
{
  MESSAGE iStatus;
  struct shmid_ds shmds ;
  
#ifdef LINUX
  /* On LINUX SMP systems this function will crash the kernel if run twice
   * at the same time.  Therefore we lock.
   */
  char *sptr ; char sLockFile[PATHNAME_SIZE] ; 
  int fildes;

  sptr = getenv( "IRIS_KEYS" ) ;
  if( sptr == NULL ) 
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_KEYS" );
      return USER_ERROR_SIG;
    }

  strncpy( sLockFile, sptr, sizeof(sLockFile)-20 ) ;
  strncat( sLockFile, "sig_shmem_unmap", 20 ) ;
  fildes = open( sLockFile, O_RDWR, 0 ) ;
  if( fildes < 0 ) {
    iStatus = errno_to_message_va( errno, 1, sLockFile ) ;
    return( iStatus ) ;
  }
  
  if( lockf( fildes, F_LOCK, 0 ) ) sig_signal( errno_to_message( errno ) );
#endif  /* #ifdef LINUX */

  memset( &shmds, 0, sizeof(shmds) ) ;
  iStatus = SS_NORMAL;


  /* There is a potential race condition here of two processes trying
   * to remove shared memory at the same time.  They may both detach,
   * then one does the IPC_STAT, and the IPC_RMID.  The second one
   * does the IPC_STAT and gets an invalid argument error.  To prevent
   * this problem, we will supress the EINVAL error on those two calls.
   * However, this also means that we will not detect a valid error if
   * the user passes in an invalid ID.  To detect this, we do an 
   * extra call first.
   */

  /* First check if it is a valid ID
   */
  if( shmctl( iden_a, IPC_STAT, &shmds ) == -1 )
    {
      iStatus = errno_to_message_va( errno, 1, "sig_shmem_unmap()" ) ;
      goto ERROR;
    }

  /* Next detach the virtual addresses
   */
  if( shmdt( (void*)addr_a ) == -1 )
    {
      iStatus = errno_to_message_va( errno, 1, "sig_shmem_unmap()" ) ;
      goto ERROR;
    }
      
  /* Get count of number of users.
   * Ignore EINVAL error, since some other process may be doing the same thing.
   */
  if( shmctl( iden_a, IPC_STAT, &shmds ) == -1 )
    if (errno != EINVAL)
      {
        iStatus = errno_to_message_va( errno, 1, "sig_shmem_unmap()" ) ;
	goto ERROR;
      }
  /* If the segment is no longer in use, then get rid of it for good.
   * Ignore EINVAL errors, since some other process may be doing the same thing.
   */
  if( shmds.shm_nattch == 0 )
    if ( shmctl( iden_a, IPC_RMID, &shmds ) == -1)
      if (errno != EINVAL)
	{
	  iStatus = errno_to_message_va( errno, 1, "sig_shmem_unmap()" ) ;
	  goto ERROR;
	}

 ERROR:

#ifdef LINUX
  /* Unlock */
  if ( lockf( fildes, F_ULOCK, 0 ) ) sig_signal( errno_to_message( errno ) );
  if ( close( fildes ) )             sig_signal( errno_to_message( errno ) );
#endif  /* #ifdef LINUX */

  return iStatus;
}

/* ======================================================================
 * Check that a shared memory segment is unused by any other processes.
 * As a side effect, free segments will be removed from the IPCS list.
 * Prints a diagnostic message and returns TRUE if the segment was in
 * use.
 */
UINT2 lshmem_inuse
(const char *segname_a,
 UINT2 lprint_a )
{
  UINT2 ireturn=FALSE ;
  void *iaddr ; SINT4 isize, iunit ; UINT2 lcreated ; MESSAGE istatus ;

  istatus = sig_shmem_map( 0, segname_a, 0, &iaddr, &isize, &iunit, &lcreated ) ;

  if( istatus != SS_NORMAL )
    {
      if( SIGBASE(istatus) != USER_ERRNO_EINVAL )
	{
	  if( lprint_a )
	    printf( "Problem checking shared memory '%s'\n", segname_a ) ;
	  sig_signal( istatus ) ;
	}
    
    }
  else
    {
      if( lcreated == 0 ) {
	if( lprint_a )
	  printf( "ERROR: shared memory '%s' (ID=%d, size=%d) is still in use...\n",
		 segname_a, iunit, isize ) ;
	ireturn = TRUE ;
      }
      istatus = sig_shmem_unmap( iaddr, isize, iunit ) ;
      if( istatus != SS_NORMAL ) sig_signal( istatus ) ;
    }
  
  return( ireturn ) ;
}

/* ======================================================================
 * Make key file name by appending the file name to the IRIS key environment
 * variable.  There must be a (empty) file there, in order to use the UNIX
 * key mechanism.  Note that the key_a variable is not declared as key_t
 * due to problems in making the prototype work for it.  Errors are signalled
 * internally.
 */
MESSAGE sig_ftok
(const char *sname_a, SINT4 *key_a )
{
  char *sptr ; char skeyfile[PATHNAME_SIZE] ; MESSAGE istatus ;

  sptr = getenv( "IRIS_KEYS" ) ;
  if( sptr == NULL ) 
    {
      sig_signal_va( USER_NO_ENV, 1, "IRIS_KEYS" );
      istatus = USER_ERROR_SIG;
    }

  strncpy( skeyfile, sptr, sizeof(skeyfile)-20 ) ;
  strncat( skeyfile, sname_a, 20 ) ;
  *key_a = (SINT4)ftok( skeyfile, 1 ) ;
  if( *key_a == -1 ) {
    sig_signal_va( USER_NO_FTOK, 1, skeyfile );	
    istatus = errno_to_message_va( errno, 2, "sig_ftok(%s)", sname_a ) ;
    
  } else {
    istatus = SS_NORMAL ;
  }
  
  return( istatus ) ;
}
