[comp.sources.amiga] Cmd.sh

doc@s.cc.purdue.edu (Craig Norborg) (06/19/87)

	Here is the latest version of cmd from Carolyn Scheppner over
    at Commodore.  The fixes/upgrades are best explained by Carolyn, so...

>Cmd allows redirection of serial and parallel output (such as printer output)
>to a file.  It can be installed for single or multiple files.
>This new version (v3) has a bug fix (now handles CMD_WRITE's of -1 length)
>and also provides much faster file capture for programs that send
>multiple small writes to printer, serial, or parallel.  This is implemented
>with a 2K output buffer and was suggested by Allen Norskog.
>
>carolyn
 
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	cmd.doc
#	cmd.with
#	cmd.asm
#	cmd.c
# This archive created: Thu Jun 18 11:29:00 1987
# By:	Craig Norborg (Purdue University Computing Center)
cat << \SHAR_EOF > cmd.doc

TITLE:  Cmd.uu v3 (exec. and icon) - Redirects serial or parallel to file

   For those of you who have asked for the ability to capture printer
output in a file...

   What follows are two uuencoded files for my Cmd.info and Cmd.

   Cmd redirects serial.device or parallel.device output to a file.
This allows you to capture the printer output of many applications
(including screen dumps) in a file.

CLI Usage: [run] Cmd [-s] [-m] [-n] parallel|serial filename
           (or  Cmd ?  for help)

  Example: run Cmd -m -n parallel ram:pfile
           To end a multiple (-m) Cmd, CTRL-C, or use BREAK if run

Workbench: Just double click.  All args specified in .info ToolTypes.
              Workbench defaults:
                 DEVICE=parallel
                 FILE=ram:CMD_file
                 SKIP=FALSE
                 MULTIPLE=FALSE
                 NOTIFY=FALSE
           To end a MULTIPLE Cmd, double-click CMD again

Options (CLI or Workbench ToolTypes):

   -s (ToolTypes: SKIP=TRUE)

      If the -s (SKIP) option is specified, Cmd will ignore an initial
      short (less than 8 bytes) write to the device and not save that
      write in the file.  This option is useful when capturing a
      screen dump because most of our drivers currently send a Reset
      string to the printer before the dump.  The driver then delays
      so that subsequent data sent is not lost.  When the dump is
      captured in a file, this programmatic delay is lost and the
      following bytes are endangered by the reset.  In most cases,
      this option should prevent the Reset string from being stored
      in the Cmd file.  You should be able to copy the resulting file
      directly to PAR: (or SER:) for a quick dump.

   -m (Tooltypes: MULTIPLE=TRUE)

      If the -m (MULTIPLE) option is not specified, the file is closed
      and the redirection removed when the device is closed by the
      application.  With the -m option, Cmd remains installed and
      opens a new file (with .n extension) each time the device is
      opened.

   -n (Tooltypes: NOTIFY=TRUE)

      The -n (NOTIFY) option causes output of progress messages about
      the redirection.  I like noisy utilities but many others don't,
      so it's an option. 


Cmd - Copyright (c) 1987 Commodore Business Machines - All Rights Reserved
This program may be freely non-commercially redistributed.
Only warranty is works fine for me --- Carolyn Scheppner

This icon already has some default Tooltypes set up.  Executable follows.
SHAR_EOF
cat << \SHAR_EOF > cmd.with
FROM    LIB:Astartup.obj, cmd.o, cmda.o
TO      cmd
LIBRARY LIB:Amiga.lib,LIB:LC.lib
SHAR_EOF
cat << \SHAR_EOF > cmd.asm

*   cmda.asm  --- assembler interface for Cmd.c
*                 Carolyn Scheppner --- CBM  05/87

   INCLUDE   'exec/types.i'

   XREF   _AbsExecBase
   XREF   _MyBeginIO
   XREF   _MyClose
   XREF   _RealClose

   XDEF   _myBeginIO
   XDEF   _myClose
   XDEF   _myExpunge

   CODE


_myBeginIO:
   movem.l d0-d7/a0-a6,-(a7)   ;save registers

   move.l  a1,-(a7)            ;push ptr to io request
   jsr     _MyBeginIO
   addq.l  #4,a7

   movem.l (a7)+,d0-d7/a0-a6   ;restore registers
   rts


_myClose:
   movem.l d0-d7/a0-a6,-(a7)   ;save registers

   move.l  a1,-(a7)            ;push ptr to io request
   jsr     _MyClose
   addq.l  #4,a7

   movem.l (a7)+,d0-d7/a0-a6   ;restore registers

   move.l  _RealClose,a0       ;continue to real Close
   jmp     (a0)


_myExpunge:
   moveq.l #0,d0               ;means unable to expunge
   rts                         ;keep changed device from being expunged

   END
SHAR_EOF
cat << \SHAR_EOF > cmd.c
/* Cmd.c --- v3 --- Carolyn Scheppner  CBM  05/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.
 *
 * v2 mods: changes to MyBeginIO for -1 and 0 length CMD_WRITES, usage
 * v3 mods: added buffering of small writes to speed file IO
 *
 * Linkage info (requires assembler module cmda):
 *    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 "libraries/dos.h" 
#include "libraries/dosextens.h"
#include "workbench/startup.h"
#include "workbench/workbench.h"
#include "devices/serial.h" 
#include "devices/parallel.h" 
 
#define TOUPPER(c)      ((c)>='a'&&(c)<='z'?(c)-'a'+'A':(c)) 
#define HIGHER(x,y)     ((x)>(y)?(x):(y))
#define WBUFLEN   2048L
#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 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 v3";

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;


VOID MyBeginIO(ior)
struct IOStdReq *ior;
   {
   reqcnt += 1;
   if(ior->io_Command == CMD_WRITE)
      {
      if(!writecnt)
         {
         while(MainBusy);
         MainBusy = TRUE;
         Signal(mainTask,OPEN_SIG);
         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>0)||(ior->io_Length > SHORT_WRITE))
         {
         if(ior->io_Length)
            {
            while(MainBusy);
            MainBusy = TRUE;
            ioR = ior;
            Signal(mainTask,WRITE_SIG);  /* Signal write */
            while(MainBusy);
            }
         }
      writecnt += 1;
      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(reqcnt) /* 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, MEMF_PUBLIC|MEMF_CLEAR)))
      cleanexit("Can't allocate write buffer\n");

   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;
            wi = 0;        /* Index into write buffer */
            Error1 = TRUE;

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

            }
         }

      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;
            }
         }

      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);

            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);
               }
            }
         }

      if(signals & BREAK_SIG)
         {
         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;

   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);

   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 */

SHAR_EOF
#	End of shell archive
exit 0