/* Cp:  A replacement for AmigaDos Copy that does not modify the date
 *	  by Jeff Lydiatt
 *	  Vancouver, Canada.
 *
 * Features:
 *   1) Handles all functions of AmigaDos copy, including the All switch.
 *   2) Regular expression pattern matching that handles the AmigaDos'
 *       pattern matching characters "#?|%'()".
 *   3) This copy keeps the date of the copied file!
 *   4) Uses a 32K buffer which should speed up single disk copies.
 *   5) In "Cp filex .", the "." is taken to mean the current directory.
 *
 * Created: 07May87
 *
 * Cp has been compiled and tested under Aztec v3.40a with patch v3
 * applied.  The 16 bit integer and small code, small data option is
 * used in compiling and linking all modules.
 *
 * Maintenance Notes:
 *  17May87 - Version 1.0 Released.
 *
 */

/* 
      Cp and the c source is freely redistributable for personal
      non-commercial use.  Commercial rights are reserved by the
      author.  Feel free to make any modifications or use any 
      of the modules in other programs.  I encourage you to do so
      and hope you will release the code so others can learn by it.
 */

#include <stdio.h>
#include <libraries/dos.h>
#include <exec/memory.h>
#include <functions.h>

#define BUFMAX 32768
#define MAXSTR 127
 
extern int Enable_Abort;
extern char *malloc();
extern int CmplPat(), Match();
extern long Chk_Abort();

/*--- A poor man's "printf" which avoids the overhead of printf ---*/

static void msg(str, parm)
char	*str, *parm;
{
   register char *s;
   register struct FileHandle *console;

   console = Output();
   for ( s=str; *s; ++s )
     {
       if ( *s == '%' && *(s+1) == 's' )
	 {
	    (void)Write( console, str, (long)(s - str));
	    (void)Write( console, parm, (long)strlen(parm));
	    (void)Write( console, (char *)(s + 2), (long)strlen((char *)(s+2)));
	    break;
	 }
     }

    if ( *s == '\0' )
       (void)Write( console, str, (long)strlen(str));
}
  
static void error( str, parm )
char *str, *parm;
{
   msg( str, parm );
   exit(NULL);
}

static void useage(name)
char *name;
{
    msg( "%s V1.0 buffered file copy by Jeff Lydiatt.  Usage:\n", name );
    msg( "  %s [From] filein [To] fileout [All]\n", name );
    msg( "  AmigaDos style pattern matching characters permitted in filein.\n" );
    msg( "  Pattern matching characters not allowed in Fileout.\n\n" );
}

/*-----------------------------------------------------------*/
/*		Parse the command string		     */
/*-----------------------------------------------------------*/

static int toupper(c)
register char c;
{
   if ( 'a' <= c && c <= 'z' )
	return c + ('A' - 'a');
   return c;
}
  
/* Case-blind implementation of strcmp() */

static int blindcmp( s1, s2 )
register char *s1, *s2;
{
     
   while (*s1 && *s2)
     {
	if ( (toupper( *s1 )) == (toupper( *s2 )) )
	   {
	      ++s1; 
	      ++s2;
	   }
        else
	   break;
     }

   if ( toupper(*s1) == toupper(*s2) )
      return 0; /* It's Equal */
   else if ( toupper(*s1) > toupper(*s2) )
      return 1;
   else
      return -1; 
}

static void parsecmd(fparm, tparm, All, argc, argv)
char *fparm, *tparm;
register char *argv[];
int *All, argc;
{
   int nextparm;
   register char *parm;

   if ((argc < 2) || ( strcmp( argv[1], "?" ) == 0) )
     {
       useage( argv[0] );
       exit( NULL );
     }

   *All = FALSE;
   nextparm = 1;
   argc--;
   fparm [0] = '\0';
   tparm[0] = '\0';
   while ( nextparm <= argc )
     {
	parm = argv[ nextparm ];
	if ( (nextparm < argc) && (blindcmp( parm, "from" ) == 0) )
	  {
	    ++nextparm;
	    (void)strncpy( fparm, argv [nextparm], MAXSTR);
	  }
	else if ( (nextparm < argc) && (blindcmp( parm, "to" ) == 0) )
	  {
	    ++nextparm;
	    (void)strncpy( tparm, argv [nextparm], MAXSTR );
	   }
	else if ( (nextparm == argc) && (blindcmp( parm, "all") == 0) )
	    *All = TRUE;
	else if ( fparm[0] == '\0' )
	   (void)strncpy( fparm, parm, MAXSTR );
	else
	   (void)strncpy( tparm, parm, MAXSTR );
	++nextparm;
     }
}

/*--------------------------------------------------------------*/
/*	Internal stack manipulation routines			*/
/*--------------------------------------------------------------*/

struct stackframe 
{
	struct stackframe *next;
	char *strptr;
};	
static struct stackframe *Stackhead;

static void initstack()
{
	Stackhead = NULL;
}

static int isempty()
{
   return Stackhead == NULL;
}

/*--------------push a string on the stack------------------*/

static void push(str)	char *str; /* Push a string on the stack */
{
   register struct stackframe *p;
   register char *s;

   if ( (p = (struct stackframe *)malloc(sizeof(struct stackframe))) == NULL
      ||(s = malloc( strlen(str)+1 )) == NULL)
      {
	msg("Not enough memory\n");
	return;
      }

   strcpy( s, str );
   p->next = Stackhead;
   p->strptr = s;
   Stackhead = p;
}

/*---------pop the next string from the stack------------------*/

static void pop(str) char *str;
{
   register struct stackframe *p;

   if (isempty())
	error("Can't happen: stack popped with nothing there\n");

   p = Stackhead;
   strcpy( str, p->strptr );
   free( p->strptr );
   Stackhead = p->next;
   free( p );
}

/*--------------------------------------------------------------*/
/*	Some handy path manipulation routines.			*/
/*--------------------------------------------------------------*/

/*------------separate path and pattern from str----------------*/

static char *getpath( path, pattern, str)
char *path, *pattern, *str;
{
   register char *s1, *s2;
   char *mark;

   mark = NULL;
   for ( s1=str; *s1; ++s1)
	if ( *s1 == ':' || *s1 == '/' )
	   mark = s1;

   if ( isdir(str) )
     {
	strcpy( path, str );
	*pattern = '\0';
     }
   else if ( !mark )
     {
	strcpy( pattern, str );
	*path = '\0';
     }
   else
     {
	s2 = path;
	s1 = str;
	while ( s1 < mark )
	   *s2++ = *s1++;
	if ( *s1 == ':' )
	   *s2++ = *s1;
	*s2 = '\0';
	strcpy( pattern, (char*)( mark+1));
     }

   return path;
}

/*------return path from parent directory and member name-------*/

static char *makepath(path, parent, member)
register char *path;
char *member;
register char *parent;
{
   register int len;

   strcpy( path, parent );
   len = strlen( path );  
   if ( len > 0 && *member )
      if ( (path[len-1] != ':') && (path[len-1] != '/') )
	  strcat( path, "/" );
   strcat( path, member );
   return path;
}

/*--- isdir: Returns 0 if NOT a directory, 1 otherwise. ---*/

static int isdir(filename)
char	*filename;
{
	struct FileInfoBlock *f;
	struct Lock *outlk;
	int		result;

	result = 0;
	outlk = (struct Lock *)Lock(filename,ACCESS_READ);
	if (outlk) 
	  {
	    f = (struct FileInfoBlock *)
		 AllocMem((long)sizeof(struct FileInfoBlock), MEMF_CHIP);
	    if (f == 0) 
		error("Unable to allocate space for FileInfoBlock!\n");
	    if (Examine(outlk,f))
	    if (f->fib_DirEntryType >= 0)
		result = 1;
	    UnLock(outlk);
	    FreeMem((void *)f,(long) sizeof(struct FileInfoBlock));
	  }
	return(result);
}

static int iswild(pattern)
register char *pattern;
{
   for (; *pattern; ++pattern )
     {
	switch( *pattern )
	 {
	   case '#':
	   case '?':
	   case '(':
	   case '%':
	   case '\'':
	   case '|': return 1;
	 }
     }
   return 0;
}

static int cp(fpath, tpath, name )
char *fpath, *tpath, *name;
{
   char fname[MAXSTR+1], tname[MAXSTR+1];
   char *buffer;
   long status, count, bufsize;
   struct FileHandle *fin, *fout;
   struct DateStamp date;

   (void)makepath( fname, fpath, name );

   if ( tpath[0] != '\0' && !isdir(tpath) )
      strcpy( tname, tpath );
   else
      (void)makepath( tname, tpath, name );

   msg( "%s ==> ", fname ); msg( "%s\n", tname);
   if ( (fin = Open( fname, MODE_OLDFILE )) == NULL )
     {
	msg( "Can't open %s for input.\n", fname );
	return 1;
     }
   if ( (fout = Open( tname, MODE_NEWFILE )) == NULL )
     {
	msg( "Can't open %s for ouput.\n", tname );
	Close( fin );
	return 1;
     }

   for ( bufsize = BUFMAX; bufsize > 2048; bufsize -= 2048 )
      if ( (buffer = malloc( (unsigned int)bufsize)) != NULL )
	break;

   if ( bufsize <= 2048 )
      {
	Close( fin );
	Close( fout );
	msg( "Not enough memory.\n" );
	return 1;
      }

    status = 1;
    while( status > 0 && (count = Read( fin, buffer, bufsize )) == bufsize )
      if ( Chk_Abort() )
	status = -1;
      else
	status = Write( fout, buffer, count );

    if (status > 0 && count > 0 )
	status = Write( fout, buffer, count );

    Close( fin );
    Close( fout);
    free( buffer );

    if ( status < 0 || count < 0 )
      {
	(void)DeleteFile( tname );
	msg( "   %s [removed].\n", tname );
	return 1;
      }

    if ( getDate( fname, &date ))
       (void)setDate( tname, &date );

    return 0;
}

main(argc,argv)
int	argc;
char	*argv[];
{
   int  aux[MAXSTR+1];
   char fparm[MAXSTR+1], tparm[MAXSTR+1];
   char fpath[MAXSTR+1], tpath[MAXSTR+1];
   char fparent[MAXSTR+1], dontSearch[MAXSTR+1];
   char fpattern[MAXSTR+1];
   char relpath[MAXSTR+1], junk[MAXSTR+1];
   void *dirlock;
   register struct FileInfoBlock *fileptr;
   int All;

   Enable_Abort = 0;

   parsecmd( fparm, tparm, &All, argc, argv );
   (void)getpath( fparent, fpattern, fparm );
   if ( strcmp(tparm, ".") == 0 )
      tparm[0] = '\0';

   /*--- Are the parms ok? --- */

   if ( iswild(fparent) )
      error( "Regular expression not allowed in path: %s.\n", fparm );
   if ( iswild(tparm) )
      error( "Regular expression not allowed as output name.\n" );

   /*----- if it's just a single file copy, just do it and exit----*/

   if ( !All && !iswild( fpattern ) && !isdir( fparm ))
     {
	(void)cp( fparent, tparm, fpattern );
        exit( NULL );
     }

   /*--- For "All" option, don't search the "to" directory --- */

   fileptr = (struct FileInfoBlock *)
	AllocMem( (long)sizeof(struct FileInfoBlock), MEMF_CHIP);

   strcpy( dontSearch, tparm );
   if ( dontSearch[0] == '\0' )
     {
	dirlock = Lock("", ACCESS_READ);
	if ( dirlock )
	   {
		(void)Examine( dirlock, fileptr );
		strcpy( dontSearch, fileptr->fib_FileName );
		UnLock( dirlock );
	   }   
     }

   initstack();
   push( "" );
   if ( fpattern[0] == '\0' )
      strcpy( fpattern, "#?" );
   if ( CmplPat( fpattern, aux ) == 0 )
     {
	msg("Bad regular expression pattern: %s.\n", fpattern);
	goto all_done;
     }

   while( !isempty() )
     {
	pop( relpath );
	(void)makepath( fpath, fparent, relpath );
	(void)makepath( tpath, tparm, relpath );

	if ( All && !isdir(tpath) )
	   {
		if ( (dirlock = CreateDir( tpath )) == NULL )
		   {
			msg( "Can't create %s \n", tpath );
			continue;
		   }
		else
		   {
			msg("%s ... [created]\n", tpath);
			UnLock( dirlock );
		   }
	   }

	if ( (dirlock = Lock( fpath, ACCESS_READ )) == NULL
	  || !Examine( dirlock, fileptr) )
	   {
		msg( "Can't find directory %s\n", fpath );
		if ( dirlock )
		   UnLock( dirlock );
		goto all_done;
	   }

	while ( ExNext( dirlock, fileptr ) )
	   {
		if ( fileptr->fib_DirEntryType > 0 )
		  {
		    if ( All && strcmp( dontSearch, fileptr->fib_FileName) )
		      push( makepath( junk, relpath, fileptr->fib_FileName ) );
		  }
		else if (Match( fpattern, aux, fileptr->fib_FileName ) )
		  {
		    if ( Chk_Abort() || cp( fpath, tpath, fileptr->fib_FileName ) )
		       {
			  UnLock( dirlock );
			  goto all_done;
		       }
		  }
	   }
	UnLock( dirlock );
     }
all_done:
   FreeMem( fileptr, (long)sizeof(struct FileInfoBlock));
   exit(NULL);
}
