
/* Cmd.c --- v4 --- Carolyn Scheppner  CBM  07/87
 *
 * Copyright (c) 1987  Commodore Business Machines - All Rights Reserved
 *    This code may be freely non-commercially redistributed.
 *
 * Redirects exec serial or parallel device CMD_WRITEs to a file
 *  (for the purpose of capturing printer output in a file)
 * Built upon fragments of Read (author?) and NoFastMem (Andy Finkel)
 *
 * CLI Usage:  [run] cmd [-s] [-m] [-n] devicename filename
 *   -s (Skip) skips any short initial write (usually a Reset if screendump)
 *   -m (Multiple) causes cmd to remain installed for multiple files
 *   -n (Notify) enables helpful progress messages 
 *   devicename serial or parallel
 *
 * WB Usage:  Just doubleclick.
 *            Specify the args in your icon's ToolTypes (use WB Info)
 *            Built-in defaults are:
 *               DEVICE=parallel
 *               FILE=ram:CMD_file
 *               SKIP=FALSE
 *               MULTIPLE=FALSE
 *               NOTIFY=FALSE
 *
 *   Note: On a screen dump, first CMD_WRITE is usually a printer RESET.
 *         The printer device then delays long enough for the reset
 *         to complete, to prevent the loss of subsequent output data.
 *         When the dump is instead captured in a file, this delay
 *         is of course lost.  If your printer driver outputs a reset
 *         at the start of a dump (as ours do), use the -s (SKIP) option
 *         to keep the initial CMD_WRITE out of the file.
 *
 * Sorry about the busywait synchronization of the device wedge
 * and the main process.  The purpose was to avoid unnecessary
 * meddling with the message structures and the device's signals.
 * I had to add a conditional kludge in MyBeginIO to allow Cmd
 * to work with our HPLaser drivers which do PWrites within a Forbid
 * in their Close logic to print/eject last sheet, and also apparently
 * during their open logic if drivers are resident.
 *
 * v2 mods: changes to MyBeginIO for -1 and 0 length CMD_WRITES, usage
 * v3 mods: added buffering of small writes to speed file IO
 * v4 mods: Conditional kludges added to MyBeginIO for HPLaser
 *          (if Forbidden, sneaks the data into main's write buffer)
 *          (EXTRALEN added to wbuf size to allow extra room for this) 
 *          myWrite now doesn't Write if len = 0
 *          MyClose now conditional on writecnt, not reqcnt
 *
 * Linkage info (requires assembler module cmda):
 * Compile with -v on LC2.
 *    FROM    LIB:Astartup.obj, cmd.o, cmda.o
 *    TO      cmd
 *    LIBRARY LIB:Amiga.lib,LIB:LC.lib
 */

#include "exec/types.h" 
#include "exec/memory.h" 
#include "exec/io.h"
#include "exec/libraries.h"
#include "exec/execbase.h"
#include "libraries/dos.h" 
#include "libraries/dosextens.h"
#include "workbench/startup.h"
#include "workbench/workbench.h"
#include "devices/serial.h" 
#include "devices/parallel.h" 

/* #define DEBUG */
 
#define TOUPPER(c)      ((c)>='a'&&(c)<='z'?(c)-'a'+'A':(c)) 
#define HIGHER(x,y)     ((x)>(y)?(x):(y))

#define WBUFLEN   2048L
#define EXTRALEN   256L

#define INBUFLEN    40L
#define REQSIZE    120L    /* should be big enough or any OpenDevice */

#define DEV_CLOSE    LIB_CLOSE
#define DEV_EXPUNGE  LIB_EXPUNGE
/*      DEV_BEGINIO  (-30)       defined in exec/io.h */

#define OPEN_SIG   SIGBREAKF_CTRL_E
#define WRITE_SIG  SIGBREAKF_CTRL_F
#define CLOSE_SIG  SIGBREAKF_CTRL_D
#define BREAK_SIG  SIGBREAKF_CTRL_C

#define SHORT_WRITE (8L)

extern VOID  myBeginIO();  /* The assembler entry */
extern VOID  myClose();    /* The assembler entry */
extern VOID  myExpunge();  /* The assembler routine */

extern struct ExecBase  *SysBase;
extern struct MsgPort   *CreatePort(); 
extern struct WBStartup *WBenchMsg;

ULONG  RealBeginIO, NewBeginIO;
ULONG  RealClose,   NewClose;
ULONG  RealExpunge, NewExpunge;

char *noMem      = "Out of memory\n";
char *portName   = "cas_TMP_CMD_PORT";
char *conSpec    = "CON:20/20/600/40/ CMD v4";

char u1[]={"\nCLI Usage: [run] Cmd [-s] [-m] [-n] devicename filename\n"};
char u2[]={"  devicename = serial or parallel\n"};
char u3[]={"  -s = SKIP any short initial write (usually a reset if screendump)\
n"};
char u4[]={"  -m = installed for MULTIPLE files until Break or CTRL_C\n"};
char u5[]={"  -n = enables NOTIFY (helpful progress messages)\n\n"};
char u6[]={"WB Tooltypes: DEVICE, FILE, and booleans SKIP,MULTIPLE,NOTIFY\n"};
char u7[]={"   Cancel installation for multiple files by reclicking\n\n"};
char *us[7] = {u1,u2,u3,u4,u5,u6,u7};
char *morehelp = "Type  cmd ?  for more help\n\n";

char *prevTaskName = NULL;
char *outFileName, *deviceName;
char mainTaskName[40];
char wbDev[INBUFLEN], wbFile[INBUFLEN];
char sbuf[120], *wbuf = 0;

struct Device *TheDevice;
struct Task   *otherTask, *mainTask;

struct IOStdReq *myReq, *ioR;
struct MsgPort  *port; 


LONG  wLen = 1, outFile = NULL;
ULONG total = 0;
ULONG IconBase = NULL;
BOOL  Error1 = TRUE, Skip = FALSE, Multiple = FALSE, Notify = FALSE;
BOOL  Done = FALSE, FromWb = FALSE, MainBusy = FALSE;
int   reqcnt = 0, writecnt = 0, filecnt = 0; fnLen, wi;

char cprt[] =
 "Copyright (c) 1987  Commodore Business Machines  All Rights Reserved";

VOID MyBeginIO(ior)
struct IOStdReq *ior;
   {
   BOOL   Forbidden;
   char   *data;
   int    k;

   /* The code conditional on Forbidden is needed to work with
    * HPLaser drivers which PWrite during a Forbid in their Close
    * logic to print and eject last sheet, and also apparently
    * during the initial write if drivers are resident.
    */
   Forbidden = (SysBase->TDNestCnt >= 0) ? TRUE : FALSE;

   reqcnt += 1;
   if((ior->io_Command == CMD_WRITE)&&(ior->io_Length))
      {
      writecnt += 1;

      if(writecnt==1)
         {
         if(!Forbidden)  while(MainBusy);
         MainBusy = TRUE;
         Signal(mainTask,OPEN_SIG);
         if(!Forbidden)   while(MainBusy);
         }

      /* If device CMD_WRITE uses length -1, convert to actual length */
      if(ior->io_Length==-1)  ior->io_Length = strlen(ior->io_Data);

      if((!Skip)||(writecnt>1)||(ior->io_Length > SHORT_WRITE))
         {
         /* This conditional kludge needed to work with HPLaser
          * drivers which PWrite during a Forbid in their
          * Close logic to print/eject last sheet
          */
         if(Forbidden)
            {
            if(ior->io_Length < (WBUFLEN + EXTRALEN - wi))
               {
               data = (char *)ior->io_Data;
               for(k=0; k<ior->io_Length; k++, wi++)  wbuf[wi]=data[k];
               }
            }
         else
            {
            while(MainBusy);
            MainBusy = TRUE;
            ioR = ior;
            Signal(mainTask,WRITE_SIG);  /* Signal write */
            while(MainBusy);
            }
         }
      ior->io_Actual = ior->io_Length;
      }
   if(!(ior->io_Flags & IOF_QUICK))  ReplyMsg(ior);
   }


VOID MyClose(ior)
struct IOStdReq *ior;
   {
   /* Note - Exec has us in a forbid here */
   if(writecnt) /* Ignores DOS's initial Open/Close/Open */
      {
      Signal(mainTask,CLOSE_SIG);  /* Signal Close */
      }
   }

main(argc, argv) 
UWORD argc; 
TEXT *argv[]; 
   { 
   ULONG signals;
   int k;

   FromWb = (argc==0) ? TRUE : FALSE;

   if(FromWb)
      {
      getWbArgs(WBenchMsg);
      deviceName  = wbDev;
      outFileName = wbFile;
      }
   else
      {
      if(strEqu(argv[1], "?"))  usageHelpExit();
      if(argc<3) usageExit();

      for(k=1; argv[k][0]=='-'; k++)
         {
         if(argv[k][1] == 's')  Skip = TRUE;
         if(argv[k][1] == 'm')  Multiple = TRUE;
         if(argv[k][1] == 'n')  Notify = TRUE;
         }
      if(argc-k < 2)  usageExit();
      deviceName  = argv[k++];
      outFileName = argv[k];
      }

   fnLen = strlen(outFileName); /* Used if Multiple extension added */

   /* Result will be mainTaskName = "cas_CMD_whatever.device"
    *   with deviceName pointing to the eighth character
    */
   strcpy(&mainTaskName[0],"cas_CMD_");
   strcpy(&mainTaskName[strlen(mainTaskName)],deviceName);
   strcpy(&mainTaskName[strlen(mainTaskName)],".device");
   deviceName = &mainTaskName[8];

   Forbid();
   if(otherTask = (struct Task *)FindTask(mainTaskName))
      {
      Permit();
      if(FromWb) Signal(otherTask,BREAK_SIG);
      else printf("Device already redirected... exiting\n");
      cleanexit();
      }

   mainTask = (struct Task *)FindTask(NULL);
   prevTaskName = mainTask->tc_Node.ln_Name;
   mainTask->tc_Node.ln_Name = mainTaskName;
   Permit();
     
   /* initialize */
   if(!(wbuf = (char *)AllocMem(WBUFLEN+EXTRALEN,MEMF_PUBLIC|MEMF_CLEAR)))
      cleanexit("Can't allocate write buffer\n");
   wi = 0;    /* index into wbuf */

   if(!(port = CreatePort(portName, 0)))  cleanexit("Can't open port\n");
 
   myReq = (struct IOStdReq *)AllocMem(REQSIZE,MEMF_CLEAR|MEMF_PUBLIC);
   if (!myReq)  cleanexit(noMem);

   myReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
   myReq->io_Message.mn_ReplyPort = port;

   if(OpenDevice(deviceName, 0, myReq, 0))
      {
      sprintf(sbuf,"Can't open %s\n",deviceName);
      cleanexit(sbuf);
      }
   TheDevice = myReq->io_Device;

   /* Install device IO redirection */

   Forbid();
   RealBeginIO = SetFunction(TheDevice, DEV_BEGINIO, myBeginIO);
   RealClose   = SetFunction(TheDevice, DEV_CLOSE,   myClose);
   RealExpunge = SetFunction(TheDevice, DEV_EXPUNGE, myExpunge);
   Permit();

   /* Expunge disabled, CloseDevice so another can open it */
   CloseDevice(myReq);

   if(Notify)
      {
      sprintf(sbuf,"Cmd redirection of %s installed\n",deviceName);
      message(sbuf);
      }

   while(!Done)
      {
      signals = Wait(OPEN_SIG|WRITE_SIG|CLOSE_SIG|BREAK_SIG);

      if(signals & OPEN_SIG)   /* Open */
         {
         if(!outFile)   /* No output file currently open */
            {
            if(Multiple)  /* If Multiple, add .n extension to filename */
               {
               filecnt++;
               sprintf(&outFileName[fnLen],".%ld",filecnt);
               }
             /* open output file */
            outFile = Open(outFileName, MODE_NEWFILE);
            wLen = 1;
            total = 0;
            /* This moved due to sneak-into-buffer HP kludge */
            /* wi = 0;     Init now at Alloc, and each Close */
            Error1 = TRUE;

            if(Notify)
               {
               sprintf(sbuf,"Redirecting %s to %s\n",
                         deviceName,outFileName);
               message(sbuf);
               }

            }
#ifdef DEBUG
      printf("Processed OPEN_SIG, file %s, handle $%lx\n",
                 outFileName,outFile);
#endif
         }

      if(signals & WRITE_SIG)   /* Write */
         {
         if((outFile)&&(wLen > -1))
            {
            wLen = bufOrWrite(outFile,ioR->io_Data,ioR->io_Length);
            }
         else if(Error1)
            {
            message("Cmd file error: Cancel device output\n");
            Error1 = FALSE;
            }
#ifdef DEBUG
      printf("Processed WRITE_SIG, ioLen %ld, wLen %ld, Error1 = %ld\n",
                 ioR->io_Length, wLen, Error1);
#endif
         }

      if(signals & (CLOSE_SIG|BREAK_SIG))
         {
         /* Close file now so user can copy even if something is wrong */
         /* Null the handle - to prevent Write or re-Close */
         if(!Multiple)  signals |= BREAK_SIG;
         if(outFile)
            {
            /* Write buffer contents */
            if((wi>0)&&(wLen>-1)) wLen = myWrite(outFile,wbuf,wi);
            wi = 0;    /* moved from Open logic */

            Forbid();
            Close(outFile);
            outFile = NULL;
            writecnt = 0;
            reqcnt = 0;
            Permit();

            if((!Multiple)||(Notify))
               {
               sprintf(sbuf,"Redirected %ld bytes from %s to %s\n",
                          total,deviceName,outFileName);
               message(sbuf);
               }
            }
#ifdef DEBUG
      printf("Processed CLOSE_SIG, total %ld\n", total);
#endif
         }

      if(signals & BREAK_SIG)
         {
#ifdef DEBUG
      printf("Got BREAK_SIG\n");
#endif
         while(!Done)
            {
            /* Wait till we can reopen the device */
            while(OpenDevice(deviceName, 0L, myReq, 0L))  Delay(50L);

            /* If it's been re-loaded, we can leave            */
            /* Shouldn't be possible since we disabled Expunge */
            if((ULONG)myReq->io_Device != (ULONG)TheDevice)
               {
               Done = TRUE;
               }
            else
               {
               Forbid();

               NewBeginIO = SetFunction(TheDevice, DEV_BEGINIO, RealBeginIO);
               NewClose   = SetFunction(TheDevice, DEV_CLOSE,   RealClose);
               NewExpunge = SetFunction(TheDevice, DEV_EXPUNGE, RealExpunge);

               if((NewBeginIO != (ULONG)myBeginIO)
                   ||(NewClose != (ULONG)myClose)
                     ||(NewExpunge != (ULONG)myExpunge))
                  {
                  /* Someone else has changed the vectors */
                  /* We put theirs back - can't exit yet  */
                  SetFunction(TheDevice, DEV_BEGINIO, NewBeginIO);              
    SetFunction(TheDevice, DEV_CLOSE  , NewClose);
                  SetFunction(TheDevice, DEV_CLOSE,   NewClose);                
  SetFunction(TheDevice, DEV_CLOSE  , NewClose);
                  SetFunction(TheDevice, DEV_EXPUNGE, NewExpunge);
                  }
               else
                  {
                  Done = TRUE;
                  }
               Permit();
               }
            CloseDevice(myReq);
            if(!Done)  message("Vectors have changed - can't restore\n");
            }
         }
      MainBusy = FALSE;
      }

   sprintf(sbuf,"\nCmd redirection of %s removed\n", deviceName);
   cleanexit(sbuf);
   }


/* Output buffering */

bufOrWrite(fh,data,len)
LONG fh;
char *data;
int len;
   {
   int k, wlen;

   wlen = len;

   /* If possible, just buffer the output data */
   if(len <  WBUFLEN - wi)
      {
      for(k=0; k<len; k++, wi++)  wbuf[wi] = data[k];
      }
   else
      {
      /* Else output any buffered data to the file */
      if(wi>0)  wlen = myWrite(fh,wbuf,wi);
      wi = 0;

      /* Then either buffer or write out current request */
      if(wlen > -1)
         {
         if(len < WBUFLEN)
            {
            for(k=0; k<len; k++, wi++)  wbuf[wi] = data[k];
            wlen = len;
            }
         else
            {
            wlen = myWrite(fh,data,len);
            }
         }
      }
   return(wlen);
   }


/* myWrite also updates total */
myWrite(fh,data,len)
LONG fh;
char *data;
int len;
   {
   int wlen = 0;

   if(len)
      {
      wlen = Write(fh,data,len);
      if (wlen > -1) total += wlen;
      }
   return(wlen);
   }


/* Cleanup and exits */

usageHelpExit()
   { 
   int k;
   for(k=0; k<7; k++) printf(us[k]);
   exit(RETURN_OK);
   } 

usageExit()
   { 
   printf(u1);
   printf(morehelp);
   exit(RETURN_OK);
   } 

cleanexit(s)
   char  *s;
   {
   message(s);
   cleanup();
   exit(RETURN_OK);
   }

cleanup()
   {
   if(myReq)   FreeMem(myReq,REQSIZE);
   if(port)    DeletePort(port);
   if(outFile) Close(outFile);
   if(wbuf)    FreeMem(wbuf,WBUFLEN+EXTRALEN);

   Forbid();
   if(prevTaskName) mainTask->tc_Node.ln_Name = prevTaskName;
   Permit();
   }


message(s)
char *s;
   {
   LONG con;

   if((!FromWb)&&(*s)) printf(s);
   if((FromWb)&&(*s)&&(con = Open(conSpec,MODE_OLDFILE)))
      {
      Write(con,s,strlen(s));
      Delay(120L);
      Close(con);
      }
   }


getWbArgs(wbMsg)
struct WBStartup *wbMsg;
   {
   struct WBArg  *wbArg;
   struct DiskObject *diskobj;
   char **toolarray;
   char *s;

   /* Defaults */
   strcpy(wbDev,"parallel");
   strcpy(wbFile,"ram:CMD_file");
   Skip = FALSE;
   Multiple = FALSE;
   Notify = FALSE;

   wbArg = wbMsg->sm_ArgList;

   if((IconBase = OpenLibrary("icon.library", 0)))
      {
      diskobj=(struct DiskObject *)GetDiskObject(wbArg->wa_Name);
      if(diskobj)
         {
         toolarray = (char **)diskobj->do_ToolTypes;

         if(s=(char *)FindToolType(toolarray,"DEVICE"))  strcpy(wbDev,s);
         if(s=(char *)FindToolType(toolarray,"FILE"))    strcpy(wbFile,s);
         if(s=(char *)FindToolType(toolarray,"SKIP"))
            {
            if(strEqu(s,"TRUE"))  Skip = TRUE;
            }
         if(s=(char *)FindToolType(toolarray,"MULTIPLE"))
            {
            if(strEqu(s,"TRUE"))  Multiple = TRUE;
            }
         if(s=(char *)FindToolType(toolarray,"NOTIFY"))
            {
            if(strEqu(s,"TRUE"))  Notify = TRUE;
            }
         FreeDiskObject(diskobj);
         }
      CloseLibrary(IconBase);
      }
   }


/* String functions */

strEqu(p, q) 
TEXT *p, *q; 
   { 
   while(TOUPPER(*p) == TOUPPER(*q))
      {
      if (*(p++) == 0)  return(TRUE);
      ++q; 
      }
   return(FALSE);
   } 

strlen(s)
char *s;
   {
   int i = 0;
   while(*s++) i++;
   return(i);
   }

strcpy(to,from)
char *to, *from;
   {
   do
      {
      *to++ = *from;
      }
   while(*from++);
   }

/* end */

