/*
 *  HARCOPY.C    copy CLI output to file as well as window.
 *
 *  By Davide P. Cervone, Copywrite (c) 1987
 *
 *  Based heavily on a source by Phillip Lindsay, (c) 1987 Commodore-Amiga, Inc.
 *  You may use this source as long as this copywrite notice is left intact.
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/semaphores.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <stdio.h>

#ifdef MANX
#include <functions.h>
#endif

/*
 *  AmigaDOS uses task signal bit 8 for message signaling
 */

#define ONE          1L
#define DOS_SIGNAL   8
#define DOS_MASK     (ONE<<DOS_SIGNAL)


/*
 *  Cast a pointer to become a structure pointer
 */

#define PTR(x,p)     ((struct x *)(p))


/*
 *  AmigaDOS packet types we're interested in viewing
 */

#define _ACTION_READ              82L
#define _ACTION_WRITE             87L


/*
 *  Short-hand for the parts of the DosPacket
 */

#define ARG1    pkt->dp_Arg1
#define ARG2    pkt->dp_Arg2
#define RES1    pkt->dp_Res1


/*
 *  Program functions
 */

#define SHOW_USAGE       0
#define START_HARDCOPY   1
#define DO_MONITOR       2
#define END_HARDCOPY     3


/*
 *  External routines and variables
 */

extern LONG AllocSignal(), Wait();
extern struct FileHandle *Open();
extern struct Message *PacketWait(), *GetMsg();
extern struct Process *FindTask();
extern struct DosLibrary *DOSBase;
extern void GetDateTime();


/*
 *  Our own variables
 */

static char *version   = "HARDCOPY v1.0, 4/25/87";
static char *copywrite = "Copywrite (c) 1987 by Davide P. Cervone";

struct MsgPort         *thePort;        /* the port we will be monitoring */
struct Process         *myProcess;      /* pointer to our own process */
struct Process         *ChosenProcess;  /* the process we are monitoring */
ULONG                  WaitMask;        /* the task signal mask */
struct SignalSemaphore CanReturn = {0}; /* coordinates PacketWait */
struct Message         *theMessage;     /* the message we received */
APTR                   OldPktWait;      /* the old pr_PktWait routine */
APTR                   OldUserData;     /* tc_UserData for monitored task */


#ifdef MANX
   LONG PWait();        /* this is the ASM stub that calls PacketWait() */
#else
   #define PWait   PacketWait
#endif

#ifndef MANX
   Ctrl_C()             /* Control-C Trap routine for Lattice */
   {
      return(0);
   }
#endif


/*
 *  PacketWait()
 *
 *  This is the routine placed in the pr_PktWait field of the monitored
 *  precess.  It is run asynchronously by the monitored process, and is
 *  called whenever AmigaDOS does a taskwait().  PacketWait() waits for
 *  a message to come in and then signals the monitoring task that one has
 *  arrived.  It then attempts to obtain the semaphore, which will not be
 *  released by the monitoring process until it is finished printing the 
 *  contents of the packet.
 */

struct Message *PacketWait()
{
#ifdef MANX
   /*
    *  if MANX, make sure we can see our data
    */
   geta4();
#endif

   SetSignal(FALSE,DOS_MASK);

   while(!(theMessage = GetMsg(thePort)))  Wait(DOS_MASK);

   Signal(myProcess,WaitMask);
   ObtainSemaphore(&CanReturn);
   ReleaseSemaphore(&CanReturn);

   return(theMessage);
} 


/*
 *  printBUF()
 *
 *  Prints a buffer to stdout.
 */

void printBUF(buf,len)
char *buf;
int len;
{
   short i;
   char outbuf[81];

   while (len > 0)
   {
      for (i=0; i<80 && len>0; i++,len--) outbuf[i] = *buf++;
      outbuf[i] = '\0';
      printf("%s",outbuf);
   }
}


/*
 *  PrintPkt()
 *
 *  For READ and WRITE packets to/from the CON: window, print the buffer
 *  to stdout.  We recognize CON: packets because ARG1 is zero (this is
 *  a real kludge, but it seems to work, except we get an extra ENDCLI 
 *  if you run HARDCOPY, then EMACS, then spawn a new CLI, then type ENDCLI
 *  to get back to EMACS.  Can't figure that one out).
 */

void PrintPkt(pkt)
struct DosPacket *pkt;
{
   switch(pkt->dp_Type)
   {
      case _ACTION_READ:
         if (ARG1 == 0) printBUF(ARG2,RES1);
         break;

      case _ACTION_WRITE:
         if (ARG1 == 0) printBUF(ARG2,RES1);
         break;

      default:      /* Ignore anything else */
         break;
   }
}


/*
 *  GetFunction()
 *
 *  Check the command-line arguments to see that they are valid.
 *  The legal possibilities are:
 *
 *      TO <filename>       To begin HARDCOPY to a file
 *      END                 To end the HARDCOPY session
 *      MONITOR <procID>    To begin menitoring the process pointed to by
 *                          <procID>.
 */

GetFunction(argc,argv)
int argc;
char *argv[];
{
   int function = SHOW_USAGE;

   if (argc == 3 && stricmp(argv[1],"TO") == 0)       function = START_HARDCOPY;
   else if (argc == 3 && strcmp(argv[1],"MONITOR") == 0 &&
           sscanf(argv[2],"%x",&ChosenProcess) == 1)  function = DO_MONITOR;
   else if (argc == 2 && stricmp(argv[1],"END") == 0) function = END_HARDCOPY;
   return(function);
}


/*
 *  SetupSignal()
 *
 *  Allocate a signal to use for our inter-task communication, and
 *  set up the mask for using it.
 */

void SetupSignal(theSignal)
LONG *theSignal;
{
   *theSignal = AllocSignal(-ONE);
   if (*theSignal == -ONE)
   {
      printf("Can't Allocate a Task Signal.\n");
      exit(10);
   }
   WaitMask = (ONE << (*theSignal));
}


/*
 *  SetupProcess()
 *
 *  Copy the process' name, and get its Message port.  Set our priority
 *  higher than the monitored process so we will be able to react to its
 *  signals, then set the pr_PktWait field to our PacketWiat() routine so
 *  that we will be signalled when it receives a packet (save the old 
 *  pr_PktWait so we can put it back when we're through).  Set the tc_UserData
 *  field of the monitored processes Task structure to point to us, so that
 *  the monitored process can signal us when it wants us to stop hardcopying.
 *  Finally, send a signal to the monitored process to show that we are ready
 *  to monitor it.
 */

void SetupProcess(theProcess,name)
struct Process *theProcess;
char *name;
{
   strcpy(name,theProcess->pr_Task.tc_Node.ln_Name);
   thePort = &theProcess->pr_MsgPort;

   Forbid();
   SetTaskPri(myProcess,(ULONG)(theProcess->pr_Task.tc_Node.ln_Pri + 1)); 
   OldPktWait = theProcess->pr_PktWait;
   theProcess->pr_PktWait = (APTR) PWait;
   OldUserData = PTR(Task,theProcess)->tc_UserData;
   PTR(Task,theProcess)->tc_UserData = (APTR) myProcess;
   Permit();
   Signal(theProcess,SIGBREAKF_CTRL_C);
}


/*
 *  MonitorProcess()
 *
 *  Wait for the monitored process to receive a message (our PacketWait()
 *  function signals us via theSignal when it has received a message), then
 *  print out the contents of the message.  A semaphore is used to coordinate
 *  this routine with the PacketWait() routine (which is run asynchonously
 *  by the monitored process).  Phillip Lindsay says "there are probably a
 *  hundred better was of doing this.  I just went with the first one [that]
 *  came to mind."  I couldn't think of a better one, so I still use it.
 *  Since our process is running at a higher priority than the monitored one,
 *  we should obtain the semaphore first.  The other process will block until
 *  we release it (when we are done printing the contents).
 */

void MonitorProcess(name,theSignal)
char *name;
ULONG theSignal;
{
   ULONG signals;
   struct DosPacket *thePacket;

   do 
   {
      signals = Wait(SIGBREAKF_CTRL_C | WaitMask); 
      ObtainSemaphore(&CanReturn); 
      if (signals & WaitMask)
      {
         /*
          *  PacketWait() signalled us so print the message it put in
          *  theMessage.
          */
         thePacket = PTR(DosPacket,theMessage->mn_Node.ln_Name);
         PrintPkt(thePacket);
      }
      ReleaseSemaphore(&CanReturn);
   } while(!(signals & SIGBREAKF_CTRL_C)); 
}


/*
 *  ClenUpProcess()
 *
 *  Put everything back the way we found it, except that the monitored process
 *  is still running our code...
 */

void CleanUpProcess(theProcess)
struct Process *theProcess;
{
   Forbid();
   theProcess->pr_PktWait = OldPktWait;
   PTR(Task,theProcess)->tc_UserData = OldUserData;
   Permit();
   SetTaskPri(myProcess,0L);
}


/*
 *  DoMonitor()
 *
 *  Get a signal for our PacketWait code to use to signal us when a packet is
 *  ready for us to look at.  Get a semaphore so that we can make the 
 *  PacketWait() routine wait for us to finish with the packet before it
 *  returns the message to the monitored process.  Set up the process so 
 *  that it includes our PacketWait() code.
 *
 *  Monitor the packet traffic, and print the I/O to the monitored CLI.
 *  We wait for a signal from the PacketWait() routine, or for a 
 *  CTRL-C.
 *
 *  When we get a CTRL-C, we are done monitoring, so we remove our
 *  PacketWait() code, but the monitored process may still be in our 
 *  waiting code, so we wait for a CTRL-E to verify that we are free to
 *  die (and remove the PacketWait code from memory).
 */

void DoMonitor()
{
   LONG TaskSignal;
   UBYTE ProcessName[81];
   
   myProcess = FindTask(NULL);
   if (ChosenProcess != NULL)
   {
      #ifndef MANX
         onbreak(&Ctrl_C);    /* Turn off CTRL-C for Lattice:  we do our own */
      #endif

      SetupSignal(&TaskSignal);
      InitSemaphore(&CanReturn);
      SetupProcess(ChosenProcess,ProcessName);
   
      MonitorProcess(ProcessName,TaskSignal);
      
      CleanUpProcess(ChosenProcess);
      Wait(SIGBREAKF_CTRL_E);
      FreeSignal(TaskSignal);
   }
}


/*
 *  StartHardCopy()
 *
 *  Creates the process that monitors the current process via a call to
 *  Execute().  We execute a RUN command that runs HARDCOPY MONITOR and
 *  passes a pointer to the current process as a parameter.  When HARDCOPY
 *  starts as the remote process, it looks up this parameter and monitors
 *  that process.  The output for HARDCOPY is re-directed to the file
 *  that the user specified in the initial call to HARDCOPY.
 *
 *  The spawned process will signal us with a CTRL-C when it is set up, 
 *  so wait for that signal.  Finally, print out a message; this will
 *  appear as the first thing in the output file.
 */

void StartHardCopy(file)
char *file;
{
   char cmd[200],time[19];
   #define COMMAND "RUN <NIL: >NIL: HARDCOPY <NIL: >\"%s\" MONITOR 0x%X"

   sprintf(cmd,COMMAND,file,FindTask(NULL));
   if (!Execute(cmd,NULL,NULL))
   {
      printf("Can't create HARDCOPY process\n");
    } else {
      #ifndef MANX
         onbreak(&Ctrl_C);  /* Turn off CTRL-C for Lattice:  we do our own */
      #endif
      printf("Waiting for HARDCOPY monitor to start ...\n");
      printf("[Press CTRL-E if any errors are reported]\n");
      if (Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E) == SIGBREAKF_CTRL_E)
      {
         printf("\nUser signalled abort!\n\n");
         printf("To remove an unwanted HARDCOPY monitor process, first try\n");
         printf("giving the command HARDCOPY END.  If that doesn't work,\n");
         printf("use STATUS to identify the process, and then use the BREAK\n");
         printf("command to send a CTRL-C and then a CTRL-E to the HARDCOPY\n");
         printf("monitor process.\n\n");
      } else {
         GetDateTime(time);
         printf("\nHARDCOPY v1.0 recorded on %s to file \"%s\"\n",
            time,file);
         printf("To end the HARDCOPY session and close the file, ");
         printf("type HARDCOPY END\n\n");
      }
   }
}


/*
 *  EndHardCopy()
 *
 *  Sends a CTRL-C and a CTRL-E to the monitoring HARDCOPY process, which 
 *  tell it to de-install the PacketWait() routine, and then die.  The
 *  monitoring process has stored its address in the UserData field of
 *  our process' Task structure, so we know were to send the signals.
 */

void EndHardCopy()
{
   struct Process *MonitoringProcess;

   myProcess = FindTask(NULL);
   MonitoringProcess = PTR(Process,PTR(Task,myProcess)->tc_UserData);
   if (MonitoringProcess == NULL)
   {
      printf("No HARDCOPY process in progress\n");
   } else {
      Signal(MonitoringProcess,SIGBREAKF_CTRL_C);
      printf("HARDCOPY output complete.\n");
      Signal(MonitoringProcess,SIGBREAKF_CTRL_E);
   }
}

void main(argc,argv)
int argc;
char *argv[];
{
   ChosenProcess = NULL;
   switch(GetFunction(argc,argv))
   {
      case SHOW_USAGE:
         printf("Usage:  HARDCOPY TO <file>\n");
         printf("   or:  HARDCOPY END\n");
         break;

      case START_HARDCOPY:
         StartHardCopy(argv[2]);
         break;

      case DO_MONITOR:
         DoMonitor();
         break;

      case END_HARDCOPY:
         EndHardCopy();
         break;
   }
}


/*
 *  GetStrTime(time,date)
 *
 *  translates the DateStamp stored in "date" into a character string and 
 *  copies it into the character string pointed to by "time", which should be 
 *  at least 19 characters long.  GetStrTime properly accounts for leap years
 *  every four years.  Every four centuries, however, a leap day is supposed
 *  to be skipped.  AmigaDOS does not correctly interpret these non-leap
 *  centuries, hence neither does GetStrTime.  In the event that AmigaDOS is 
 *  ever corrected to fix this bug, you can remove the comment delimiters from
 *  the line in GetStrTime that implements non-leap centuries.  Unfortunately,
 *  the year 2000 is a non-leap century, hence this bug may actually
 *  come into play (if anyone still has Amigas in 14 years).
 */

static int
   DaysIn[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};

static char
   *NameOf[] = {"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

#define CENTURY             (100 YEARS + 25 LEAPDAYS)
#define FOURCENTURIES       (400 YEARS + 99 LEAPDAYS)
#define FOURYEARS           (4 YEARS + 1 LEAPDAY)
#define LEAPYEAR            (1 YEARS + 1 LEAPDAY)
#define LEAPDAYS
#define LEAPDAY
#define YEARS               * 365
#define YEAR                365
#define FEBDAYS             59          /* days until the end of February */

void GetStrTime(time,date)
char time[19];             /* storage area for the return value */
struct DateStamp *date;    /* the DateStamp to convert */
{
   int year,month,day,hour,minute,second;

   day = date->ds_Days + 78 YEARS + 20 LEAPDAYS;
/* day += (day - FEBDAYS - CENTURY + FOURCENTURIES) / FOURCENTURIES; */
   year = 4 * (day/FOURYEARS) + 1900;
   day %= FOURYEARS;
   day += (day - FEBDAYS - 1 LEAPDAY) / YEAR;
   year += day / LEAPYEAR;
   day %= LEAPYEAR;

   for (month=1; day >= DaysIn[month]; month++);
   day = day - DaysIn[month-1] + 1;

   hour   = date->ds_Minute / 60;
   minute = date->ds_Minute - hour*60;
   second = date->ds_Tick / TICKS_PER_SECOND;

   sprintf(time,"%02d-%3s-%02d %02d:%02d:%02d",
      day,NameOf[month],(year % 100),hour,minute,second);
}


/*
 *  GetDateTime(str)
 *
 *  Uses GetStrTime() to get the current date and time as a string.
 *  "str" must be at least 19 characters long.  See GetStrTime for
 *  more information.
 */

void GetDateTime(str)
char *str;
{
   struct DateStamp date;

   DateStamp(&date);
   GetStrTime(str,&date);
}


/*
 *  This code stub has been known to save lives... 
 */

#if MANX        
#asm
        XREF _PacketWait

        XDEF _PWait
_PWait:
        movem.l a2/a3/a4,-(sp)
        jsr _PacketWait
        movem.l (sp)+,a2/a3/a4
        rts
#endasm
#endif
