/* **********************************************************************
 * *	                                                               *
 * *             Routines to Perform Mapped I/O to Files                *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/mapio.c
 *
 *        COPYRIGHT (c) 1992, 1995, 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.
 *
 *   IMAPCREATE     : Create a file for mapped I/O
 *   IMAPOPEN       : Open an existing file for mapped I/O
 *   IMAPOPEN_SHORT : Like IMAPOPEN, except allows for short beginning map
 *   IMAPCLOSE      : Close a mapped file
 *   MAPGETTIME     : Return file revision time from last open or create
 *   MAPMMAP        : Use mapped files, or direct I/O
 *   MAPCACHE       : Set caching size of read-only files
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#define INCLUDE_SYSTIME_PROTOTYPES 1
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>

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

#include "user_def.h"

#if defined(IRIX) || defined(LINUX)
#define MMAPFLAGS MAP_SHARED
#else
#define MMAPFLAGS MAP_SHARED | MAP_FILE | MAP_VARIABLE
#endif

static struct stat statbuf_c ;  /* In common, for sharing file times */

/* The following variable controls whether we will use the UNIX mmap file
 * mapping service, or do direct file I/O.  We assume that file mapping is not
 * available at first.
 */
static SINT2 luse_mmap_c = FALSE ;

/* The following variables give the maximum size and the current size of the
 * read cache on UNIX platforms that are not using mapped files.
 */
static UINT4 cachemax_c=0, cachesz_c=0 ;

/* The following array keeps track of the mappings that are active at the
 * moment.  This information is used both for error checking, and to supply
 * an occasional otherwise-unknown value.
 */
#define NAMELEN 40
struct mapping {
  void *iaddr ; SINT4 isize, ichan ; UINT4 imaxsize, ibtime ;
  UINT1 lwrite, luse_mmap, lMapClosed, lChanClosed ; char sname[NAMELEN] ;
} ;
static struct mapping mappings_c[MAP_CACHE_MAX] = {{0},} ;

/* Local Function Prototypes.
 */
static struct mapping *find_mapping( const void *iaddr_a ) ;
static char * tailname( const char * sname_a ) ;
static void kill_cache_buffer( void ) ;

/* Routines to help with the mapping information
 */
static struct mapping *find_mapping( const void *iaddr_a )
{
  int ii ; struct mapping *pmapping ;
  for( ii=0 ; ii<MAP_CACHE_MAX ; ii++ ) {
    pmapping = &mappings_c[ii] ;
    if( pmapping->iaddr == iaddr_a ) return( pmapping ) ;
  }
  return( NULL ) ;
}

static char * tailname( const char * sname_a ) {
  int ii = strlen( sname_a ) - (NAMELEN-1) ;
  if( ii < 0 ) ii = 0 ; return( (char *)&sname_a[ii] ) ;
}

/* ======================================================================
 * Specify whether we are to use mapped files, or direct I/O.  This
 * decision remains in effect for all files that are subsequently
 * opened.  You can change your mind at any time.
 */
void mapmmap( UINT2 luse_mmap_a )
{
  luse_mmap_c = (luse_mmap_a != 0) ;
}

/* ======================================================================
 * MAPCACHE() specifies the maximum size of read caching for non mapped read
 * only files.  The size remains in effect for all files that are subsequently
 * closed.  You can change your mind at any time.  Decreasing the size will
 * result in deallocation of buffers for files that were closed and cached in
 * the past.
 */
static void kill_cache_buffer()
{
  SINT4 ii ; UINT4 imaxtime=0, ibasetime, itime ;
  struct mapping *pmapkill=NULL ;

  getbtime( &ibasetime ) ;
  for( ii=0 ; ii<MAP_CACHE_MAX ; ii++ ) {
    struct mapping *pmapping = &mappings_c[ii] ;
    if( 0 != pmapping->iaddr  &&  pmapping->lMapClosed == TRUE ) {
      itime = btimeGrace( ibasetime - pmapping->ibtime ) ;
      if( itime > imaxtime ) { imaxtime=itime ; pmapkill=pmapping ; }
    }
  }
  if( pmapkill ) 
    {
      MESSAGE istatus;
      istatus = sig_free( pmapkill->iaddr ) ;
      if (istatus != SS_NORMAL)
	{
	  istatus = iMessageMod_va( istatus, 1, "kill_cache_buffer" );
	  sig_signal( istatus );
	}
      else
	{
	  pmapkill->iaddr = NULL ;
	  cachesz_c -= pmapkill->isize ;
	}
    }
}

void mapcache( UINT4 cachesz_a ) {
  cachemax_c = cachesz_a ;
  while( cachesz_c > cachemax_c ) kill_cache_buffer() ;
}

/* ======================================================================
 * Open and map to an existing file.  The access can be either read or
 * read/write.
 *
 * Entry Parameters
 *   SNAME_A  : Disk name of file to be opened
 *   LWRITE_A : Indicates whether section can be written to
 *
 * Exit Parameters
 *   RETURN   : Error Status
 *   IADDR_A  : Starting virtual address file section
 *   ISIZE_A  : Size of file section in bytes
 *   ICHAN_A  : I/O Channel Number.
 *
 * For IMAPOPEN_SHORT, the additional entry parameter IMAXSIZE_A represents an
 * upper bound on the size of mapped section.  If the file is larger than this
 * amount, then only the initial contents will be mapped.  This is useful when
 * we know that were are opening the file with the intent of using only the
 * first IMAXSIZE_A bytes of it.
 */
#define MAXOPENSIZE 0xFFFFFFFF

UINT4 imapopen
  (const char* sname_a,
   UINT2 lwrite_a,
   void** iaddr_a,
   SINT4* isize_a,
   SINT4* ichan_a )
{
  return imapopen_short( sname_a, lwrite_a, iaddr_a, isize_a, ichan_a, MAXOPENSIZE ) ;
}

UINT4 imapopen_short
  (const char* sname_a,
   UINT2 lwrite_a,
   void** iaddr_a,
   SINT4* isize_a,
   SINT4* ichan_a,
   UINT4 imaxsize_a )
{
  UINT4 istatus=SS_NORMAL ; struct mapping *pmapping ;
  
  /* If we already have this file open (for reading only) from a previous
   * imapopen(), then simply return the existing virtual addresses.  If an
   * old version exists but can not be used, then deallocate the old one.
   */
  {
    SINT4 ii ; char *pname = tailname(sname_a) ;
    for( ii=0 ; ii<MAP_CACHE_MAX ; ii++ ) {
      pmapping = &mappings_c[ii] ;
      if( 0 != pmapping->iaddr  &&  0 == strcmp( pname, pmapping->sname ) &&
	 pmapping->lMapClosed == TRUE ) {

	/* For an old file to be usable, it must match the current read-only
	 * request, and the original open size limitation must match the
	 * current one.
	 */
	cachesz_c -= pmapping->isize ;
	if( lwrite_a == FALSE  &&  pmapping->lwrite == FALSE  &&
	   pmapping->imaxsize == imaxsize_a ) {

	  *iaddr_a = pmapping->iaddr ;
	  *isize_a = pmapping->isize ;
	  *ichan_a = pmapping->ichan ;
	  pmapping->lMapClosed = FALSE ; return( SS_NORMAL ) ;

	} else 
	  {
	    MESSAGE istatus2;
	    istatus2 = sig_free( pmapping->iaddr ) ; 
	    if (istatus2 != SS_NORMAL)
	      {
		istatus2 = iMessageMod_va( istatus2, 1, "imap_open" );
		sig_signal( istatus2 );
	      }
	    pmapping->iaddr = NULL ;
	  }
      }  
    }
  }

  pmapping = find_mapping(NULL) ;
  if( pmapping == NULL ) { kill_cache_buffer() ; pmapping = find_mapping(NULL) ; }
  if( pmapping == NULL ) istatus = USER_IMAPFULL ;
  else {
    int fildes, oflag ; UINT4 isize = 0 ;
    void *vptr = NULL ;
    
    if( lwrite_a ) oflag = O_RDWR ; else oflag = O_RDONLY ;
    fildes = open( sname_a, oflag ) ;
    if( fildes == -1 ) {
      istatus = errno_to_message_va( errno, 1, sname_a ) ;
    
    } else { 			/* File opened okay... */
      if( fstat( fildes, &statbuf_c ) == -1 ) {
	istatus = errno_to_message_va( errno, 1, sname_a ) ;
	if( -1 == close( fildes ) )
	  sig_signal( errno_to_message_va( errno, 1, sname_a ) ) ;

      } else {			/* Stats obtained on file... */
	UINT2 lRegularFile = S_ISREG( statbuf_c.st_mode ) ;
	UINT1 lChanClosed = FALSE ;

	/* If this is a regular file, then verify that the size is not
	 * zero.  Some types of special device files can also be
	 * mapped, but we insist that IMAXSIZE_A be properly specified
	 * since the inode's size will be meaningless.
	 */
	if( lRegularFile && (statbuf_c.st_size == 0) )
	  istatus = USER_ZLEN_FILE ;

	else if( (! lRegularFile ) && (imaxsize_a == MAXOPENSIZE) )
	  istatus = USER_USE_SHORT ;

	else if( (! lRegularFile ) && (! luse_mmap_c) )
	  istatus = USER_USE_MMAP ;

	else {			/* File size can be determined... */
	  if( lRegularFile ) isize = MIN( statbuf_c.st_size, imaxsize_a ) ;
	  else               isize = imaxsize_a ;

	  if( luse_mmap_c ) {
	    int prot = PROT_READ ; if( lwrite_a ) prot |= PROT_WRITE ;
	    vptr = mmap( 0, isize, prot, MMAPFLAGS, fildes, 0 ) ;
	    if( vptr == (void *)-1 )
	      istatus = errno_to_message_va( errno, 1, sname_a ) ;
	    
	  } else {		/* Do not use mmap... */
	    istatus = sig_malloc( isize, &vptr ) ;
	    if( istatus != SS_NORMAL )
	      istatus = iMessageMod_va( istatus, 1, "imap_open" ) ;
	    else {
	      if( isize != read( fildes, vptr, isize ) ) {
		MESSAGE istatus2 ;
		istatus = errno_to_message_va( errno, 1, sname_a ) ;
		istatus2 = sig_free( vptr ) ;
		if( istatus2 != SS_NORMAL ) {
		  istatus2 = iMessageMod_va( istatus2, 1, "imap_open" ) ;
		  sig_signal( istatus2 ) ;
		}
	      }
	    }
	  }
	}
	if( (istatus != SS_NORMAL) || 
	    ( lRegularFile && (luse_mmap_c || (lwrite_a == FALSE)) ) ) {
	  lChanClosed = TRUE ;
	  if( -1 == close( fildes ) )
	    sig_signal( errno_to_message_va( errno, 1, sname_a ) ) ;
	}

	if( istatus == SS_NORMAL ) {
	  *iaddr_a = vptr ;
	  *isize_a = isize ;
	  *ichan_a = fildes ;

	  pmapping->iaddr    = *iaddr_a ;
	  pmapping->isize    = *isize_a ;
	  pmapping->ichan    = *ichan_a ;
	  pmapping->lwrite   = lwrite_a ;
	  pmapping->imaxsize = imaxsize_a ;
	  pmapping->lMapClosed  = FALSE ;
	  pmapping->lChanClosed = lChanClosed ;
	  strcpy( pmapping->sname, tailname(sname_a) ) ;
	  pmapping->luse_mmap = luse_mmap_c ;
	}
      }
    }
  }
  return istatus ;
}

/* ======================================================================
 * Create and open a new file having a specified size.
 *
 * Entry Parameters
 *   SNAME_A    : Disk name of file to be opened
 *   IDESSIZE_A : Desired size of the file
 *
 * Exit Parameters
 *   RETURN   : Error Status
 *   IADDR_A  : Starting virtual address file section
 *   ISIZE_A  : Size of file section in bytes
 *   ICHAN_A  : I/O Channel Number.
 */
UINT4 imapcreate
  (const char* sname_a,
   SINT4 idessize_a,   /* Desired size of open */
   void** iaddr_a,
   SINT4* isize_a,
   SINT4* ichan_a )
{
  UINT4 istatus=SS_NORMAL ; struct mapping *pmapping ;
  
  pmapping = find_mapping( NULL ) ;
  if( pmapping == NULL ) { kill_cache_buffer() ; pmapping = find_mapping(NULL) ; }
  if( pmapping == NULL ) istatus = USER_IMAPFULL ;
  else {
    int fildes, oflag, mode ;
    void *vptr = NULL ;
    
    oflag = O_CREAT | O_EXCL | O_RDWR ;
    mode  = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ;
    fildes = open( sname_a, oflag, mode ) ;
    if( fildes == -1 )
      istatus = errno_to_message_va( errno, 1, sname_a ) ;
    
    else {		/* File opened okay... */
      if( ftruncate( fildes, idessize_a ) == -1 ) {
	istatus = errno_to_message_va( errno, 1, sname_a ) ;
	if( -1 == close( fildes ) )
	  sig_signal( errno_to_message_va( errno, 1, sname_a ) ) ;

      } else {
	if( fstat( fildes, &statbuf_c ) == -1 ) {
	  istatus = errno_to_message_va( errno, 1, sname_a ) ;
	  if( -1 == close( fildes ) )
	    sig_signal( errno_to_message_va( errno, 1, sname_a ) ) ;
	    
	} else {		/* Stats obtained on file... */
	  UINT1 lChanClosed = FALSE ;

	  if( luse_mmap_c ) {	/* Use mmap... */
	    int prot  = PROT_READ | PROT_WRITE ;
	    vptr = mmap( 0, statbuf_c.st_size, prot, MMAPFLAGS, fildes, 0 ) ;
	    if( (int)vptr == -1 ) {
	      istatus = errno_to_message_va( errno, 1, sname_a ) ;
	    }
	    if( -1 == close( fildes ) )
	      sig_signal( errno_to_message_va( errno, 1, sname_a ) ) ;
	    lChanClosed = TRUE ;
	      
	  } else {		/* Do not use mmap... */
	    istatus = sig_malloc( statbuf_c.st_size, &vptr ) ;
	    if( istatus != SS_NORMAL )
	      istatus = iMessageMod_va( istatus, 1, "imap_create" ) ;
	    else
	      memset( vptr, 0, statbuf_c.st_size );
	  }

	  if( istatus == SS_NORMAL ) {
	    *iaddr_a = vptr ;
	    *isize_a = statbuf_c.st_size ;
	    *ichan_a = fildes ;

	    pmapping->iaddr    = *iaddr_a ;
	    pmapping->isize    = *isize_a ;
	    pmapping->ichan    = *ichan_a ;
	    pmapping->lwrite   = TRUE ;
	    pmapping->imaxsize = MAXOPENSIZE ;
	    pmapping->lMapClosed  = FALSE ;
	    pmapping->lChanClosed = lChanClosed ;
	    strcpy( pmapping->sname, tailname(sname_a) ) ;
	    pmapping->luse_mmap = luse_mmap_c ;
	  }
	}
      }
    }
  }
  return istatus ;
}

/* ======================================================================
 * Close a mapped I/O file
 *
 * Entry Parameters
 *    IADDR_A  : Address of virtual section
 *    ISIZE_A  : Size of section in bytes
 *    ICHAN_A  : Channel number for I/O
 */
UINT4 imapclose
  (const void *iaddr_a,
   SINT4 isize_a,
   SINT4 ichan_a )
{
  UINT4 istatus=SS_NORMAL ; struct mapping *pmapping ;

  pmapping = find_mapping( iaddr_a ) ;
  if( pmapping == NULL || pmapping->lMapClosed == TRUE ) istatus = USER_IMAPADDR ;
  else {
    if( pmapping->isize != isize_a ) istatus = USER_IMAPSIZE ;
    else {
      if( pmapping->ichan != ichan_a ) istatus = USER_IMAPCHAN ;
      else {

	if( pmapping->luse_mmap ) {
	  if( munmap((caddr_t) iaddr_a, isize_a ) == -1 ) 
	    istatus = errno_to_message_va( errno, 1, "imapclose()" ) ;
	  if( ! pmapping->lChanClosed ) {
	    if( -1 == close( ichan_a ) )
	      sig_signal( errno_to_message_va( errno, 1, "imapclose()" ) ) ;
	  }
	  pmapping->iaddr = NULL ;

	} else {		/* Do not use mmap... */
	  if( pmapping->lwrite ) {
	    MESSAGE istatus2;
	    if( -1 == lseek( ichan_a, 0, SEEK_SET ) )
	      istatus = errno_to_message_va( errno, 1, "imapclose()" ) ;
	    else {
	      if( isize_a != write( ichan_a, iaddr_a, isize_a ) )
		istatus = errno_to_message_va( errno, 1, "imapclose()" ) ;
	    }
	    if( -1 == close( ichan_a ) )
	      if( istatus == SS_NORMAL )
		istatus = errno_to_message_va( errno, 1, "imapclose()" ) ;

	    istatus2 = sig_free( pmapping->iaddr ) ;
	    if (istatus2 != SS_NORMAL)
	      {
		istatus2 = iMessageMod_va( istatus2, 1, "imapclose" );
		sig_signal( istatus2 );
	      }
	    pmapping->iaddr = NULL ;

	  } else {
	    pmapping->lMapClosed = TRUE ; cachesz_c += pmapping->isize ;
	    getbtime( &pmapping->ibtime ) ;
	    while( cachesz_c > cachemax_c ) kill_cache_buffer() ;
	  }
	}
      }
    }
  }
  return istatus;
}

/* ======================================================================
 *  Return last-revised time for the file that was last opened using either
 *  imapopen() or imapcreate().
 */
void MapGetYmds
  (struct ymds_time *pYmds_a,
   int iFlags_a )
{
  YmdsFromTime_t( pYmds_a, statbuf_c.st_mtime, 0, iFlags_a ) ;
}
