/* **********************************************************************
 * *                                                                    *
 * *      Support Routines for Setup Limits and ASCII File Formats      *
 * *                                                                    *
 * **********************************************************************
 * File: limsubs.c
 *
 *                     COPYRIGHT (c) 2001, 2004 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.
 */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>

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

#define LIM_DEFINITIONS
#include "config_lib.h"

static MESSAGE ilim_getInText
( struct lim_inText *inText_a,
  void *file_a,
  SINT4 iOper_a ) ;        /* Operation to perform */

/* ==========================================
 * Simple bit scramble for encoding text
 */
static SINT4 iBitScramble( SINT4 iarg_a ) {
  SINT4 iVal = 0 ;
  if( iarg_a & 0x01 ) iVal |= 0x02 ;
  if( iarg_a & 0x02 ) iVal |= 0x08 ;
  if( iarg_a & 0x04 ) iVal |= 0x10 ;
  if( iarg_a & 0x08 ) iVal |= 0x04 ;
  if( iarg_a & 0x10 ) iVal |= 0x01 ;
  return( iVal ) ;
}

/* ==========================================
 * Top level entry point for limit checking routines.
 */
MESSAGE lim_toplevel
( const char *sname_a,		/* Name of structure */
  volatile void *item_a,	/* Pointer to structure */
  SINT4 ioper_a,		/* Operation to perform */
  void *file_a,			/* Diagnostic messages, or NULL */
  MESSAGE (*lim_kernel_a)	/* Case handler function */
  ( const char *sname_a, void *item_a, SINT4 ioper_a,
    const char *spath_a, void *ioarg_a ) )
{
  MESSAGE istatus ;

  switch( ioper_a ) {
  case SETUP_MINVAL:		/* Get minimum value */
  case SETUP_MAXVAL:		/* Get maximum value */
  case SETUP_DEFVAL:		/* Get default value */
  case SETUP_TEST:		/* Test structure for bounds */
  case SETUP_REPAIR:		/* Repair illegal structure entries */
  case SETUP_TYPEOUT:		/* Print only relevant contents + comments */
  case SETUP_TYPEALL:		/* Print entire contents + no comments */
    istatus = lim_kernel_a( sname_a, (void *)item_a, ioper_a, sname_a, file_a ) ;
    if( file_a ) fflush( (FILE *)file_a ) ;
    break ;

  case SETUP_READIN:		/* Readin contents of a structure */
  case SETUP_READIN_MEM:	/*   (Read from memory) */
  case SETUP_READIN_ERR:	/*   (Giving complete error reports) */
  case SETUP_READIN_MOD:	/*   (UNK + Only allow SLCLASS_IRISMOD elements) */
    {
      struct lim_inText inText ; SINT4 nLines=0 ;
      /* Get the first batch of lines and process them
       */
      istatus = ilim_getInText( &inText, file_a, ioper_a ) ;
      while( (istatus == SS_NORMAL) && (inText.nLines > 0) ) {
	nLines += inText.nLines ;
	istatus = lim_kernel_a( sname_a, (void *)item_a, ioper_a, sname_a, &inText ) ;
	if( istatus == SS_NORMAL ) {
	  /* If we are supposed to signal unknown element input
	   * errors, then check to make sure that every line was
	   * actually used.
	   */
	  if( (ioper_a==SETUP_READIN_ERR) ||
	      (ioper_a==SETUP_READIN_MOD) ){
	    SINT4 iLine ;
	    for( iLine=0 ; iLine < inText.nLines ; iLine++ ) {
	      if( ! inText.lines[iLine].lUsed ) {
		sig_signal( istatus = iMessageMod_va( CONFIG_LIM_BADELEMENT, 1,
						      inText.lines[iLine].sLine ) ) ;
		break ;
	      }
	    }
	  }
	  /* If we're still okay, then get another batch of input
	   * lines and keep going.
	   */
	  if( istatus == SS_NORMAL )
	    istatus = ilim_getInText( &inText, file_a, ioper_a ) ;
	}
      }
      /* Optionally signal an error if the input file was empty.
       */
      if( (ioper_a==SETUP_READIN_ERR) ||
	  (ioper_a==SETUP_READIN_MOD) ){
	if( nLines == 0 ) istatus = CONFIG_LIM_NULLFILE ;
      }
    }
    break ;

  default:
    istatus = iMessageMod_va( CONFIG_LIM_BADOP, 2, "%d", ioper_a ) ;
    break ;
  }
  return( istatus ) ;
}

/* ==========================================
 * Routine to replace text such as "[idim1]" with the actual
 * value of that dimension filled in as a value.
 */
char *slim_actual_item
( const char *spath_a,		/* Name of prefix portion */
  const char *sitem_a,		/* Name of the item (possibly dimensioned) */
  SINT4 idim1_a,		/* Possible dimensions */
  SINT4 idim2_a )
{
  static char spath[PATHNAME_SIZE] ; char sdim[10], sitem[PATHNAME_SIZE], *sptr ;

  strcpy( sitem, sitem_a ) ; strcpy( spath, spath_a ) ; strcat( spath, "." ) ;
  if( NULL == (sptr = strstr( sitem, "[idim1]" )) ) {
    strcat( spath, sitem ) ;
  } else {
    *sptr = 0 ; strcat( spath, sitem ) ;
    sprintf( sdim, "[%d]", idim1_a ) ; strcat( spath, sdim ) ;
    if( (sptr = strstr( sptr+1, "[idim2]" )) != NULL ) {
      sprintf( sdim, "[%d]", idim2_a ) ; strcat( spath, sdim ) ;
    }
  }
  return( spath ) ;
}

/* ==========================================
 * Perform the Test/Repair functions on a string.  The ISPRINT()
 * macro is used as the basis for accepting or rejecting
 * individual characters.  Once a bad character is encountered,
 * the rest of the string is considered invalid from that point.
 */
void lim_opTestRepairString
( char *sString_a,		/* String to be Tested/Repaired */
  SINT4 iLength_a,		/* Length of the string */
  FILE *file_a,			/* Where to write errors */
  const char *sItemName_a,  	/* Name of the structure element */
  UINT1 lRepair_a )		/* Test vs Repair */
{
  SINT4 ii, iError ; UINT1 lError = FALSE ;

  for( ii=0 ; ii < iLength_a ; ii++ ) {
    char ch = sString_a[ii] ;
    if( ch == '\0' ) break ;
    if( ! isprint(ch) ) { iError = ii ; lError = TRUE ; break ; }
  }
  if( lError ) {
    if( lRepair_a )
      for( ii=iError ; ii < iLength_a ; ii++ ) sString_a[ii] = '\0' ;

    if( file_a ) {
      fprintf( file_a, "%s = '", sItemName_a ) ;
      for( ii=0 ; ii < iError ; ii++ ) fprintf( file_a, "%c", sString_a[ii] ) ;
      fprintf( file_a, "'...\n" ) ;
    }
  }
}

/* ==========================================
 * Perform the SETUP_TYPEOUT and SETUP_TYPEALL functions on a string.
 * Non printable characters are replaced with '\nnn' notation, and the
 * backslash and quote characters are represented by '\\' and '\"'.
 */
void lim_opTypeoutString
( char *sString_a,		/* String to be typed */
  SINT4 iLength_a,		/* Length of the string */
  FILE *file_a,			/* Where to write */
  const char *sItemName_a,  	/* Name of the structure element */
  SINT4 iClass_a )		/* Classification of this element */
{
  SINT4 ii ;
  if( file_a ) {
    UINT1 lEncoded = (iClass_a & SLCLASS_ENCODED), keyCh='%' ;

    fprintf( (FILE *)file_a, "%s = \"", sItemName_a ) ;
    if( lEncoded ) fprintf( (FILE *)file_a, "\\177" ) ;

    for( ii=0 ; ii < iLength_a ; ii++ ) {
      UINT1 ch = (UINT1)sString_a[ii] ;
      if( ch == '\0'  ) break ;

      if( lEncoded ) {
	UINT1 encCh = 0xFF & (iBitScramble( keyCh ) + (SINT4)(0x7F & ch)) ;
	keyCh += ch ; ch = encCh ;
      }
      if     (  ch == '\\'                  ) fprintf( file_a, "\\\\" ) ;
      else if(  ch == '"'                   ) fprintf( file_a, "\\\"" ) ;
      else if( (ch & 0x80) || ! isprint(ch) ) fprintf( file_a, "\\%3.3o", ch ) ;
      else                                    fprintf( file_a, "%c", ch ) ;
    }
    fprintf( (FILE *)file_a, "\"\n" ) ;
  }
}

/* ==========================================
 * Perform the SETUP_READIN function on a string, using the same
 * conventions for non printable characters as above.
 */
void lim_opReadinString
( char *sString_a,		/* String to be filled in */
  SINT4 iLength_a,		/* Length of the string */
  struct lim_inLine **pLine_a,	/* Processed line of text */
  MESSAGE *iStatus_a,		/* Error status, only written on error */
  SINT4 ioper_a,		/* Type of READIN being done */
  SINT4 iClass_a )		/* Classification of this element */
{
  if( pLine_a ) {
    SINT4 index ; const char *sLine=(*pLine_a)->sLine, *sStart, *sField ;

    /* Set the "Used" flag for this line, but check whether it is
     * okay for us to actually use its contents.
     */
    (*pLine_a)->lUsed = TRUE ;
    if( (ioper_a == SETUP_READIN_MOD) && !(iClass_a & SLCLASS_IRISMOD) ) {
      *iStatus_a = iMessageMod_va( CONFIG_LIM_CANTMOD, 1, sLine ) ;
    } else {
      UINT1 lFirst=TRUE, lEncoded=FALSE, keyCh='%' ;
      /* Move to the start of the text following the element name,
       * skipping over whitespace and the '=' sign.  Keep track of
       * the start of the right side in case there's an error.
       */
      sField = &sLine[ 1 + strlen( sLine ) ] ;
      while( (*sField == '=') || isspace( *sField ) ) sField++ ;
      sStart = sField ; if( *sField == '"' ) sField++ ;

      /* Clear the entire string by default, and then scan the
       * input text to fill in as much as possible.
       */
      memset( (char *)sString_a, 0, iLength_a ) ;

      for( index=0 ; index < iLength_a ; ) {
	UINT1 ch = (UINT1)*sField++ ;
	if     ( ch == '\0' ) break ;
	else if( ch == '"'  ) break ;
	else if( ch == '\\' ) {
	  if     ( *sField == '\\' ) { ch = '\\' ; sField++ ; }
	  else if( *sField == '"'  ) { ch = '"'  ; sField++ ; }
	  else {
	    SINT4 iOctal ;
	    if( 1 == sscanf( sField, "%3o", &iOctal ) ) {
	      ch = (0xFF & iOctal) ; sField += 3 ;
	    } else {
	      *iStatus_a = iMessageMod_va( CONFIG_LIM_INTEXT, 1, sStart ) ;
	      break ;
	    }
	  }
	}
	/* Decrypt the character if it is an encoded field.  Look
	 * for (and skip over) the DEL at the beginning of the
	 * string, indicating that it was written with encoding.
	 * This gives us backward compatability for reading old
	 * files.
	 */
	if( lFirst ) {
	  lFirst = FALSE ;
	  if( (iClass_a & SLCLASS_ENCODED) && (ch == '\177') ) {
	    lEncoded = TRUE ; continue ;
	  }
	}
	if( lEncoded ) {
	  UINT1 decCh = 0x7F & ( ((SINT4)ch) - iBitScramble( keyCh ) ) ;
	  ch = decCh ; keyCh += ch ;
	}
	sString_a[ index++ ] = (char)ch ;
      }
    }
  }
}

/* ==========================================
 * Perform the SETUP_READIN function on a scalar, returning the
 * numeric value on the right side of an assignment string.
 */
FLT8 flim_opReadinScalar
( FLT8 fValue_a,		/* Pre-existing value */
  FLT8 fMin_a, FLT8 fMax_a,	/* Bounds */
  const char *sItemName_a, 	/* Name of the structure element */
  struct lim_inLine **pLine_a,	/* Processed line of text */
  MESSAGE *iStatus_a,		/* Error status, only written on error */
  SINT4 ioper_a,		/* Type of READIN being done */
  SINT4 iClass_a ) 		/* Classification of this element */
{
  FLT8 fValueRet = fValue_a ;
  /* Return the pre-existing value if no new element was found
   */
  if( pLine_a ) {
    const char *sLine=(*pLine_a)->sLine ;
    /* Set the "Used" flag for this line, but check whether it is
     * okay for us to actually use its contents.
     */
    (*pLine_a)->lUsed = TRUE ;
    if( (ioper_a == SETUP_READIN_MOD) && !(iClass_a & SLCLASS_IRISMOD) ) {
      *iStatus_a = iMessageMod_va( CONFIG_LIM_CANTMOD, 1, sLine ) ;

    } else {
      const char *sField ; char *sEndPtr ; FLT8 fValueTry ;
      /* Okay, try to extract a numeric value.  Move to the start
       * of the text following the element name, skipping over
       * whitespace and the '=' sign.
       */
      sField = &sLine[ 1 + strlen( sLine ) ] ;
      while( (*sField == '=') || isspace( *sField ) ) sField++ ;

      fValueTry = strtod( sField, &sEndPtr ) ;
      if( sField == sEndPtr ) {
	*iStatus_a = iMessageMod_va( CONFIG_LIM_INTEXT, 1, sField ) ;

      } else {
	/* If we were able to parse a value, optionally check the
	 * bounds before returning it.
	 */
	if( (ioper_a==SETUP_READIN) || (ioper_a==SETUP_READIN_MEM)) {
	  fValueRet = fValueTry ;
	} else {
	  MESSAGE istatus = SS_NORMAL ;
	  lim_opTestScalar( fValueTry, fMin_a, fMax_a, sItemName_a, NULL, &istatus ) ;
	  if( istatus == SS_NORMAL ) fValueRet = fValueTry ;
	  else                      *iStatus_a = istatus ;
	}
      }
    }
  }
  return( fValueRet ) ;
}

/* ==========================================
 * Read lines of text from an input stream, and prepare a structure
 * that can then be searched efficiently to parse and insert those
 * lines.
 */
static MESSAGE ilim_getInText
( struct lim_inText *inText_a,
  void *file_a,
  SINT4 iOper_a )        /* Operation to perform */
{
  MESSAGE istatus = SS_NORMAL ;

  /* First read in a bunch of text lines into the fixed-size array.
   * It is fine not to read the entire file at this point.  Modify
   * each line so that there is a NULL at the first whitespace or '='
   * character, i.e., immediately after the structure element name.
   * The remainder of the line is still valid after this newly
   * inserted NULL.
   */
  inText_a->nLines = 0 ; errno = 0 ;

  while( inText_a->nLines < CFGINLINES ) {
    struct lim_inLine *pLine = &inText_a->lines[ inText_a->nLines ] ;
    char *sLine = pLine->sLine ;

    pLine->lUsed = FALSE ;	    /* Mark line's data as unused so far */
    sLine[ CFGLINELEN-1 ] = '\0' ;  /* Guarantees two NULLs at end */

    if( SETUP_READIN_MEM == iOper_a ) {
      char *pEnd ; SINT4 iLineSize, iCopySize ;
      struct lim_memory *pMemory = (struct lim_memory*)file_a ;

      if( pMemory->iSize <= 0 ) break ;
      pEnd = memchr( pMemory->sPointer, '\n', pMemory->iSize ) ;

      /* Copy data and advance the pointer by the size of the line
       */
      if (NULL == pEnd) iLineSize = pMemory->iSize ;
      else              iLineSize = (pEnd - pMemory->sPointer) + 1 ;

      iCopySize = MIN( iLineSize, CFGLINELEN-2 ) ;
      memcpy( sLine, pMemory->sPointer, iCopySize ) ; sLine[iCopySize]=0 ;
      pMemory->sPointer += iLineSize ;
      pMemory->iSize    -= iLineSize ;

    } else {
      if( 0 == fgets( sLine, CFGLINELEN-1, (FILE *)file_a ) ) {
	if( feof( (FILE *)file_a ) ) break ;
	if( errno ) istatus = errno_to_message_va( errno, 1, "setup_limits()" ) ;
	break ;
      }
    }
    /* Flush 'comment' lines, i.e., those that do not begin with a
     * graphical character, or those beginning with '#'.
     */
    if( isgraph( *sLine ) && (*sLine != '#') ) {
      while( *sLine ) {
	if( (*sLine == '=') || isspace( *sLine ) ) { *sLine = '\0' ; break ; } ;
	sLine++ ;
      }
      inText_a->nLines++ ;
    }
  }
  /* If the read went okay, then sort the lines so that they can be
   * binary searched later.  A permutation array is used so that
   * qsort() does not have to move whole character strings around.
   */
  if( istatus == SS_NORMAL ) {
    SINT4 iline ;
    for( iline=0 ; iline < inText_a->nLines ; iline++ ) {
      inText_a->perm[ iline ] = &inText_a->lines[ iline ] ;
    }
    qsort( inText_a->perm, inText_a->nLines, sizeof(void *), ilim_cmpInText ) ;
  }
  return( istatus ) ;
}

/* --------------------
 * Comparison function for input text lines.
 */
int ilim_cmpInText( const void *arg1_a, const void *arg2_a )
{
  return( strcmp( (*(struct lim_inLine **)arg1_a)->sLine,
		  (*(struct lim_inLine **)arg2_a)->sLine ) ) ;
}

/* ==========================================
 * Test a scalar value against min/max bounds.  Set error status and
 * print a message if there is a problem.
 */
void lim_opTestScalar
( FLT8 fValue_a,		/* Value to be tested */
  FLT8 fMin_a, FLT8 fMax_a,	/* Bounds */
  const char *sItemName_a, 	/* Name of the structure element */
  FILE *file_a,			/* Where to write */
  MESSAGE *iStatus_a )		/* Error status, only written on error */
{
  if( (fValue_a < fMin_a) || ( fValue_a > fMax_a) ) {
    *iStatus_a = iMessageMod_va( CONFIG_LIM_TESTVAL, 1, sItemName_a ) ;
    if( file_a ) fprintf( file_a, "%s = %G  (range %G to %G)\n",
			  sItemName_a, fValue_a, fMin_a, fMax_a ) ;
  }
}

/* ==========================================
 * Test a scalar value against min/max bounds.  Return original value
 * if it is within bounds.  Otherwise, return a valid value and print
 * a message showing what was changed.
 */
FLT8 flim_opRepairScalar
( FLT8 fValue_a,		/* Value to be tested */
  FLT8 fMin_a, FLT8 fMax_a,	/* Bounds */
  FLT8 fDef_a,			/* Default value */
  const char *sItemName_a, 	/* Name of the structure element */
  FILE *file_a )		/* Where to write */
{
  FLT8 fValue = fValue_a ;
  if( (fValue < fMin_a) || ( fValue > fMax_a) ) {
    FLT8 fNewValue = fDef_a ;
    if( (fMin_a == 0.0) && (fMax_a == 1.0) )
      fNewValue = (FLT8)( (fValue != 0.0) ? 1 : 0 ) ;
    if( file_a ) fprintf( file_a, "%s changed from %G to %G\n",
			  sItemName_a, fValue, fNewValue ) ;
    fValue = fNewValue ;
  }
  return( fValue ) ;
}

/* ==========================================
 * This subroutine reads saved values from a config file
 */
MESSAGE lim_loadFile
( const char *sRemoteHostname_a,
  const char *sFileName_a,
  const char *sStructName_a,
  volatile void *pStruct_a,
  LimitsProc func_limits_a )
{
  MESSAGE iStatus = lim_loadFileDir( sRemoteHostname_a, "IRIS_CONFIG", sFileName_a,
				     sStructName_a, pStruct_a, func_limits_a ) ;
  return( iStatus ) ;
}
/* ==========================================
 * This subroutine reads saved values from a config file
 */
MESSAGE lim_loadFileDir
( const char *sRemoteHostname_a,
  const char *sDirEnv_a,         /* Directory environment variable, or path */
  const char *sFileName_a,
  const char *sStructName_a,
  volatile void *pStruct_a,
  LimitsProc func_limits_a )
{
  UINT1 lremote_host, lDirPath ; 
  MESSAGE istatus ;
  FILE *stream ; char spath[PATHNAME_SIZE] ;

  /* Initialize to all defaults in case of an early error
   */     
  istatus = (*func_limits_a)( sStructName_a, pStruct_a, SETUP_DEFVAL, NULL ) ;
  if( istatus != SS_NORMAL ) return( istatus ) ;
      
  /* Determine if we have a remote host */
  if(      sRemoteHostname_a    == NULL ) lremote_host = FALSE ;
  else if( sRemoteHostname_a[0] == '\0' ) lremote_host = FALSE ;
  else                                    lremote_host = TRUE ;
    
  /* Determine if we have a directory path */
  switch (sDirEnv_a[0])
    {
    case '\0':
    case '.':
    case '/':
      lDirPath = TRUE;
      break;
    default:
      lDirPath = FALSE;
      break;
    }

  /* Translate environment variable and make the file name */
  if( lremote_host )
    {
      if (lDirPath)
	{
	  strncpy( spath, sDirEnv_a, DIRECTORY_SIZE ) ;
	  spath[DIRECTORY_SIZE] = '\0' ;
	  strcat ( spath, sFileName_a ) ;
	}
      else
	{
	  sprintf( spath, "${%s}%s", sDirEnv_a, sFileName_a ) ;
	}
      stream = sigrfopen( sRemoteHostname_a, spath, "r" ) ;
    } 
  else 
    {
      if (lDirPath)
	{
	  strncpy( spath, sDirEnv_a, DIRECTORY_SIZE ) ;
	}
      else
	{
	  char *sptr = getenv( sDirEnv_a ) ;
	  if( NULL == sptr ) {
	    return( iMessageMod_va( USER_NO_ENV, 1, sDirEnv_a ) ) ;
	  }
	  strncpy( spath, sptr, DIRECTORY_SIZE ) ;
	}
      spath[DIRECTORY_SIZE] = '\0' ;
      strcat ( spath, sFileName_a ) ;
      stream = fopen( spath, "r" ) ;
    }

  if( NULL == stream ) return errno_to_message_va( errno, 1, spath ) ;
  istatus = (*func_limits_a)( sStructName_a, pStruct_a, SETUP_READIN, stream ) ;

  if( istatus != SS_NORMAL )
    istatus = iMessageMod_va( istatus, 1, spath ) ;

  if( lremote_host ) sigrclose( stream ) ;
  else               fclose( stream ) ;

  return( istatus ) ;
}

/* ==========================================
 * This subroutine reads ASCII saved values from a memory buffer
 */
MESSAGE lim_loadMemory
( struct lim_memory *pMemory_a,
  const char *sStructName_a,
  volatile void *pStruct_a,
  LimitsProc func_limits_a )
{
  MESSAGE istatus ;

  /* Initialize to all defaults in case of an early error
   */     
  istatus = (*func_limits_a)( sStructName_a, pStruct_a, SETUP_DEFVAL, NULL ) ;
  if( istatus != SS_NORMAL ) return( istatus ) ;
      
  istatus = (*func_limits_a)( sStructName_a, pStruct_a, SETUP_READIN_MEM, pMemory_a ) ;

  if( istatus != SS_NORMAL ) istatus = iMessageMod_va( istatus, 1, sStructName_a ) ;

  return( istatus ) ;
}

/* ==========================================
 * This subroutine writes new values to a config file. 
 */
MESSAGE lim_saveFile
( const char *sRemoteHostname_a,
  const char *sFileName_a,
  const char *sStructName_a,
  volatile const void *pStruct_a,
  LimitsProc func_limits_a )
{
  MESSAGE iStatus=lim_saveFileDir( sRemoteHostname_a, "IRIS_CONFIG", sFileName_a,
				   sStructName_a, pStruct_a, func_limits_a ) ;
  return( iStatus ) ;
}

/* ==========================================
 * This subroutine writes new values to a config file, directory
 * environment variable specified.
 */
MESSAGE lim_saveFileDir
( const char *sRemoteHostname_a,
  const char *sDirEnv_a,           /* Directory environment variable */
  const char *sFileName_a,
  const char *sStructName_a,
  volatile const void *pStruct_a,
  LimitsProc func_limits_a )
{
  UINT1 lremote_host, lDirPath ; MESSAGE istatus ;
  FILE *stream ; char spath[PATHNAME_SIZE] ;

  /* Determine if we are accessing a remote host */
  if(      sRemoteHostname_a    == NULL ) lremote_host = FALSE ;
  else if( sRemoteHostname_a[0] == '\0' ) lremote_host = FALSE ;
  else                                    lremote_host = TRUE ;
  
  /* Determine if we have a directory path */
  switch (sDirEnv_a[0])
    {
    case '\0':
    case '.':
    case '/':
      lDirPath = TRUE;
      break;
    default:
      lDirPath = FALSE;
      break;
    }

  /* Translate environment variable, make the file name, and open
   * it for writing.
   */
  if( lremote_host )
    {
      if (lDirPath)
	{
	  strncpy( spath, sDirEnv_a, DIRECTORY_SIZE ) ;
	  spath[DIRECTORY_SIZE] = '\0' ;
	  strcat ( spath, sFileName_a ) ;
	}
      else
	{
	  sprintf( spath, "${%s}%s", sDirEnv_a, sFileName_a ) ;
	}
      stream = sigrfopen( sRemoteHostname_a, spath, "w" ) ;
    } 
  else
    {    
      if (lDirPath)
	{
	  strncpy( spath, sDirEnv_a, DIRECTORY_SIZE ) ;
	}
      else
	{
	  char *sptr = getenv( sDirEnv_a ) ;
	  if( NULL == sptr ) {
	    return( iMessageMod_va( USER_NO_ENV, 1, sDirEnv_a ) ) ;
	  }
	  strncpy( spath, sptr, DIRECTORY_SIZE ) ;
	}
      spath[DIRECTORY_SIZE] = '\0' ;
      strcat ( spath, sFileName_a ) ;
      stream = fopen( spath, "w" ) ;
    }

  if (NULL == stream) return errno_to_message_va( errno, 1, spath ) ;
  istatus = (*func_limits_a)( sStructName_a, (volatile void *)pStruct_a,
			      SETUP_TYPEOUT, stream ) ;

  if( istatus != SS_NORMAL )
    istatus = iMessageMod_va( istatus, 1, spath ) ;

  if( lremote_host ) sigrclose( stream ) ;
  else               fclose( stream ) ;

  return( istatus ) ;
}

/* ==========================================
 * Print a nicely formatted list showing the differences between two
 * structures.  The output format string is passed into this routine
 * and should contain three '%s' elements to print the structure name,
 * the old value, and the new value.
 */
MESSAGE lim_diffStructs
( volatile const void *pStructX_a,
  volatile const void *pStructY_a,
  const char *sStructName_a, LimitsProc func_limits_a,
  const char *sFormat_a, FILE *file_a )
{
  /* Open two temporary files that will hold the ASCII contents of
   * each structure being compared.
   */
  MESSAGE istatus = SS_NORMAL ; FILE *fileX = tmpfile() ;
  if( fileX == NULL ) {
    istatus = errno_to_message_va( errno, 1, "lim_diffStructs()" ) ;
  } else {
    FILE *fileY = tmpfile() ;
    if( fileY == NULL ) {
      istatus = errno_to_message_va( errno, 1, "lim_diffStructs()" ) ;
    } else {
      /* Temporary files were opened successfully.  Format the two
       * structures into their respective files.
       */
      istatus = (*func_limits_a)
	( sStructName_a, (volatile void *)pStructX_a, SETUP_TYPEALL, fileX ) ;
      if( istatus != SS_NORMAL ) sig_signal( istatus ) ;
      else {
	istatus = (*func_limits_a)
	  ( sStructName_a, (volatile void *)pStructY_a, SETUP_TYPEALL, fileY ) ;
	if( istatus != SS_NORMAL ) sig_signal( istatus ) ;
	else {
	  /* Rewind the two files, read them back line by line, and
	   * report any differences that we find.
	   */
	  char sLineX[ CFGLINELEN ], sLineY[ CFGLINELEN ] ;
	  rewind( fileX ) ; rewind( fileY ) ;
	  while( fgets( sLineX, CFGLINELEN, fileX ) &&
		 fgets( sLineY, CFGLINELEN, fileY ) ) {
	    if( strncmp( sLineX, sLineY, CFGLINELEN-1 ) ) {
	      char *pEqX = strstr( sLineX, " = " ) ;
	      char *pEqY = strstr( sLineY, " = " ) ;
	      if( pEqX && pEqY ) {
		sLineX[ strlen(sLineX) - 1 ] = '\0' ; *pEqX = '\0' ;
		sLineY[ strlen(sLineY) - 1 ] = '\0' ;
		fprintf( file_a, sFormat_a, sLineX, pEqX+3, pEqY+3 ) ;
	      }
	    }
	  }
	}
      }      
      fclose( fileY ) ;
    }
    fclose( fileX ) ;
  }  
  return( istatus ) ;
}

/* ==========================================
 * Return last modification time of a file in IRIS_CONFIG.
 */
UINT4 mtimeIrisConfigFile( const char *sFileName_a )
{
  UINT4 mtime = -1 ; char *sptr = getenv( "IRIS_CONFIG" ) ;
  if( NULL != sptr ) {
    char spath[PATHNAME_SIZE] ; struct stat statbuf ;
    strncpy( spath, sptr, PATHNAME_SIZE-20 ) ;
    spath[PATHNAME_SIZE-20] = '\0' ;
    strcat ( spath, sFileName_a ) ;
    if( stat( spath, &statbuf ) == 0 ) {
      mtime = statbuf.st_mtime ;
    }
  }
  return( mtime ) ;
}
