/*---------------------------------------------------------------------------*/
/* Command: cat.c							     */
/* Author: James E. Cooper Jr.						     */
/* Change History:							     */
/*  Date    Person	  Action					     */
/* -------  ------------- -----------------				     */
/* 20AUG92  Jim Cooper	  Initial Creation				     */
/*									     */
/* Notes:								     */
/*   2.0 specific program similar to the UNIX 'cat' command, with full       */
/*   command-line parsing, wildcard support, and recursive subdirectory      */
/*   scanning.	The AmigaDOS ROM code makes this whole thing almost trivial! */
/*---------------------------------------------------------------------------*/
#define __USESYSBASE

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/rdargs.h>

#include <string.h>

#include <proto/dos.h>
#include <proto/exec.h>

#define DOSLIB	"dos.library"
#define DOSVER	36L			/* We require AT LEAST V36 of OS */

#define THISPROC   ((struct Process *)(SysBase->ThisTask))
#define Result2(x) THISPROC->pr_Result2 = x

#define BUFLEN 256			/* Size of buffer to read into */

#define MSG_CAT_FAILED "Cat failed"

#define TEMPLATE  "FILE/A/M,ALL/S"
#define OPT_FILE  0
#define OPT_ALL   1
#define OPT_COUNT 2

int cmd_cat(void)
{
   struct ExecBase *SysBase = (*((struct ExecBase **) 4));
   struct DosLibrary *DOSBase;
   long temprc, rc;
   long opts[OPT_COUNT];
   struct RDArgs *rdargs;
   struct AnchorPath __aligned ua;
   BPTR oldlock, curlock, fh;
   char fname[32];
   char *curarg, **argptr, *buf;

   /*------------------------------------------------------------------------*/
   /* Set up the default return code.					     */
   /*------------------------------------------------------------------------*/
   rc = RETURN_FAIL;

   /*------------------------------------------------------------------------*/
   /* And open the dos library for our use.				     */
   /*------------------------------------------------------------------------*/
   if ((DOSBase = (struct DosLibrary *)OpenLibrary(DOSLIB, DOSVER)))
   {
      /*---------------------------------------------------------------------*/
      /* O.K., now we know we're running under the correct version of DOS.   */
      /* We need to get a buffer to read data into, in order for out program */
      /* to work.  If we can't get one, we'll just fall out with a failure.  */
      /*---------------------------------------------------------------------*/
      if ((buf = AllocVec(BUFLEN, 0L)) == NULL)
      {
	 PrintFault(IoErr(), MSG_CAT_FAILED);
      }
      else
      {
	 /*------------------------------------------------------------------*/
	 /* Clear the options array, just to be on the safe side...	     */
	 /*------------------------------------------------------------------*/
	 memset((char *)opts, 0, sizeof(opts));

	 /*------------------------------------------------------------------*/
	 /* Parse the command line.					     */
	 /*------------------------------------------------------------------*/
	 rdargs = ReadArgs(TEMPLATE, opts, NULL);

	 /*------------------------------------------------------------------*/
	 /* If there was an error parsing, print an error message and exit!  */
	 /*------------------------------------------------------------------*/
	 if (rdargs == NULL)
	 {
	    PrintFault(IoErr(), NULL);
	 }
	 else
	 {
	    /*---------------------------------------------------------------*/
	    /* Initialize for MultiArgs handling...			     */
	    /*---------------------------------------------------------------*/
	    argptr = (char **)opts[OPT_FILE];

	    /*---------------------------------------------------------------*/
	    /* The following while loop handles the MultiArgs spec.	     */
	    /*---------------------------------------------------------------*/
	    while (curarg = *argptr++)
	    {
	       /*------------------------------------------------------------*/
	       /* Clear our UserAnchor structure to all zeros before going   */
	       /* to the next step.					     */
	       /*------------------------------------------------------------*/
	       memset(&ua, 0, sizeof(struct AnchorPath));

	       /*------------------------------------------------------------*/
	       /* Set up a default message leader.			     */
	       /*------------------------------------------------------------*/
	       strcpy(fname, MSG_CAT_FAILED);

	       /*------------------------------------------------------------*/
	       /* Finally, call the matcher.				     */
	       /*------------------------------------------------------------*/
	       MatchFirst(curarg, &ua);
	       temprc = IoErr();

	       /*------------------------------------------------------------*/
	       /* Process all matches returned.  If 'MatchFirst()' returned  */
	       /* an error, the 'while()' let's us skip all this and fall    */
	       /* through the exit code.				     */
	       /*------------------------------------------------------------*/
	       while (temprc == 0)
	       {
		  strcpy(fname, ua.ap_Info.fib_FileName);

		  if (ua.ap_Info.fib_DirEntryType > 0 && opts[OPT_ALL])
		  {
		     /*------------------------------------------------------*/
		     /* The flag APF_DIDDIR tells us that we are 'backing    */
		     /* out' of a subdirectory... in other words, we have    */
		     /* processed all sub-files of that directory and we are */
		     /* moving back to its parent.  If this flag is set, we  */
		     /* need to clear it.				     */
		     /*------------------------------------------------------*/
		     if (!(ua.ap_Flags & APF_DIDDIR))
		     {
			/*---------------------------------------------------*/
			/* If we are resizing ALL files, tell the matcher to */
			/* enter this directory.			     */
			/*---------------------------------------------------*/
			ua.ap_Flags |= APF_DODIR;
		     }
		     ua.ap_Flags &= ~APF_DIDDIR;
		  }

		  /*---------------------------------------------------------*/
		  /* If we have an error OTHER than that we are out of	     */
		  /* files, we need to exit IMMEDIATELY!		     */
		  /*---------------------------------------------------------*/
		  if (temprc && (temprc != ERROR_NO_MORE_ENTRIES))
		  {
		     break;
		  }

		  /*---------------------------------------------------------*/
		  /* Set us to the directory this file lives in.	     */
		  /*---------------------------------------------------------*/
		  curlock = DupLock(ua.ap_Current->an_Lock);
		  oldlock = CurrentDir(curlock);

		  /*---------------------------------------------------------*/
		  /* We must open the file to read.			     */
		  /*---------------------------------------------------------*/
		  if ((fh = Open(fname, MODE_OLDFILE)) == NULL)
		  {
		     temprc = IoErr();
		     CurrentDir(oldlock);
		     UnLock(curlock);
		     break;
		  }

		  /*---------------------------------------------------------*/
		  /* O.K., the file is open.  Now we simply read lines from  */
		  /* the file and write them out.  Simple two-function	     */
		  /* operation. 					     */
		  /*---------------------------------------------------------*/
		  while (FGets(fh, buf, BUFLEN))
		  {
		     PutStr(buf);
		  }

		  /*---------------------------------------------------------*/
		  /* Close the file, now that we're finished with it.        */
		  /*---------------------------------------------------------*/
		  Close(fh);

		  /*---------------------------------------------------------*/
		  /* Now change the directory back to where it was when we   */
		  /* got started.					     */
		  /*---------------------------------------------------------*/
		  CurrentDir(oldlock);
		  UnLock(curlock);

		  /*---------------------------------------------------------*/
		  /* Now get the next file which matches the given pattern.  */
		  /*---------------------------------------------------------*/
		  MatchNext(&ua);
		  temprc = IoErr();
	       }

	       /*------------------------------------------------------------*/
	       /* If the only error we had was that we ran out of files to   */
	       /* type, we are doing fine!  Set the return code so the user  */
	       /* knows everything went well.				     */
	       /*------------------------------------------------------------*/
	       if (temprc == ERROR_NO_MORE_ENTRIES)
	       {
		  rc = RETURN_OK;
	       }
	       else
	       {
		  PrintFault(temprc,fname);

		  /*---------------------------------------------------------*/
		  /* If the user hit Ctrl-C, we have to warn him that he is  */
		  /* being rude!					     */
		  /*---------------------------------------------------------*/
		  if (temprc == ERROR_BREAK)
		  {
		     rc = RETURN_WARN;
		  }
		  else
		  {
		     /*------------------------------------------------------*/
		     /* If there was any other error, FAIL!!!		     */
		     /*------------------------------------------------------*/
		     rc = RETURN_FAIL;
		  }
	       }
	       /*------------------------------------------------------------*/
	       /* We have to clean up after ourselves.			     */
	       /*------------------------------------------------------------*/
	       MatchEnd(&ua);
	    }
	    FreeArgs(rdargs);
	 }
      }
      CloseLibrary((struct Library *)DOSBase);
   }
   else
   {
      /*---------------------------------------------------------------------*/
      /* They tried to run us under the wrong version of the OS.  We need to */
      /* exit gracefully, yet still let them know what went wrong...	     */
      /*---------------------------------------------------------------------*/
      Result2(ERROR_INVALID_RESIDENT_LIBRARY);
   }
   return(rc);
}
