[comp.sys.amiga] HARDCOPY of CLI sessions

dpvc@ur-tut.UUCP (05/11/87)

[If you can read this, there ain't no line-eater bug]

Well, here's a little program that uses Phillip Lindsay's MONPROC idea to
create a hardcopy transcript of any CLI session.  See the documentation for
details.  I thought it made a good, real-life example of the techniques used
in MONPROC.  Hope someone finds this useful!

Davide P. Cervone
University of Rochester Computing Center            DPVC@UORDBV.BITNET
Taylor Hall                                         dpvc@tut.cc.rochester.EDU
Rochester, New York  14627                          dpvc@ur-tut.UUCP

#	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:
#	hardcopy.doc
#	hardcopy.c
# This archive created: Fri May 08 21:18:43 1987
# By:	 (Davide P. Cervone)
sed 's/^X//' << \SHAR_EOF > hardcopy.doc
XOVERVIEW:
X
XHARDCOPY is a program that "clones" the CLI output stream and sends it to two
Xplaces at one time:  the normal CLI window, and a file.  With it, you can
Xcreate a hardcopy listing of all the activity that went on in a CLI window. 
XThis can be useful for documentation purposes, error analysis, and error
Xreporting.  The advantage of using HARDCOPY instead of output-redirection is
Xthat you still see the output in the CLI window (as well as capture it in a 
Xfile).  This makes getting a hardcopy listing of an interactive session much 
Xeasier.  HARDCOPY's major drawback is that it only copies text that goes to 
Xor from the CLI window; it does not record any of the activity that occurs in 
Xprograms that open their own Intuition windows.  It will, however, record all 
Xdata entered in CON and RAW windows opened from the CLI window from which a
XHARDCOPY is being made.
X
X
XUSING HARDCOPY:
X
XTo use HARDCOPY, type:
X
X    1> HARCOPY TO <file>
X
Xwhere <file> is the name of a file where HARDCOPY will record your CLI
Xsession.  This can be on disk, in RAM:, or even directly to PRT:.  Note that
XHARDCOPY will be writing to this file every time something happens in your CLI
Xwindow, so if you are HARDCOPYing to disk, make sure you select a disk that
Xcan stay in a disk drive!
X
XHARDCOPY will try to start up a new process that will monitor your CLI and
Xcopy its output to the file you have selected.  In order to start the
Xmonitoring process, HARDCOPY uses the Execute() function.  This function
Xrequires the RUN command to be in the C: directory.  If your workbench disk is
Xnot in a drive, you will get a disk-insert requester, so don't be alarmed.
X
XOnce the monitoring process is started, HARDCOPY waits for it to signal that
Xit is ready to begin copying to the file (you will receive a message informing 
Xyou of this).  In a few seconds, you should get another message that says the
Xhardcopy session is beginning.  
X
XIf you get an error message (like "Unknown command HARDCOPY"), or if HARDCOPY 
Xseems to hang (it does not return to the CLI prompt), then follow the 
Xinstructions under ERROR RECOVERY.
X
XOnce the HARDCOPY monitor process is under way, you should be returned to the
XCLI prompt.  At this point you can enter commands normally.  All the input
Xfrom and output to the CLI window (or any CON or RAW window opened from the
XCLI) will be routed to the file as well as the CLI window.
X
XWhen you want to stop sending HARDCOPY output to the file, issue the command
X
X    1> HARDCOPY END
X
XThis will signal the monitor process to close the file and stop running.  You
Xshould receive a message informing you that the HARDCOPY transcript is
Xcomplete.  At this point, you can use the output file in whatever way you
Xchoose.
X
X
XUSAGE NOTES:
X
XTo avoid disk swapping due to the HARDCOPY process, make sure the HARDCOPY
Xoutput file is on a disk that will remain in one of the disk drives.  RAM: is
Xgood for small HARDCOPY sessions, but it can fill up faster than you think!
X
XDon't HARDCOPY TO *.  When anything appears in your CLI window, HARDCOPY will
Xtry to write it to the output file.  Since the output file is your CLI,
XHARDCOPY will see its own output as something that must be copied.  This will
Xcause am infinite loop, or a deadlock situation, depending on when the output
Xoccurs.
X
XDon't ENDCLI without first ending your HARDCOPY session.  If you do, the CLI
Xwindow won't close (since HARDCOPY is still using it), but you will not be 
Xable to enter any more commands.  See the ERROR RECOVERY section for 
Xinformation on what to do if this occurs.
X
XHARDCOPY uses the Execute() call, which requires the RUN command to be in the C:
Xdirectory.  HARDCOPY executes the HARDCOPY command, so the HARDCOPY command
Xitself must be in the current directory, or in the current PATH searched by
Xthe Execute() call.
X
XYou can not TYPE, EDIT, or otherwise look at or change the HARDCOPY output file
Xuntil after you issue the HARDCOPY END command.
X
XYou can not have more than one HARDCOPY session going to the same file.
X
XHARDCOPY creates a new version of the file each time it is invoked, so it will
Xdestroy the current version of the specified output file, if one already exists.
X
X
XERROR RECOVERY:
X
XThere are two kinds of errors that can occur:  HARDCOPY may fail to start the
Xmonitoring process, or something may happen to your CLI so that you need to end
Xthe HARDCOPY monitor "by hand".
X
XWhen you start HARDCOPY, it spawns a new process to do the monitoring for
Xyou.  HARDCOPY waits to make sure that this process is running and ready to
Xrecord your output before it lets you enter more commands (it informs you of
Xthis with a "waiting" message).  
X
XIf an error message appears while HARDCOPY is waiting, or if HARDCOPY never 
Xcompletes (you don't get the CLI prompt back), then you should wait till all
Xthe disk activity (if any) stops, and then press CTRL-E.  This will stop 
XHARDCOPY from waiting for the monitor process to start up (this may leave a
Xmonitor process active).
X
XUse the STATUS command to check that there is no HARDCOPY monitor process 
Xrunning.  If there is, try typing HARDCOPY END.  If that does not work, then 
Xuse the BREAK command to send a CTRL-C and then a CTRL-E to the HARDCOPY 
Xmonitor process (see the example below).
X
XIf something happens to your CLI so that you can not enter the HARDCOPY END
Xcommand (for instance, you type ENDCLI while there is a HARDCOPY session in
Xprogress), then you can stop HARDCOPY from another CLI.  Use the STATUS
Xcommand to find the process number of the HARDCOPY monitor.  Then use BREAK to
Xsend it a CTRL-C.  If the monitored CLI is still active, make sure some input
Xor output occurs to the window (unfortunately, type-ahead does not qualify as
Xinput).  Once you have done this (or if it is not possible), then use BREAK 
Xagain to send a CTRL-E to the HARDCOPY process.  Use STATUS to verify that the 
Xprocess really is gone.
X
XFor example, suppose you have two CLIs running (1 and 2), and had a HARDCOPY 
Xsession in progress for CLI number 2, but forgot about it and typed ENDCLI
Xwithout having ended the HARDCOPY session.  You should do the following (from
XCLI number 1):
X
X    1> STATUS                                    ; see what processes there are
X    Task  1: Loaded as command: STATUS           ; this CLI
X    Task  4: Loaded as command: HARDCOPY         ; HARDCOPY monitor process
X
X    1> BREAK 4 ALL                               ; send it a CTRL-C and CTRL-E
X
X    1> STATUS                                    ; check what we've done
X    Task  1: Loaded as command: STATUS
X    1>                                           ; monitor is gone!
X
XNow suppose you have two CLIs running (1 and 2), and had a HARDCOPY session in
Xprogress for CLI number 2, but ran a program that entered an infinite loop, so
Xyou can not end the HARDCOPY session by typing HARDCOPY END.  You should do 
Xthe following (from CLI number 1):
X
X    1> STATUS                                    ; see what processes there are
X    Task  1: Loaded as command: STATUS           ; this CLI
X    Task  2: Loaded as command: LoopForEver      ; CLI 2 in an infinite loop
X    Task  4: Loaded as command: HARDCOPY         ; HARDCOPY monitor process
X
X    1> BREAK 4 C                                 ; send monitor a CTRL-C
X    ; click in CLI window 2, and force some I/O to occur;
X    ; click back in CLI window 1;
X
X    1> STATUS                                    ; check what we've done
X    Task  1: Loaded as command: STATUS
X    Task  2: Loaded as command: LoopForEver      ; nothing we can do about this
X    1>                                           ; but monitor is gone!
X
X
XUSES FOR HARDCOPY:
X
XI see HARDCOPY as a useful method of documenting CLI functions.  For instance, 
Xit can be used to make a transcript of what people have to do to start up your
Xprogram or customize their environment to make your program more useful, or
Xwhat they have to do the move your program to a hard disk, etc.
X
XHARDCOPY can be used to make an error log during compilation of programs when
Xyou want to see the errors AND record them in a file (where you can use an
Xeditor to look at the error log at the same time as the source code).
X
XIf you are running a program that sets up its own screen and eventually
Xcrashes, you can have it use printf() to send diagnostic messages to the CLI
Xwindow, and have them recorded to a file even if the program crashes before
Xyou can flip the screen to look at the CLI window.
X
XYou can use HARDCOPY to capture the exact input you used to produce an error,
Xso that you can report the error easier (and so people will believe that it
Xreally occured).
X
XYou can HARDCOPY TO PRT: to produce a printed listing of your work as it
Xhappens.  
X
XHARDCOPY can be used to have your CLI session show up in two CON windows
Xsimultaneously.  (Pretty useless, but it looks neat!  Use
X
X    1> HARDCOPY TO CON:0/0/640/100/Hardcopy
X
Xto see what I mean.)
X
XI expect there are other uses, but I can't think of them.  I wrote it mostly
Xas an exercise, and because I've always wanted to be able to do this on a VAX
Xrunning VMS, but never could.  If you come up with interesting uses, please 
Xlet me know.
X
X
XTECHNICAL NOTES:
X
XHARDCOPY is based on MONPROC by Phillip Lindsay of Commodore-Amiga, and is an
Xexample of a real-life program that uses his method to monitor the AmigaDOS
Xactivity of a process.  It was the simplest and most useful example I could
Xthink of, but undoubtedly there are others.
X
XPhillip pointed out that the pr_PktWait field of the Process structure is used
Xfor an alternate taskwait() routine.  AmigaDOS calls this routine whenever a
Xprocess is waiting for AmigaDOS messages to be issued or returned.  HARDCOPY
Xinstalls its own code in this field.  This code waits for AmigaDOS packets to
Xarrive, and then signals a monitoring process.  This process checks to see if
Xthe packet is to or from the CLI window.  If it is, it writes the contents of
Xthe packet's data buffer to the output file.  Then it signals the pr_PktWait
Xroutine that it is through writing (via a semaphore).  At that point, the
Xpacket wait routine returns the packet to the process for its own use.
X
XHARDCOPY assumes that all READ and WRITE packets with dp_Arg1 fields equal to
Xzero are CLI I/O packets.  This assumption probably is wrong, but it holds in
Xenough cases to make this program useful.  If someone finds cases where this
Xfails, please let me know.
X
XThe HARDCOPY TO <file> command simply sets up the monitoring process and
Xpasses it a pointer to the process to be monitored (via an undocumented 
Xcommand-line argument).  The monitor process signals HARDCOPY TO that it is
Xready via a CTRL-C (HARDCOPY TO is waiting for either the CTRL-C from the
Xmonitor or a CTRL-E from the user).
X
XThe monitor process runs as a separate process, but the CLI process executes
Xthe pr_PktWait routine, which shares data with the HARDCOPY monitor process,
Xincluding the signal and semaphore used to coordinate the activity of the
Xpacket wait routine and the HARDCOPY process.  This gets to be somewhat
Xconfusing when you look at the code, so try to remember that the PacketWait()
Xroutine (and the StartHardCopy() and EndHardCopy() routines) run in the
Xmonitored CLI process, while the rest of the program runs in the monitoring
XHARDCOPY process, asynchronously.
X
XWhen the user wants to end the hardcopy session, he runs HARDCOPY END, which
Xsends a CTRL-C to the monitor (the monitor stores a pointer to itself in the
Xtc_UserData field of the monitored CLI task; HARDCOPY END looks here for the 
Xaddress of the task to signal).
X
XWhen the monitor process receives the CTRL-C, it de-installs the pr_PktWait
Xcode.  At this point, since HARDCOPY END is running in the CLI window, and
Xsince it is not performing a taskwait(), it is safe to remove the pr_PktWait
Xcode (that is, the CLI process is not currently running it).  Just to be sure,
Xhowever, the monitor waits for a CTRL-E.  HARDCOPY END writes a message that
Xthe session is complete (ensuring that pr_PktWait is not in use anymore), and
Xsignals the CTRL-E to the monitor process.  
X
XThe extra CTRL-E is there mainly so that you can stop HARDCOPY monitors 
X"by hand" using the BREAK command.  The time between the BREAK C and BREAK E
Xcommands is used to make sure some I/O takes place in the monitored CLI
Xwindow.  This ensures that the monitored process is no longer waiting in the
XPacketWait() routine when HARDCOPY receives the CTRL-E and its code is removed
Xfrom memory.
X
X
XHOW TO COMPILE AND LINK HARDCOPY:
X
XHARDCOPY was developed using the Lattice C Compiler version 3.10.  Phillip
XLindsay's code was originally designed for the Manx Aztec C compiler, and I
Xhave tried to keep it as compatable as possible.  I do not own the Manx
Xcompiler, however, so I don't know that it still works with it.  I suspect
Xthat it will need nothing more than minor adjustments.
X
XTo compile and link with Lattice, use the command:
X
X    1> LC -v -L HARDCOPY
X
XThe -v suppresses stack overflow checking.  THIS IS CRITICAL!  HARDCOPY will
Xfail if it is not compiled in this fashion.  I do not believe that Manx has
Xoverflow checking.  
X
XThe executable should be placed in the C: directory.
X
X
XAUTHOR:
X
XDavide P. Cervone                                   DPVC@UORDBV.BITNET
XUniversity of Rochester Computing Center            dpvc@tut.cc.rochester.EDU
XTaylor Hall                                         dpvc@ur-tut.UUCP
XRochester New York  14627
X(716) 275-2811
SHAR_EOF
sed 's/^X//' << \SHAR_EOF > hardcopy.c
X/*
X *  HARCOPY.C    copy CLI output to file as well as window.
X *
X *  By Davide P. Cervone, Copywrite (c) 1987
X *
X *  Based heavily on a source by Phillip Lindsay, (c) 1987 Commodore-Amiga, Inc.
X *  You may use this source as long as this copywrite notice is left intact.
X */
X
X#include <exec/types.h>
X#include <exec/ports.h>
X#include <exec/semaphores.h>
X#include <libraries/dos.h>
X#include <libraries/dosextens.h>
X#include <stdio.h>
X
X#ifdef MANX
X#include <functions.h>
X#endif
X
X/*
X *  AmigaDOS uses task signal bit 8 for message signaling
X */
X
X#define ONE          1L
X#define DOS_SIGNAL   8
X#define DOS_MASK     (ONE<<DOS_SIGNAL)
X
X
X/*
X *  Cast a pointer to become a structure pointer
X */
X
X#define PTR(x,p)     ((struct x *)(p))
X
X
X/*
X *  AmigaDOS packet types we're interested in viewing
X */
X
X#define _ACTION_READ              82L
X#define _ACTION_WRITE             87L
X
X
X/*
X *  Short-hand for the parts of the DosPacket
X */
X
X#define ARG1    pkt->dp_Arg1
X#define ARG2    pkt->dp_Arg2
X#define RES1    pkt->dp_Res1
X
X
X/*
X *  Program functions
X */
X
X#define SHOW_USAGE       0
X#define START_HARDCOPY   1
X#define DO_MONITOR       2
X#define END_HARDCOPY     3
X
X
X/*
X *  External routines and variables
X */
X
Xextern LONG AllocSignal(), Wait();
Xextern struct FileHandle *Open();
Xextern struct Message *PacketWait(), *GetMsg();
Xextern struct Process *FindTask();
Xextern struct DosLibrary *DOSBase;
Xextern void GetDateTime();
X
X
X/*
X *  Our own variables
X */
X
Xstatic char *version   = "HARDCOPY v1.0, 4/25/87";
Xstatic char *copywrite = "Copywrite (c) 1987 by Davide P. Cervone";
X
Xstruct MsgPort         *thePort;        /* the port we will be monitoring */
Xstruct Process         *myProcess;      /* pointer to our own process */
Xstruct Process         *ChosenProcess;  /* the process we are monitoring */
XULONG                  WaitMask;        /* the task signal mask */
Xstruct SignalSemaphore CanReturn = {0}; /* coordinates PacketWait */
Xstruct Message         *theMessage;     /* the message we received */
XAPTR                   OldPktWait;      /* the old pr_PktWait routine */
XAPTR                   OldUserData;     /* tc_UserData for monitored task */
X
X
X#ifdef MANX
X   LONG PWait();        /* this is the ASM stub that calls PacketWait() */
X#else
X   #define PWait   PacketWait
X#endif
X
X#ifndef MANX
X   Ctrl_C()             /* Control-C Trap routine for Lattice */
X   {
X      return(0);
X   }
X#endif
X
X
X/*
X *  PacketWait()
X *
X *  This is the routine placed in the pr_PktWait field of the monitored
X *  precess.  It is run asynchronously by the monitored process, and is
X *  called whenever AmigaDOS does a taskwait().  PacketWait() waits for
X *  a message to come in and then signals the monitoring task that one has
X *  arrived.  It then attempts to obtain the semaphore, which will not be
X *  released by the monitoring process until it is finished printing the 
X *  contents of the packet.
X */
X
Xstruct Message *PacketWait()
X{
X#ifdef MANX
X   /*
X    *  if MANX, make sure we can see our data
X    */
X   geta4();
X#endif
X
X   SetSignal(FALSE,DOS_MASK);
X
X   while(!(theMessage = GetMsg(thePort)))  Wait(DOS_MASK);
X
X   Signal(myProcess,WaitMask);
X   ObtainSemaphore(&CanReturn);
X   ReleaseSemaphore(&CanReturn);
X
X   return(theMessage);
X} 
X
X
X/*
X *  printBUF()
X *
X *  Prints a buffer to stdout.
X */
X
Xvoid printBUF(buf,len)
Xchar *buf;
Xint len;
X{
X   short i;
X   char outbuf[81];
X
X   while (len > 0)
X   {
X      for (i=0; i<80 && len>0; i++,len--) outbuf[i] = *buf++;
X      outbuf[i] = '\0';
X      printf("%s",outbuf);
X   }
X}
X
X
X/*
X *  PrintPkt()
X *
X *  For READ and WRITE packets to/from the CON: window, print the buffer
X *  to stdout.  We recognize CON: packets because ARG1 is zero (this is
X *  a real kludge, but it seems to work, except we get an extra ENDCLI 
X *  if you run HARDCOPY, then EMACS, then spawn a new CLI, then type ENDCLI
X *  to get back to EMACS.  Can't figure that one out).
X */
X
Xvoid PrintPkt(pkt)
Xstruct DosPacket *pkt;
X{
X   switch(pkt->dp_Type)
X   {
X      case _ACTION_READ:
X         if (ARG1 == 0) printBUF(ARG2,RES1);
X         break;
X
X      case _ACTION_WRITE:
X         if (ARG1 == 0) printBUF(ARG2,RES1);
X         break;
X
X      default:      /* Ignore anything else */
X         break;
X   }
X}
X
X
X/*
X *  GetFunction()
X *
X *  Check the command-line arguments to see that they are valid.
X *  The legal possibilities are:
X *
X *      TO <filename>       To begin HARDCOPY to a file
X *      END                 To end the HARDCOPY session
X *      MONITOR <procID>    To begin menitoring the process pointed to by
X *                          <procID>.
X */
X
XGetFunction(argc,argv)
Xint argc;
Xchar *argv[];
X{
X   int function = SHOW_USAGE;
X
X   if (argc == 3 && stricmp(argv[1],"TO") == 0)       function = START_HARDCOPY;
X   else if (argc == 3 && strcmp(argv[1],"MONITOR") == 0 &&
X           sscanf(argv[2],"%x",&ChosenProcess) == 1)  function = DO_MONITOR;
X   else if (argc == 2 && stricmp(argv[1],"END") == 0) function = END_HARDCOPY;
X   return(function);
X}
X
X
X/*
X *  SetupSignal()
X *
X *  Allocate a signal to use for our inter-task communication, and
X *  set up the mask for using it.
X */
X
Xvoid SetupSignal(theSignal)
XLONG *theSignal;
X{
X   *theSignal = AllocSignal(-ONE);
X   if (*theSignal == -ONE)
X   {
X      printf("Can't Allocate a Task Signal.\n");
X      exit(10);
X   }
X   WaitMask = (ONE << (*theSignal));
X}
X
X
X/*
X *  SetupProcess()
X *
X *  Copy the process' name, and get its Message port.  Set our priority
X *  higher than the monitored process so we will be able to react to its
X *  signals, then set the pr_PktWait field to our PacketWiat() routine so
X *  that we will be signalled when it receives a packet (save the old 
X *  pr_PktWait so we can put it back when we're through).  Set the tc_UserData
X *  field of the monitored processes Task structure to point to us, so that
X *  the monitored process can signal us when it wants us to stop hardcopying.
X *  Finally, send a signal to the monitored process to show that we are ready
X *  to monitor it.
X */
X
Xvoid SetupProcess(theProcess,name)
Xstruct Process *theProcess;
Xchar *name;
X{
X   strcpy(name,theProcess->pr_Task.tc_Node.ln_Name);
X   thePort = &theProcess->pr_MsgPort;
X
X   Forbid();
X   SetTaskPri(myProcess,(ULONG)(theProcess->pr_Task.tc_Node.ln_Pri + 1)); 
X   OldPktWait = theProcess->pr_PktWait;
X   theProcess->pr_PktWait = (APTR) PWait;
X   OldUserData = PTR(Task,theProcess)->tc_UserData;
X   PTR(Task,theProcess)->tc_UserData = (APTR) myProcess;
X   Permit();
X   Signal(theProcess,SIGBREAKF_CTRL_C);
X}
X
X
X/*
X *  MonitorProcess()
X *
X *  Wait for the monitored process to receive a message (our PacketWait()
X *  function signals us via theSignal when it has received a message), then
X *  print out the contents of the message.  A semaphore is used to coordinate
X *  this routine with the PacketWait() routine (which is run asynchonously
X *  by the monitored process).  Phillip Lindsay says "there are probably a
X *  hundred better was of doing this.  I just went with the first one [that]
X *  came to mind."  I couldn't think of a better one, so I still use it.
X *  Since our process is running at a higher priority than the monitored one,
X *  we should obtain the semaphore first.  The other process will block until
X *  we release it (when we are done printing the contents).
X */
X
Xvoid MonitorProcess(name,theSignal)
Xchar *name;
XULONG theSignal;
X{
X   ULONG signals;
X   struct DosPacket *thePacket;
X
X   do 
X   {
X      signals = Wait(SIGBREAKF_CTRL_C | WaitMask); 
X      ObtainSemaphore(&CanReturn); 
X      if (signals & WaitMask)
X      {
X         /*
X          *  PacketWait() signalled us so print the message it put in
X          *  theMessage.
X          */
X         thePacket = PTR(DosPacket,theMessage->mn_Node.ln_Name);
X         PrintPkt(thePacket);
X      }
X      ReleaseSemaphore(&CanReturn);
X   } while(!(signals & SIGBREAKF_CTRL_C)); 
X}
X
X
X/*
X *  ClenUpProcess()
X *
X *  Put everything back the way we found it, except that the monitored process
X *  is still running our code...
X */
X
Xvoid CleanUpProcess(theProcess)
Xstruct Process *theProcess;
X{
X   Forbid();
X   theProcess->pr_PktWait = OldPktWait;
X   PTR(Task,theProcess)->tc_UserData = OldUserData;
X   Permit();
X   SetTaskPri(myProcess,0L);
X}
X
X
X/*
X *  DoMonitor()
X *
X *  Get a signal for our PacketWait code to use to signal us when a packet is
X *  ready for us to look at.  Get a semaphore so that we can make the 
X *  PacketWait() routine wait for us to finish with the packet before it
X *  returns the message to the monitored process.  Set up the process so 
X *  that it includes our PacketWait() code.
X *
X *  Monitor the packet traffic, and print the I/O to the monitored CLI.
X *  We wait for a signal from the PacketWait() routine, or for a 
X *  CTRL-C.
X *
X *  When we get a CTRL-C, we are done monitoring, so we remove our
X *  PacketWait() code, but the monitored process may still be in our 
X *  waiting code, so we wait for a CTRL-E to verify that we are free to
X *  die (and remove the PacketWait code from memory).
X */
X
Xvoid DoMonitor()
X{
X   LONG TaskSignal;
X   UBYTE ProcessName[81];
X   
X   myProcess = FindTask(NULL);
X   if (ChosenProcess != NULL)
X   {
X      #ifndef MANX
X         onbreak(&Ctrl_C);    /* Turn off CTRL-C for Lattice:  we do our own */
X      #endif
X
X      SetupSignal(&TaskSignal);
X      InitSemaphore(&CanReturn);
X      SetupProcess(ChosenProcess,ProcessName);
X   
X      MonitorProcess(ProcessName,TaskSignal);
X      
X      CleanUpProcess(ChosenProcess);
X      Wait(SIGBREAKF_CTRL_E);
X      FreeSignal(TaskSignal);
X   }
X}
X
X
X/*
X *  StartHardCopy()
X *
X *  Creates the process that monitors the current process via a call to
X *  Execute().  We execute a RUN command that runs HARDCOPY MONITOR and
X *  passes a pointer to the current process as a parameter.  When HARDCOPY
X *  starts as the remote process, it looks up this parameter and monitors
X *  that process.  The output for HARDCOPY is re-directed to the file
X *  that the user specified in the initial call to HARDCOPY.
X *
X *  The spawned process will signal us with a CTRL-C when it is set up, 
X *  so wait for that signal.  Finally, print out a message; this will
X *  appear as the first thing in the output file.
X */
X
Xvoid StartHardCopy(file)
Xchar *file;
X{
X   char cmd[200],time[19];
X   #define COMMAND "RUN <NIL: >NIL: HARDCOPY <NIL: >\"%s\" MONITOR 0x%X"
X
X   sprintf(cmd,COMMAND,file,FindTask(NULL));
X   if (!Execute(cmd,NULL,NULL))
X   {
X      printf("Can't create HARDCOPY process\n");
X    } else {
X      #ifndef MANX
X         onbreak(&Ctrl_C);  /* Turn off CTRL-C for Lattice:  we do our own */
X      #endif
X      printf("Waiting for HARDCOPY monitor to start ...\n");
X      printf("[Press CTRL-E if any errors are reported]\n");
X      if (Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E) == SIGBREAKF_CTRL_E)
X      {
X         printf("\nUser signalled abort!\n\n");
X         printf("To remove an unwanted HARDCOPY monitor process, first try\n");
X         printf("giving the command HARDCOPY END.  If that doesn't work,\n");
X         printf("use STATUS to identify the process, and then use the BREAK\n");
X         printf("command to send a CTRL-C and then a CTRL-E to the HARDCOPY\n");
X         printf("monitor process.\n\n");
X      } else {
X         GetDateTime(time);
X         printf("\nHARDCOPY v1.0 recorded on %s to file \"%s\"\n",
X            time,file);
X         printf("To end the HARDCOPY session and close the file, ");
X         printf("type HARDCOPY END\n\n");
X      }
X   }
X}
X
X
X/*
X *  EndHardCopy()
X *
X *  Sends a CTRL-C and a CTRL-E to the monitoring HARDCOPY process, which 
X *  tell it to de-install the PacketWait() routine, and then die.  The
X *  monitoring process has stored its address in the UserData field of
X *  our process' Task structure, so we know were to send the signals.
X */
X
Xvoid EndHardCopy()
X{
X   struct Process *MonitoringProcess;
X
X   myProcess = FindTask(NULL);
X   MonitoringProcess = PTR(Process,PTR(Task,myProcess)->tc_UserData);
X   if (MonitoringProcess == NULL)
X   {
X      printf("No HARDCOPY process in progress\n");
X   } else {
X      Signal(MonitoringProcess,SIGBREAKF_CTRL_C);
X      printf("HARDCOPY output complete.\n");
X      Signal(MonitoringProcess,SIGBREAKF_CTRL_E);
X   }
X}
X
Xvoid main(argc,argv)
Xint argc;
Xchar *argv[];
X{
X   ChosenProcess = NULL;
X   switch(GetFunction(argc,argv))
X   {
X      case SHOW_USAGE:
X         printf("Usage:  HARDCOPY TO <file>\n");
X         printf("   or:  HARDCOPY END\n");
X         break;
X
X      case START_HARDCOPY:
X         StartHardCopy(argv[2]);
X         break;
X
X      case DO_MONITOR:
X         DoMonitor();
X         break;
X
X      case END_HARDCOPY:
X         EndHardCopy();
X         break;
X   }
X}
X
X
X/*
X *  GetStrTime(time,date)
X *
X *  translates the DateStamp stored in "date" into a character string and 
X *  copies it into the character string pointed to by "time", which should be 
X *  at least 19 characters long.  GetStrTime properly accounts for leap years
X *  every four years.  Every four centuries, however, a leap day is supposed
X *  to be skipped.  AmigaDOS does not correctly interpret these non-leap
X *  centuries, hence neither does GetStrTime.  In the event that AmigaDOS is 
X *  ever corrected to fix this bug, you can remove the comment delimiters from
X *  the line in GetStrTime that implements non-leap centuries.  Unfortunately,
X *  the year 2000 is a non-leap century, hence this bug may actually
X *  come into play (if anyone still has Amigas in 14 years).
X */
X
Xstatic int
X   DaysIn[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
X
Xstatic char
X   *NameOf[] = {"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
X                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
X
X#define CENTURY             (100 YEARS + 25 LEAPDAYS)
X#define FOURCENTURIES       (400 YEARS + 99 LEAPDAYS)
X#define FOURYEARS           (4 YEARS + 1 LEAPDAY)
X#define LEAPYEAR            (1 YEARS + 1 LEAPDAY)
X#define LEAPDAYS
X#define LEAPDAY
X#define YEARS               * 365
X#define YEAR                365
X#define FEBDAYS             59          /* days until the end of February */
X
Xvoid GetStrTime(time,date)
Xchar time[19];             /* storage area for the return value */
Xstruct DateStamp *date;    /* the DateStamp to convert */
X{
X   int year,month,day,hour,minute,second;
X
X   day = date->ds_Days + 78 YEARS + 20 LEAPDAYS;
X/* day += (day - FEBDAYS - CENTURY + FOURCENTURIES) / FOURCENTURIES; */
X   year = 4 * (day/FOURYEARS) + 1900;
X   day %= FOURYEARS;
X   day += (day - FEBDAYS - 1 LEAPDAY) / YEAR;
X   year += day / LEAPYEAR;
X   day %= LEAPYEAR;
X
X   for (month=1; day >= DaysIn[month]; month++);
X   day = day - DaysIn[month-1] + 1;
X
X   hour   = date->ds_Minute / 60;
X   minute = date->ds_Minute - hour*60;
X   second = date->ds_Tick / TICKS_PER_SECOND;
X
X   sprintf(time,"%02d-%3s-%02d %02d:%02d:%02d",
X      day,NameOf[month],(year % 100),hour,minute,second);
X}
X
X
X/*
X *  GetDateTime(str)
X *
X *  Uses GetStrTime() to get the current date and time as a string.
X *  "str" must be at least 19 characters long.  See GetStrTime for
X *  more information.
X */
X
Xvoid GetDateTime(str)
Xchar *str;
X{
X   struct DateStamp date;
X
X   DateStamp(&date);
X   GetStrTime(str,&date);
X}
X
X
X/*
X *  This code stub has been known to save lives... 
X */
X
X#if MANX        
X#asm
X        XREF _PacketWait
X
X        XDEF _PWait
X_PWait:
X        movem.l a2/a3/a4,-(sp)
X        jsr _PacketWait
X        movem.l (sp)+,a2/a3/a4
X        rts
X#endasm
X#endif
SHAR_EOF
#	End of shell archive
exit 0