/*
 * **********************************************************************
 * *	                                                                *
 * *              Portable File and Directory Routines                  *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/fileops.c
 *
 *                  COPYRIGHT (c) 1994, 1996, 1998, 1999  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_opendir()            Open a directory in preparation for searching
 *   sig_readdir()            Get next element from a directory
 *   sig_closedir()           Clean up after searching
 *   sig_delete_file()        Delete a single file
 *   sig_delete_file_wild()   Delete all files matching a wildcard spec
 *   sig_copy_file()          Copy one file to another
 *   sig_fnmatch()            Pattern matching for file names
 *   sfilelist()              Return list of files in a directory
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include "sigtypes.h"
#include "user_lib.h"

/* Function prototypes used just within this file
 */
static char match_c[80] ;	/* Names to match */
static char directory_c[120] ;	/* Directory to search */
static UINT4 directory_len_c ;

static DIR* dirp_c = NULL ;	/* Points to open directory */

/* Cover for STRCMP() with args that match the requirements of
 * QSORT() to avoid prototype mismatch errors.
 */
static UINT2 lfilelist_fwd_c ;	/* Sort in forward direction */
static int strcmp_cover( const void *str1_a, const void *str2_a )
{
  int icompare = strcmp( str1_a, str2_a ) ;
  return( lfilelist_fwd_c ? icompare : -icompare ) ;
}

/* ======================================================================
 * Open a directory in preparation for searching there.
 *
 * Entry Parameters
 *   DIRECTORY_A   Name of directory
 *   MATCH_A       Pattern of files to match
 */
MESSAGE sig_opendir
( const char* directory_a,
  const char* match_a )
{
  MESSAGE istatus ;

  if( dirp_c ) {
    istatus = USER_OPENDIR_SEQ ;

  } else {
    strncpy( match_c    , match_a    , sizeof(match_c)     ) ;
    strncpy( directory_c, directory_a, sizeof(directory_c) ) ;
    directory_c[sizeof(directory_c)-1] = 0 ;
    directory_len_c = strlen( directory_c ) ;

    /* Add trailing / if not already there.
     */
    if ( directory_c[directory_len_c-1] != '/' )
      if (directory_len_c < sizeof(directory_c)-1)
	{
	  directory_c[directory_len_c++] = '/';
	  directory_c[directory_len_c] = 0;
	}

    dirp_c = opendir( directory_a ) ;
    if( dirp_c == NULL ) istatus = errno_to_message_va( errno, 1, directory_a ) ;
                    else istatus = SS_NORMAL ;
  }    
  return( istatus ) ;
}

/* ======================================================================
 * Get next matching directory entry.  Returns the full path name of the
 * elements that are found.
 *
 * Entry Parameters
 *   PATH_LEN_A    Size of PATH_A
 * Exit Parameters
 *   PATH_A        Path of the directory element
 *   VALID_A       NZ if valid entry found.
 *   INAME_A       Index (origin zero) into PATH_A of start of file name
 *   -Return-      The return status will be SS_NORMAL as long as no error
 *                 occurs.  This includes the final call to sig_readdir(),
 *                 which returns VALID_A as FALSE.
 */
MESSAGE sig_readdir(
  char*   path_a,
  UINT4   path_len_a,
  UINT4*  iname_a,
  UINT2*  valid_a )
{
  struct dirent *dp ; SINT4 ii ;

  errno = 0 ; /* readdir() leaves errno unaltered on successful completion */
  while( NULL != (dp = readdir( dirp_c )) ) {
    if((0 != sig_fnmatch( match_c, dp->d_name )) &&
       (0 != strcmp ( "."    , dp->d_name     )) &&
       (0 != strcmp ( ".."   , dp->d_name     ))  ) {
      strncpy( path_a, directory_c, path_len_a ) ;
      ii = path_len_a - directory_len_c ;
      if( ii > 0 ) strncpy( &path_a[directory_len_c], dp->d_name, ii ) ;

      *valid_a = TRUE ; *iname_a = directory_len_c ;
      return SS_NORMAL ;
    }
  }
  *valid_a = FALSE ;
  if( errno ) return errno_to_message_va( errno, 1, "sig_readdir()" ) ;
  else return SS_NORMAL ;
}

/* ======================================================================
 * Close the directory that is currently being searched
 */
MESSAGE sig_closedir()
{
  MESSAGE istatus ;
  if( dirp_c == NULL ) {
    istatus = USER_CLOSEDIR_SEQ ;

  } else {
    if( closedir( dirp_c ) ) {
      istatus = errno_to_message_va( errno, 1, "sig_closedir()" ) ;
    } else {
      istatus = SS_NORMAL ;
    }
    dirp_c = NULL ;
  }
  return( istatus ) ;
}

/* ======================================================================
 * Delete a single file.  Full path name of the file is supplied
 */
MESSAGE sig_delete_file
  (const char *path_a )
{
  if( remove( path_a ) )
    return( errno_to_message_va( errno, 1, path_a ) ) ;
  else
    return( SS_NORMAL ) ;
}

/* ======================================================================
 * Delete all files matching a wild card spec.  This routine could also
 * be called to delete a single file, but would be less effecient than
 * the above routine.
 *
 * Entry Parameters
 *   DIRECTORY_A   Name of directory
 *   MATCH_A       Pattern of files to match
 */
MESSAGE sig_delete_file_wild(
  const char*   directory_a,
  const char*   match_a )
{
  MESSAGE istatus ;

#define NWILD 40
  UINT4 iname ; UINT2 lvalid ; MESSAGE ist ; SINT4 iwild ;
  char spaths[NWILD][80] ;

  istatus = SS_NORMAL ; lvalid = 1 ;
  while( (istatus == SS_NORMAL) && (lvalid != 0) ) {

    /* First loop through and locate a bunch of files.  We dont have
     * to find them all in just one pass...
     */
    istatus = sig_opendir( directory_a, match_a ) ; iwild = 0 ;
    if( istatus == SS_NORMAL ) {
      istatus = sig_readdir( spaths[iwild], sizeof(spaths[0]), &iname, &lvalid ) ;
      while( (istatus == SS_NORMAL) && (lvalid != 0) ) {
	iwild++ ; if( iwild >= NWILD ) break ;
	istatus = sig_readdir( spaths[iwild], sizeof(spaths[0]), &iname, &lvalid ) ;
      }
      ist = sig_closedir() ;
      if( istatus == SS_NORMAL ) istatus = ist ;
    }

    /* Then delete each of the ones that we found...
     */
    while( (istatus == SS_NORMAL) && (iwild > 0) ) {
      iwild-- ; istatus = sig_delete_file( spaths[iwild] ) ;
    }
  }
  return istatus ;
}

/* ======================================================================
 * Copy source file to destination file.  If the desination file already
 * exists it is clobbered as long as okay_to_clobber_a is nonzero.
 * Optional function lstop_e is evaluated periodically to determine
 * whether to stop the copy midstream.
 */
MESSAGE sig_copy_file
  (const char *sdest_path_a,
   const char *source_path_a,
   UINT2 okay_to_clobber_a,
   UINT2 (*lstop_e)(void) )
{
  MESSAGE istatus;
  int ierror;
  FILE *source_file, *dest_file, *fds ;
  UINT2 lcontinue;
  SINT4 iread_size, iwrite_size;

#define BUFFER_SIZE (4096)
  char ibuffer[BUFFER_SIZE];

  /* ----------------------------------------------------------------------- */
  istatus = SS_NORMAL;

  source_file = fopen( source_path_a, "r" );
  if (source_file != NULL)
    {
      if( okay_to_clobber_a ) {
	dest_file = fopen( sdest_path_a, "w" );
      } else {
	fds = fopen( sdest_path_a, "r" );	
	if( fds != NULL ) {
	  fclose(fds) ; dest_file = NULL ; errno = EEXIST ;
	} else {
	  dest_file = fopen( sdest_path_a, "w" );
	}
      }
      if (dest_file != NULL)
	{
	  lcontinue = TRUE;
	  /* The first read should get data */
	  iread_size = fread( ibuffer, 1, BUFFER_SIZE, source_file );
	  if (iread_size == 0)
	    {
	      istatus = errno_to_message_va( errno, 1, source_path_a ) ;
	      lcontinue = FALSE;
	    }
	  while( lcontinue )
	    {
	      iwrite_size = fwrite( ibuffer, iread_size, 1, dest_file );
	      if (iwrite_size != 1)
		{
		  istatus = errno_to_message_va( errno, 1, sdest_path_a ) ;
		  lcontinue = FALSE;
		}
	      iread_size = fread( ibuffer, 1, BUFFER_SIZE, source_file );
	      if (iread_size == 0) lcontinue = FALSE;

	      if( (int)lstop_e ) {
		if (lstop_e()) lcontinue = FALSE;
	      }
	    }

	  ierror = fclose( dest_file );
	  if (ierror == EOF) istatus = errno_to_message_va( errno, 1, sdest_path_a ) ;
	}

      else
	{
	  istatus = errno_to_message_va( errno, 1, sdest_path_a ) ;
	}

      ierror = fclose( source_file );
      if (ierror == EOF) istatus = errno_to_message_va( errno, 1, source_path_a ) ;
    }
  else
    {
      istatus = errno_to_message_va( errno, 1, source_path_a ) ;
    }

  return istatus;
}

/* ======================================================================
 * Copy part of a source file to destination file.  If the desination file already
 * exists it is clobbered as long as okay_to_clobber_a is nonzero.
 */
MESSAGE sig_copy_partial_file
  (const char *sdest_path_a,
   const char *source_path_a,
   UINT2 okay_to_clobber_a,
   UINT4 isize_a )
{
  MESSAGE istatus, istatus2;
  int ierror;
  FILE *source_file, *dest_file, *fds ;
  SINT4 iread_size, iwrite_size;

  char* pbuffer;

  /* ----------------------------------------------------------------------- */
  istatus = SS_NORMAL;
  
  /* Skip a zero sized operation */
  if (isize_a==0) return istatus;

  istatus = sig_malloc( isize_a, (void **)&pbuffer );
  if (istatus!=SS_NORMAL) 
    return (iMessageMod_va( istatus, 1, "sig_copy_partial_file()" )) ;

  source_file = fopen( source_path_a, "r" );
  if (source_file == NULL)
    {
      istatus = errno_to_message_va( errno, 1, source_path_a ) ;
      return istatus;
    }

  if( okay_to_clobber_a )
    {
      dest_file = fopen( sdest_path_a, "w" );
    }
  else
    {
      fds = fopen( sdest_path_a, "r" );	
      if( fds != NULL ) {
	fclose(fds) ; dest_file = NULL ; errno = EEXIST ;
      } else {
	dest_file = fopen( sdest_path_a, "w" );
      }
    }

  if (dest_file != NULL)
    {
      /* Read the data in one command. */
      iread_size = fread( pbuffer, 1, isize_a, source_file );
      if (iread_size != 0)
	{
	  iwrite_size = fwrite( pbuffer, iread_size, 1, dest_file );
	  if (iwrite_size != 1)
	    {
	      istatus = errno_to_message_va( errno, 1, sdest_path_a ) ;
	    }
	}
      else
	{
	  istatus = errno_to_message_va( errno, 1, source_path_a ) ;
	}
      
      ierror = fclose( dest_file );
      if (ierror == EOF) istatus = errno_to_message_va( errno, 1, sdest_path_a ) ;
    }
  else
    {
      istatus = errno_to_message_va( errno, 1, sdest_path_a ) ;
    }
  
  ierror = fclose( source_file );
  if (ierror == EOF) istatus = errno_to_message_va( errno, 1, source_path_a ) ;

  istatus2 = sig_free( pbuffer );
  if (istatus2!=SS_NORMAL) 
    sig_signal( iMessageMod_va( istatus2, 1, "sig_copy_partial_file" ));
  return istatus;
}

/* ======================================================================
 * Return TRUE iff the file name matches the wild card specification.  This
 * is a platform-independent version of the C-library fnmatch() routine, which
 * is not universally available.  (Note: reverse sense of return value)
 */
UINT2 sig_fnmatch
(const char *wild_a,
 const char *name_a )
{
  const char *pwild = wild_a, *pname = name_a ;

  for( ; ; pwild++, pname++ ) {

    /* Handle obvious termination cases
     */
    if( *pname == 0 ) {
      while( *pwild ) if( '*' != *pwild++ ) return FALSE ;
      return TRUE ;
    } else {
      if( *pwild == 0 ) return FALSE ;
    }
    /* If the next characters mismatch, then we have a definite FALSE,
     * otherwise let them pass and continue checking.  Question marks
     * match any single character. Asterisk matches if any subsequent
     * part of the name can be recursively found to match the remaining
     * portion of the wildcard.
     */
    switch( *pwild ) {
    default:
      if( *pwild != *pname ) return FALSE ; break ;

    case '?' :
      break ;

    case '*' :
      for( ; ; pname++ ) {
        if( 0 != sig_fnmatch( pwild+1, pname ) ) return TRUE  ;
        if( 0 == *pname                        ) return FALSE ;
      }
    }
  }
}
/* ============================================================================
 * Return a list of files that match a specification within a
 * chosen directory.  Strip the path, suffix ...etc, only keep
 * the file name.  The file names are separated by a space, and
 * the entire list is terminated with a null.  The first
 * character of the list will be a space (why?).
 *
 * The calling program must free the pointer after it is
 * returned.  This routine will always malloc a multiple of 4
 * bytes, so it is OK to round up the size to the next multiple
 * of 4 bytes for packing the output array.
 */
char* sfilelist
(const char *dir_a, const char* file_a, UINT2 lforward_a )
{
  char spec[FILENAME_SIZE], *stokend, *sname, *slist, *snew_list ;
  MESSAGE istatus ;
  UINT2 lvalid ;
  UINT4 iname ;
  SINT4 isize, ilength;

  lfilelist_fwd_c = lforward_a ; /* Set sorting direction */

  isize = 240;
  if( NULL == (slist = (char *)malloc( isize )) )
    {
      static char sdummy = 0 ;
      sig_signal( errno_to_message_va
		  ( errno, 3, "sfilelist(%s, %s)", dir_a, file_a ) ) ;
      slist = &sdummy ;
    }
  else
    {
      slist[0] = 0;
      istatus = sig_opendir( dir_a, file_a ) ;
      if( istatus == SS_NORMAL ) {

	istatus = sig_readdir( spec, sizeof(spec), &iname, &lvalid ) ;

	while( (istatus == SS_NORMAL) && (lvalid != 0) )
	  {
	    sname   = &spec[iname] ;
	    stokend = strstr( sname, "." ) ;
	    if ( stokend != NULL ) 
	      {
		*stokend = 0;
		/* Increase the size of the buffer if it looks like it might
		 * fill up.
		 */
		ilength = strlen( slist );
		if ((isize-ilength) < ( sizeof(spec)+2))
		  {
		    isize += 240;
		    snew_list = (char *)realloc( slist, isize );
		    if (NULL == slist)
		      {
			sig_signal( errno_to_message_va
				    ( errno, 3, "sfilelist(%s, %s)", dir_a, file_a ) );
			return slist;
		      }
		    slist = snew_list;
		  }
		strcat( slist, " " );
		strcat( slist, sname );
	      }
	    istatus = sig_readdir( spec, sizeof(spec), &iname, &lvalid ) ;
	  }
	istatus = sig_closedir() ;

	/* Now sort the list of file names.  Convert the array of
	 * variable length names separated by spaces into an
	 * array of fixed length null terminated names that
	 * QSORT() can use.
	 */
	if( *slist == ' ' ) {
	  char *plist, *psort, ch ; SINT4 ii, inum, imaxlen ;

	  for( inum=0, imaxlen=0, plist=slist+1 ; ; ) {
	    for( ii=0 ; ; ii++ ) {
	      ch = *plist++ ; if( (ch == ' ') || (ch == '\0') ) break ;
	    }
	    if( ii > 0 ) { imaxlen = MAX( imaxlen, ii ) ; inum++ ; }
	    if( (ii == 0) || (ch == '\0') ) break ;
	  }
	  if( (inum > 0) && (imaxlen > 0) ) {
	    if( NULL != (psort = (char *)malloc( inum * (imaxlen+1) )) ) {
	      for( ii=0, plist=slist+1 ; ii < inum ; ii++ ) {
		char *pslot = &psort[ ii * (imaxlen+1) ] ;
		while( (*plist != ' ') && (*plist != '\0') ) *pslot++ = *plist++ ;
		*pslot = '\0' ; plist++ ;
	      }

	      qsort( psort, inum, imaxlen+1, strcmp_cover ) ;

	      for( ii=0, plist=slist+1 ; ii < inum ; ii++ ) {
		char *pslot = &psort[ ii * (imaxlen+1) ] ;		
		while( *pslot != '\0' ) *plist++ = *pslot++ ; *plist++ = ' ';
	      }
	      *--plist = '\0' ; free( psort ) ;
	    }
	  }
	}
      }
  }
  return( slist ) ;
}
