[comp.sys.amiga] MONPROC Re-visited

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

[I'm not afraid of no line eaters!]

I was so intrigued by Phillip Lindsay's MONPROC program, I got out my
AmigaDOS Technical Reference Manual and looked up the DOS packet section.
I found that there was a lot of interesting information in the packets 
besides the packet type, so I modified MONPROC to print these fields as 
well.  For instance, it prints the data that was read or writen during 
ACTION_READ and ACTION_WRITE packets, and prints the name of the file 
associated with the Lock field for ACTION_PARENT and ACTION_LOCATE_OBJECT.

The following is the source to this modified version.  While I was
working on it, I restructured the program a bit (but the algorithms are the
same).  It is changed to such a degree, however, that I can't just post the
differences, so I'm posting the whole source.  I have retained Phillip's 
copywrite notice, as he requested.

See the documentation for usage information, especially the cautions about
monitoring file system processes (remember to use the NOLOCK option when
you do), and for compiling and linking instructions.

I found this a very useful and fun tool, and hope others do as well.

Davide P. Cervone
University of Rochester Computing Center            DPVC@UORDBV.BITNET
Taylor Hall                                         dpvc@tut.cc.rochester.EDU
Rochester, New York  14627                          dpvc@ut-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:
#	monproc.doc
#	monproc.c
#	getprocs.c
#	getdir.c
#	printpkt.c
# This archive created: Fri May 08 21:29:59 1987
# By:	 ()
sed 's/^X//' << \SHAR_EOF > monproc.doc
XOVERVIEW:
X
XMONPROC is a utility that monitors the AmigaDOS message activity for any 
Xprocess.  This is useful when debugging device handlers or other such
Xprocesses, or just for seeing how AmigaDOS works.
X
XMONPROC was originally written by Phillip Lindsay of Commodore-Amiga, Inc.
Xusing the Manx Aztec C compiler.  I have re-organized the routines, although
Xthe algorithms are basically the same, and have used the Lattice C compiler
Xversion 3.10.  I hope that it still works with Manx, but I do not own that
Xcompiler, so I can not test it.
X
XI have retained Phillips original copywrite notice, and have added my own when
Xappropriate.  I hope that this satisfies Phillip's request to use the code but
Xkeep the copywrite notice intact.  I have included his original mail message
Xat the end of this document.
X
X
XHOW TO USE MONPROC:
X
XTo start MONPROC, type
X
X    1> MONPROC
X
XMONPROC will show you a list of the processes currently running, and request
Xthat you choose one from the list by typing its number.  Once you have
Xselected a process to monitor, MONPROC inserts its own PacketWait() routine in
Xthe pr_PktWait field of the monitored process' Process structure.  Every time
Xthe monitored process calls the AmigaDOS taskwait() routine to wait for a
Xmessage to be issued or returned, MONPROC prints the contents of the message,
Xand then returns it to the monitored process for its own use.  (See the
Xcomments within the code for details of how this is done).
X
XUsually, you get lots of packets when anything interesting happens.  For
Xinstance, if you monitor a CLI process, you'll see the command-line prompts,
Xthen the command that was typed, then file lookups as the CLI searches the
Xcurrent PATH for the command, then file opens and file reads as the scatter
Xloader brings the command into memory, then a close file, and then file
Xlook ups as the re-direction files are openned, and finally the output 
Xfrom the command.
X
XIn order to slow down the printed packets so that you can read them, just
Xclick in the CLI window where the monitor is running (and the messages are
Xprinting) and press the space-bar.  This will halt the output until you 
Xpress the back-space key.  Note that the monitored process will block until
Xyou press the back-space because it is waiting for the monitor process to
Xreturn its AmigaDOS message to it.
X
XWhen you are done monitoring the process, click in the window where the
Xpackets are being displayed and press CTRL-C.  You should receive a message
Xstating that the monitor is waiting for one more packet or for a CTRL-E.
XTry to force some DOS activity to occur in the monitored process (i.e., if the
Xmonitored process is a CLI, give it a command; if it's a disk handler, read
Xsomething from the disk, etc.).  MONPROC should end when the next packet
Xarrives.
X
XIf, for some reason, you can not force an AmigaDOS message to the monitored
Xprocess, you can type CTRL-E to stop MONPROC.  The next message that arrives
Xfor the monitored processd is likely to cause it to crash, however.
X
XThe reason for this is simple, when you press CTRL-C, MONPROC removes its
XPacketWait() routine from the pr_PktWait field of the monitored process, but
Xthe monitored process probably IS STILL USING THE PacketWait() ROUTINE, so we
Xcan't let the monitor die just yet. (If we did, it's code would be removed 
Xfrom memory, and the monitored process would crash when it received the next 
Xmessage and tried to run the rest of the PacketWait() routine).  To solve this,
Xwe simply wait for one more message to be signalled, and then die.  Since
Xwe've already removed our code from pr_PktWiat, we can be assured that after
Xthe monitored process finishes using our PacketWait() routine, it will go
Xback to using the normal one, so we can delete our code from memory without 
Xcausing a crash.
X
X
XCOMMAND OPTIONS:
X
XMONPROC takes two optional arguments.  The first controls the way large data
Xbuffers are displayed, and the second controls how FileLocks are displayed.
X
XNormally, MONPROC tries to display data buffers as character strings within
Xquotes.  When the buffer is very long, however (e.g., when a disk handler is
Xreturning a request for 1728 characters from a file), MONPROC will print
Xthe data buffer as a HEX dump (HEX codes followed by ASCII representations of
Xthese codes).  Since these take up a lot of space, you might not want to see
Xthem.  To suppress HEX dumps, use the NODUMP option when calling MONPROC:
X
X    1> MONPROC NODUMP
X
XAny data buffer that is over 50 characters will not be printed when you use
Xthe NODUMP option.
X
XThe second command line option is NOLOCK.  Normally when an AmigaDOS packet
Xincludes a FileLock structure, MONPROC tries to look up the name of the file
Xassociated with the lock and prints that rather than the lock itself. 
XUsually, this gives the most information about the lock.  In order to look up
Xthe name, however, MONPROC calls AmigaDOS routines like DupLock(),
XParentDir(), and Examine().  If you are monitoring a file system handler, it
Xwill not be able to respond to these calls, because it is waiting for the
Xmonitor process to return the packet containing the FileLock in question.
XIf MONPROC waits for the handler to finish with ParentDir() or Examine()
Xbefore returing the packet, we have a deadlock situation, and both processes
Xfreeze.
X
XTo avoid this problem, use the NOLOCK option whenever you monitor file system
Xprocesses (like those associated with DF0:, DF1:, RAM:, and VD0:):
X
X    1> MONPROC NOLOCK
X
XWhen NOLOCK is used, MONPROC will not try to look up the names of the locks,
Xbut will print just the volume name for the given lock.  In addition, it will
Xgive the access type (shared or exclusive) for the lock.  This information
Xnormally is not shown, because MONPROC can not look up the name associated
Xwith an exclusive lock.
X
XThe NODUMP and NOLOCK options can be used together, and can be supplied in any
Xorder.  The defaults are to show Locks and perform HEX dumps when needed. 
XThese defaults can be changed in the file PrintPkt.c
X
X
XCOMPILING AND LINKING MONPROC:
X
XFor Lattice C, use the following commands to compile and link the files:
X
X    1> LC -v -L MONPROC GETPROCS GETDIR PRINTPKT
X
XThe -v option is required; MONPROC will not work without it.   I do not have 
Xthe Manx copmiler, so I can't tell you how to compile MONPROC with it.  I
Xsuspect that if any changes are required to make it run with Manx, they are
Xprobably minor.  I have retained all of Phillip Lindsay's #ifdef MANX code, so
Xthe transition should be pretty easy.
X
X
XSOME INTERESTING USES FOR THIS PROGRAM:
X
XI saw MONPROC basically as an interesting tool for learning about how AmigaDOS
Xworks.  For instance, you can use it to see how the scatter-loader actually
Xloads your program.  I was shocked to see how many parts my programs
Xcontained.  You can see what effect the SMALLDATA and SMALLCODE options have
Xon your executables.
X
XA more interesting use is to see how some of the CLI commands work.  You can
Xfollow the ParentDir calls for the CD command, and can see how the CLI looks
Xdown the PATH directories for your commands.
X
XYou can use MONPROC to help you debug device handlers (for instance, I used it
Xto help me understand Matt Dillon's PIPE device).
X
XYou can use it to help debug file actions from within your own programs (see
Xwhen your program opens files, and whether they are for reading or writing;
Xsee the data as it is sent to the files; see where you end up after a Seek(),
Xsee when it closes the files; etc.).
X
XFinally, you can use MONPROC to produce a hardcopy listing of the I/O activity
Xto particular devices.  For instance, I used MONPROC as the basis for my own
Xprogram called HARDCOPY, which produces a hardcopy transcript of any CLI 
Xsession (i.e., all the text that appears in the CLI window is sent to a file 
Xas well as to the CLI window).
X
XDavide P. Cervone
XUniversity of Rochester Computing Center            DPVC@UORDBV.BITNET
XTaylor Hall                                         dpvc@tut.cc.rochester.EDU
XRochester, New York  14627                          dpvc@ur-tut.UUCP
X(716) 275-2811
X
X
XPHILLIP LINDSAY'S ORIGINAL COMMENTS:
X
X    In the process structure you will find a field labeled "pr_PktWait." This
Xis a pointer for a alternate wait routine for AmigaDOS message handling.
XEverytime the AmigaDOS taskwait() routine is called a check is made to see
Xif the process PktWait field is non-zero...if non-zero a subroutine call is
Xmade to that address. The requirement of the subroutine is to wait for a
Xmessage to arrive at the process port (pr_MsgPort) and to return the address
Xof the message (NOT PACKET!) in D0.
XAnyway, what follows is a simple program that will allow you to monitor any
Xprocess for packet activity. A binary is available for the asking. (uuencoded)
X    BTW, When you call a AmigaDOS routine [like Read()] that talks to a
XAmigaDOS handler you can monitor a process receiving packets.
X-phil [ I promise this time no two signatures ]
X==============================================================================
X  Phillip Lindsay - Commodore Business Machines - Amiga Technical Support
X  UUCP: {ihnp4|seismo|caip}!cbmvax!phillip      - Phone: (215) 431-9180
X  No warranty is implied or otherwise given in the form of suggestion or 
X  example. Any opinions found here are of my making.
X
SHAR_EOF
sed 's/^X//' << \SHAR_EOF > monproc.c
X/*
X *  MONPROC.C    - Monitor AmigaDOS process packet activity.
X *
X *        Phillip Lindsay (c) 1987 Commodore-Amiga, Inc.
X *  You may use this source as long as this copywrite notice is left intact.
X *
X *  re-organized and slightly re-worked by Davide P. Cervone, 4/25/87.
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#define ONE         1L
X
X/*
X * AmigaDOS uses task signal bit 8 for message signaling
X */
X
X#define DOS_SIGNAL   8
X#define DOS_MASK     (ONE<<DOS_SIGNAL)
X
X#define PTR(x,p)     ((struct x *)(p))
X
Xextern void GetProcList(),FreeProcList(),PrintPkt();
Xextern LONG AllocSignal(), Wait();
Xextern struct Message *PacketWait(), *GetMsg();
Xextern struct Process *FindTask();
Xextern struct DosLibrary *DOSBase;
Xextern int Dump_OK, Lock_OK;
X
X
Xstruct MsgPort         *thePort;        /* the port we will be monitoring */
Xstruct Process         *myProcess;      /* pointer to our own process */
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 */
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 *  DoArguments()
X *
X *  Parses the command line to make sure it contains only valid arguments,
X *  and sets the flag variables to their proper values (they are initialized
X *  in printpkt.c).  The legal options are:
X *
X *      [NO]DUMP        To control hex dumps of long buffers
X *      [NO]LOCK        To control the display of LOCK parameters
X *                      (you should use NOLOCK if you are monitoring a
X *                      file system process).
X */
X
Xvoid DoArguments(argc,argv)
Xint argc;
Xchar **argv;
X{
X   while (--argc)
X   {
X      argv++;
X      if (stricmp(*argv,"NODUMP") == 0)      Dump_OK = FALSE;
X      else if (stricmp(*argv,"DUMP") == 0)   Dump_OK = TRUE;
X      else if (stricmp(*argv,"NOLOCK") == 0) Lock_OK = FALSE;
X      else if (stricmp(*argv,"LOCK") == 0)   Lock_OK = TRUE;
X      else
X      {
X         printf("Bad Argument:  '%s'\n",*argv);
X         printf("Usage:  MONPROC [NO]DUMP [NO]LOCK\n");
X         exit(5);
X      }
X   }
X}
X
X
X/*
X *  GetProcToMonitor()
X *
X *  Lists the processes currently running and lets the user choose one.
X *  Returns a pointer the the chosen process.
X */
X
Xstruct Process *GetProcToMonitor()
X{
X   struct List           ProcList;
X   struct Node           *theProc;
X   struct Process        *ChosenProcess = NULL;
X   ULONG                 count,choice;
X   char                  s[80];
X
X/*
X *  Set up ProcList and a list header and read the process list into it
X *  (we assume the process list won't change).
X */
X   NewList(&ProcList);
X   GetProcList(&ProcList);
X
X/*
X *  If there are any processes, list them and let the user chose one by 
X *  number.  If he choses a legal number, find the process in the list
X *  and return a pointer to the process structure for that process.
X *  Finally, free the process list.
X */
X   if (ProcList.lh_TailPred == PTR(Node,&ProcList))
X   {
X      printf("No Processes.\n");
X   } else {
X      do
X      {
X         printf("\nEnter NUMBER for process to monitor:\n"); 
X         printf("Pick# Process  MsgPort  Name\n");    
X         for (count=1,theProc=ProcList.lh_Head;    /* start at the list head */
X              theProc->ln_Succ;                    /* while there are more */
X              theProc=theProc->ln_Succ,count++)    /* go on to the next */
X         {
X            printf("%4ld. %08lX %08lX %s\n",count,
X               theProc->ln_Name,
X               &(PTR(Process,theProc->ln_Name)->pr_MsgPort),
X               PTR(Process,theProc->ln_Name)->pr_Task.tc_Node.ln_Name);
X         }
X         printf("\nNUMBER: ");
X         gets(s);
X      } while (sscanf(s,"%ld",&choice) != 1);
X  
X      if (choice < 1 || choice >= count)
X      {
X         printf("Operation Aborted.\n");
X      } else {
X         for (count=1,theProc=ProcList.lh_Head;
X              count < choice;
X              theProc=theProc->ln_Succ,count++);
X         ChosenProcess = PTR(Process,theProc->ln_Name);
X      }
X      FreeProcList(&ProcList);
X   }
X   return(ChosenProcess);
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.");
X      exit(10);
X   }
X   WaitMask = (ONE << (*theSignal));
X}
X
X
X/*
X *  SetupProcess()
X *
X *  Copy the process name, and gets it 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 so that we begin monitoring 
X *  the other task.  Save the old one so we can put it back.
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   Permit();
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 wil 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         printf("\n%s: ",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   Permit();
X   SetTaskPri(myProcess,0L);
X}
X
X
X/*
X *  WaitForLastPacket()
X *
X *  Since the monitored process is still running our PacketWait() code, we
X *  can't quit yet.  Since we already put back the old pr_PktWait pointer,
X *  our code will not be called after the next packet is received, so we have
X *  to wait for one more packet before we can quit.  Note that PacketWait()
X *  will still signal us, so we know when it will be safe to remove the code.
X *  Just in case we goof, let CTRL-E cancel, too.
X */
X
Xvoid WaitForLastPacket(theSignal)
XULONG theSignal;
X{
X   ULONG signals;
X
X   printf("\nThe process must receive another message before we can safely\n");
X   printf("remove the packet wait code.  We will wait for one more packet.\n");
X   printf("Press CTRL-E if you want to quit early, but that will likely\n");
X   printf("crash the monitored process!\n");
X
X   signals = Wait(SIGBREAKF_CTRL_E | WaitMask);
X   if (signals & WaitMask)
X   {
X      ObtainSemaphore(&CanReturn);
X      ReleaseSemaphore(&CanReturn);
X   }
X}
X
Xvoid main(argc,argv)
Xint argc;
Xchar **argv;
X{
X   struct Process    *ChosenProcess;
X   LONG              TaskSignal;
X   UBYTE             ProcessName[81];
X   
X   if (argc > 1) DoArguments(argc,argv);
X
X   printf("\n%s - monitor AmigaDOS process packet activity.\n",*argv);
X
X   myProcess = FindTask(NULL);
X   ChosenProcess = GetProcToMonitor();
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      printf("Monitor Installed, press CTRL-C to quit monitoring\n"); 
X   
X      MonitorProcess(ProcessName,TaskSignal);
X      
X      CleanUpProcess(ChosenProcess);
X      WaitForLastPacket(TaskSignal);
X      FreeSignal(TaskSignal);
X
X      printf("\nAll done.\n");
X   }
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
sed 's/^X//' << \SHAR_EOF > getprocs.c
X/*
X *  GETPROCS.C - Grab all available processes and return them to you in a
X *               simple exec list. The list is made up of  nodes--the "name"
X *               fields pointing to the process structure. 
X *
X *             Phillip Lindsay (c) 1987 Commodore-Amiga Inc. 
X *  You may use this source as long as the copyright notice is left intact.
X *
X *  Re-organized and re-worked by Davide P. Cervone, 4/25/87
X */
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <exec/tasks.h>
X#include <exec/execbase.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#define NODESIZE        ((ULONG)sizeof(struct Node))
X#define MEMFLAGS        (MEMF_PUBLIC | MEMF_CLEAR)
X
Xextern struct Node *AllocMem(), *RemTail();
X
X/*
X *   GetNode()
X *
X *  Allocates a node structure for you and initializes the node
X *  with the given values.
X */     
X
Xstruct Node *GetNode(name,type,pri)
Xchar *name;
XUBYTE type,pri;
X{
X   register struct Node *theNode;
X
X   theNode =  AllocMem(NODESIZE,MEMFLAGS); 
X   if (theNode != NULL)
X   {
X      theNode->ln_Name = name;
X      theNode->ln_Type = type;
X      theNode->ln_Pri  = pri;
X   }
X   return(theNode);
X}
X
X
X/*
X *  FreeNode()
X *
X *  Frees a given Exec node.  You must remove it from any list before
X *  calling FreeNode()
X */
X
Xvoid FreeNode(theNode)
Xstruct Node *theNode;
X{
X   FreeMem(theNode,NODESIZE);
X}
X
X
X/*
X *  AddProcs()
X *
X *  Adds the processes from the ProcList onto the end of the list 'plist'.
X */
X
Xvoid AddProcs(plist,ProcList)
Xstruct List *plist;
Xstruct List *ProcList;
X{
X   register struct Node  *aProc,*bProc;
X
X   if (ProcList->lh_TailPred != (struct Node *)ProcList)
X   {
X      for (aProc=ProcList->lh_Head; aProc->ln_Succ; aProc=aProc->ln_Succ)
X      { 
X         if (aProc->ln_Type == NT_PROCESS)
X         { 
X            if (bProc = GetNode(aProc,0,0)) AddTail(plist,bProc);
X         }
X      }
X   }    
X}
X
X
X/*
X *  GetProcList()
X *
X *  For each process in the system's task list, GetProcList appends an exec
X *  node to the given list and fills in the node name field with a pointer
X *  to the process strcuture.
X *
X *        plist     is an initialized Exec List.
X *
X *  WARNING:  This isn't a casual subroutine.  The returned list is valid as
X *  long as a process is not ended or added after this subroutine is
X *  called.  To be real safe you should probably check to see if the
X *  process is still around before trying to look at the data structure.  
X */
X
Xvoid GetProcList(plist)
Xstruct List *plist;
X{
X   extern   struct ExecBase   *SysBase;
X   register struct ExecBase   *execbase=SysBase;
X   register struct Node       *aProc;
X
X/*
X * I haven't clocked this code, but as a rule, you shouldn't stay disabled for
X * more than 250ms.  I have no doubt I'm illegal, but you can't be too safe
X * when dealing with something that changes with a blink of an interrupt.
X */
X
X   Disable();
X
X   /*
X    *  Add our own process to the list
X    */
X   if (execbase->ThisTask->tc_Node.ln_Type == NT_PROCESS)
X   {
X      if (aProc = GetNode(execbase->ThisTask,0,0)) AddTail(plist,aProc);
X   }
X
X   /*
X    *  Add the process from the ExecBase queues
X    */
X   AddProcs(plist,&execbase->TaskReady);
X   AddProcs(plist,&execbase->TaskWait);
X
X   Enable();
X}
X
X
X/*
X *  FreeProcList()
X *
X *  Frees all nodes in the given list.   FreeProcList() assumes the nodes
X *  where initialized with GetNode().
X */
X
Xvoid FreeProcList(plist)
Xstruct List *plist;
X{
X   register struct Node *theProc;
X
X   while (theProc = RemTail(plist)) FreeNode(theProc);
X}
SHAR_EOF
sed 's/^X//' << \SHAR_EOF > getdir.c
X/*  GETDIR.C  - Does some simple lock and device-list manipulation
X *
X *  Copywrite (c) 1987 by Davide P. Cervone
X *  This code may be used so long as this copywrite notice is left in tact.
X */
X
X#include "exec/types.h"
X#include "exec/memory.h"
X#include "libraries/dos.h"
X#include "libraries/dosextens.h"
X
X#define BCPL(x,y)         ((struct x *)(BADDR(y)))
X#define BCPL_TO_CHAR(y)   ((char *)(BADDR(y)))
X#define FILELOCK(x)       BCPL(FileLock,x)
X#define DEVLIST(x)        BCPL(DeviceList,x)
X
X#define FIBSIZE           ((ULONG)sizeof(struct FileInfoBlock))
X#define RETURN(dir)       return((*dir == '\0')? NULL: dir)
X
X
X/*
X *  BSTRcopy()
X *
X *  Copies a BCPL string into a C string and returns the length of the
X *  string.
X */
X
XBSTRcpy(to,BCPLfrom)
Xchar *to, *BCPLfrom;
X{
X   char *from = BCPL_TO_CHAR(BCPLfrom);
X
X   strncpy(to,from+1,(int)(*from));
X   *(to+(*from)) = '\0';
X   return((int) *from);
X}
X
X
X/*
X *  GetPathFromLock()
X *
X *  GetPathFromLock looks up the path name to the file or directory specified
X *  by the lock.  The volume name is included in the returned string. 
X *  If an error occurs, the function returns NULL, and the path name is set
X *  to an empty string (a null byte).  The lock remains intact (it is not
X *  UnLocked).
X *
X *      dir         a pointer to a string area where the path name
X *                  will be stored.  No check is made to be sure that
X *                  the path name fits.
X *      lock        a lock on the file or directory whose path is to
X *                  be found.
X *
X *  Note:  there seems to be a problem with the RAM: device (AmigaDOS v1.1).  
X *  ParentDir() always returns NULL for a lock on the RAM-disk;  Therefore, 
X *  GetPathFromLock() always returns "RAM:" for any lock on the RAM: device.
X *
X *  Note:  this function finds the complete path name to the locked object,
X *  including the volume name.  ASSIGNed names will be translated to their
X *  physical names.  For instance, if you CD to the C: directory, and
X *  C: is ASSIGNed as SYS:C, and SYS: is ASSIGNed as your boot disk, and
X *  your boot disk is named "WorkBench", then if you call GetPathFromLock()
X *  with a lock in the C: directory, you will get back the string 
X *  "WorkBench:c", not "C:".
X */
X
Xchar *GetPathFromLock(dir,lock)
Xchar *dir;
Xstruct FileLock *lock;
X{
X   struct FileLock *CurDir, *OldDir, *DupLock(), *ParentDir();
X   struct FileInfoBlock *fib, *AllocMem();
X   char *subdir = dir, *s, *s1, c;
X   int len;
X
X/*
X *  Clear the path name, and duplicate the lock (so we can UnLock our 
X *  duplicate without destroying the user's lock).  If DupLock returned
X *  NULL, then there was an error.  (An exclusive write lock, perhaps?)
X */
X
X   *dir = '\0';
X   CurDir = DupLock(lock);
X   if (CurDir == NULL) return(NULL);
X
X/*
X *  the FileInfoBlock must be longword alligned, so use AllocMem
X */
X   fib = AllocMem(FIBSIZE,MEMF_CLEAR | MEMF_PUBLIC);
X   if (fib != NULL)
X   {
X/*
X *  Get the device name from the volume entry in the device list
X *  that is pointed to by the file lock.  Since the device list is
X *  a shared list, use Forbid() and Permit() so it doesn't change while
X *  we are looking at it.  Add a colon, and set the subdir pointer to
X *  the character following the volume name.  Subdirectory names will be
X *  added starting there.
X */
X      Forbid();
X      subdir += BSTRcpy(dir,DEVLIST(FILELOCK(CurDir)->fl_Volume)->dl_Name);
X      Permit();
X      *subdir++ = ':';
X      *subdir   = '\0';
X/*
X *  While we have a valid lock (ParentDir() will return a NULL pointer
X *  when we reach the root of the disk), examine the directory (or file)
X *  that we have locked.  If we can't examine it, give an error, otherwise
X *  move up to the parent dir and unlock the old directory lock.  If we haven't
X *  moved up past the root, then we want to add the directory name (found
X *  in the FileInfoBlock structure returned by Examine()) to the path name.
X *  To do so, we shift any existing sub-directory names to the right leaving
X *  enough room for the new directory name (plus a slash, if needed).
X *
X *  Note:  the highest directory on a disk (the root), seems to have the same 
X *  name as the disk itself (look at the ASSIGN list, for instance), so 
X *  ParentDir() does not return NULL until we move past the root, that's why
X *  the ParentDir() call comes before we add the directory name into the 
X *  string.
X *
X *  For instance, if the current directory is "disk:a/b/c/d", and
X *  the CurDir lock is on "d", then the calls to ParentDir() will
X *  produce locks on "c", "b", "a", "disk", and then NULL.
X */
X      while (CurDir != NULL)
X      {
X         if (!Examine(CurDir,fib))
X         {
X            *dir = '\0';
X            UnLock(CurDir);
X            CurDir = NULL;
X         } else {
X            OldDir = CurDir;
X            CurDir = ParentDir(OldDir);
X            UnLock(OldDir);
X
X            if (CurDir != NULL)
X            {
X               len = strlen(fib->fib_FileName);
X               for (s=subdir+strlen(subdir), s1=s+len+1;
X                  s >= subdir; *s1-- = *s--);
X               c = *subdir;
X               strcpy(subdir,fib->fib_FileName);
X               *s1 = (c != '\0')? '/': c;
X            }
X         }
X      }
X      FreeMem(fib,FIBSIZE);
X   }
X   RETURN(dir);
X}
X
X
X/*
X *  GetInfoVolume()
X *
X *  GetInfoVolume looks up the volume name from an InfoData structure and
X *  returns a C string containing the name.
X *
X *      name        a pointer to a string area where the volume name
X *                  will be stored.  No check is made to be sure that
X *                  the name fits.
X *      info        an InfoData structure that has been filed in by a call
X *                  to Info().
X *
X */
X
Xvoid GetInfoVolume(name,info)
Xchar *name;
Xstruct InfoData *info;
X{
X/*
X *  Get the device name from the volume entry in the device list
X *  that is pointed to by the InfoData.  Since the device list is
X *  a shared list, use Forbid() and Permit() so it doesn't change while
X *  we are looking at it.  Add a colon for looks.
X */
X   Forbid();
X   name += BSTRcpy(name,DEVLIST(info->id_VolumeNode)->dl_Name);
X   Permit();
X   *name++ = ':';
X   *name   = '\0';
X}
SHAR_EOF
sed 's/^X//' << \SHAR_EOF > printpkt.c
X/*
X * PrintPkt.C - This doesn't do much, it takes a packet type (number) and
X *     gives you something more readable on stdout.
X *
X *       Phillip Lindsay (c) 1987 Commodore-Amiga Inc. 
X * You may use this source as long as the copyright notice is left intact.
X * 
X * Modified to print details of packet contents
X *    by Davide P. Cervone, 4/25/87
X */
X
X#include <exec/types.h>
X#include <libraries/dosextens.h>
X
X/*
X *  packet types
X */
X
X#define _ACTION_NIL                0L
X#define _ACTION_GET_BLOCK          2L
X#define _ACTION_SET_MAP            4L
X#define _ACTION_DIE                5L
X#define _ACTION_EVENT              6L
X#define _ACTION_CURRENT_VOLUME     7L
X#define _ACTION_LOCATE_OBJECT      8L
X#define _ACTION_RENAME_DISK        9L
X#define _ACTION_FREE_LOCK         15L
X#define _ACTION_DELETE_OBJECT     16L
X#define _ACTION_RENAME_OBJECT     17L
X#define _ACTION_MORE_CACHE        18L
X#define _ACTION_COPY_DIR          19L
X#define _ACTION_WAIT_CHAR         20L
X#define _ACTION_SET_PROTECT       21L
X#define _ACTION_CREATE_DIR        22L
X#define _ACTION_EXAMINE_OBJECT    23L
X#define _ACTION_EXAMINE_NEXT      24L
X#define _ACTION_DISK_INFO         25L
X#define _ACTION_INFO              26L
X#define _ACTION_FLUSH             27L
X#define _ACTION_SET_COMMENT       28L
X#define _ACTION_PARENT            29L
X
X/*
X *  This is normally a returning timer device request. (internal)
X */
X#define _ACTION_TIMER             30L
X
X#define _ACTION_INHIBIT           31L
X#define _ACTION_DISK_TYPE         32L
X#define _ACTION_DISK_CHANGE       33L
X#define _ACTION_SET_FILE_DATE     34L
X#define _ACTION_READ              82L
X#define _ACTION_WRITE             87L
X#define _ACTION_SET_SCREEN_MODE  994L
X
X/*
X * When a handler internally sends a device i/o request they are sent using
X * their process port and in the form of a "packet" the packet types below:
X * (ACTION_TIMER above also falls into this catagory)
X */
X#define _ACTION_READ_INTERNAL   1001L   
X#define _ACTION_WRITE_INTERNAL  1002L   
X
X#define _ACTION_FIND_INPUT      1005L
X#define _ACTION_FIND_OUTPUT     1006L
X#define _ACTION_CLOSE           1007L
X#define _ACTION_SEEK            1008L
X
X
X/*
X *  Short-hand for the parts of the DosPacket
X */
X#define ARG1    pkt->dp_Arg1
X#define ARG2    pkt->dp_Arg2
X#define ARG3    pkt->dp_Arg3
X#define ARG4    pkt->dp_Arg4
X#define RES1    pkt->dp_Res1
X
X#define BCPL(s,p)   ((struct s *)(((ULONG)p)<<2))
X
X
X
Xint Dump_OK = TRUE;    /* it's OK to perform HEX dumps for long data buffers */
Xint Lock_OK = TRUE;    /* it's OK to print LOCKs */
X
X/*
X *  printall()
X *
X *  Prints all the important parts of a DosPacket when we don't know
X *  what they are.
X */
X
Xvoid printall(s,pkt)
Xchar *s;
Xstruct DosPacket *pkt;
X{
X   printf("%s (%X,%X,%X) Returning: %X\n",s,ARG1,ARG2,ARG3,RES1);
X}
X
X
X/*
X *  printINT()
X *
X *  Prints a label and an integer in decimal notation.
X */
X
Xvoid printINT(s,i)
Xchar *s;
Xint i;
X{
X   printf("   %s: %d",s,i);
X}
X
X
X/*
X *  printBOOL()
X *
X *  Prints return values as OK or FALSE
X */
X
Xvoid printBOOL(b)
Xint b;
X{
X   printf("   Return: %s\n",(b)? "OK": "FALSE");
X}
X
X
X/*
X *  printX()
X *
X *  Prints a label and HEX value
X */
X
Xvoid printX(s,x)
Xchar *s;
Xunsigned int x;
X{
X   printf("   %s: %X",s,x);
X}
X
X
X/*
X *  printQuoted()
X *
X *  Prints a data buffer as a quoted string.  Special characters are shown
X *  as '\n','\r','\t', '\001', etc.
X */
X
Xvoid printQuoted(buf,length)
Xunsigned char *buf;
Xint length;
X{
X   short i;
X   unsigned char c, c7;
X   char line[200];
X   char *s = &line[0];
X
X   for (i=0; i<length; i++)
X   {
X      c = *(buf++); c7 = c & 0x7F;
X      if (c7 < ' ' || c7 > 0x7E)
X      {
X         switch(c)
X         {
X            case '\b':
X               *s++ = '\\';
X               *s++ = 'b';
X               break;
X
X            case '\t':
X               *s++ = '\\';
X               *s++ = 't';
X               break;
X
X            case '\r':
X               *s++ = '\\';
X               *s++ = 'r';
X               break;
X
X            case '\n':
X               *s++ = '\\';
X               *s++ = 'n';
X               break;
X
X            default:
X               sprintf(s,"\\%03o",c);
X               s += 4;
X               break;
X         }
X      } else {
X         if (c == '"' || c == '\\') *s++ = '\\';
X         *s++ = c;
X      }
X   }
X   *s = '\0';
X   printf("\"%s\"",line);
X}
X
X
X/*
X *  printDump()
X *
X *  Prints a data buffer in HEX dump format with ASCII equivalents.
X *  Non-printing characters are shown as '.'  Only the first 80 characters 
X *  are displayed.
X */
X
Xvoid printDump(buf,length)
Xunsigned char *buf;
Xunsigned int length;
X{
X   short i,j,count;
X   int l = (length > 80)? 80: length;
X   unsigned char c, c7;
X   char line[40];
X
X   line[39] = '\0';
X   count = 0;
X   printf("\n");
X   for (i=l/8; i>=0; i--)
X   {
X      for (j=0; j<39; j++) line[j] = ' ';
X      for (j=0; j<8 & count<l; j++,count++)
X      {
X         c = *(buf++); c7 = c & 0x7F;
X         sprintf(&line[j*3],"%02X ",c);
X         sprintf(&line[j+26],"%c ",(c7 < ' ' || c7 > 0x7E)? '.': c);
X      }
X      line[24] = ' ';
X      printf("\n         %s",line);
X   }
X   if (l < length) printf("\n                 [more]\n"); else printf("\n");
X}
X
X
X/*
X *  printBUF()
X *
X *  Prints a data buffer as a quoted string (if it is small enough)
X *  or as a HEX dump (if it is big).
X */
X
Xvoid printBUF(s,buf,length)
Xchar *s;
Xunsigned char *buf;
Xunsigned int length;
X{
X   printf("   %s: ",s);
X   if (length <= 50)
X      printQuoted(buf,length);
X     else
X      if (Dump_OK) printDump(buf,length);
X}
X
X
X/*
X *  printBSTR()
X *
X *  Prints the contents of a BCPL string buffer
X */
X
Xvoid printBSTR(s1,s2)
Xchar *s1,*s2; 
X{
X   printBUF(s1,BADDR(s2)+1,(long)(*((char *)BADDR(s2))));
X}
X
X/*
X *  printBPTR()
X *
X *  Prints the contents of a buffer pointed to by a BPTR
X */
X
X#define printBPTR(s,b,l)    printBUF(s,BADDR(b),l)
X
X
X/*
X *  printLOCK()
X *
X *  Prints the name of the file locked by a FileLock.  DON'T ATTEMPT TO
X *  MONITOR LOCKS FROIM FILE HANDLERS!  It will put you into a deadlock 
X *  situation:  the handler is waiting for the returned packet from 
X *  PacketWait, and we are waiting for the handler to process our DupLock 
X *  in GetPathFromLock.  Use MONPROC NOLOCK when you want to monitor file
X *  handlers.
X *  Notice that we expect a BPTR to a Lock.
X */
X
Xvoid printLOCK(s,FL)
Xchar *s;
XBPTR FL;
X{
X   struct FileLock *fl = BCPL(FileLock,FL);
X   char name[60];
X
X   if (Lock_OK)
X   {
X      name[0] = '\0';
X      if (FL) GetPathFromLock(name,FL);
X      printBUF(s,name,strlen(name));
X   } else {
X      if (fl)
X      {
X         printBSTR("Lock Volume",BCPL(DeviceList,fl->fl_Volume)->dl_Name);
X         printINT("Lock Access",fl->fl_Access);
X      } else {
X         printf("   Volume: \"\"");
X      }
X   }
X
X/*
X   printX(s,fl);
X   if (fl)
X   {
X      printINT("Key",fl->fl_Key);
X      printINT("Access",fl->fl_Access);
X      printX("Task",fl->fl_Task);
X      printf("\n");
X      printBSTR("Volume",BCPL(DeviceList,fl->fl_Volume)->dl_Name);
X   }
X*/
X}
X
X
X/*
X *  printFH()
X *
X *  Should print the contents of a FileHandle.  Does anyone have a good idea
X *  about what's useful in a Filehandle?
X *  Note that we expect a BPTR to a FileHandle.
X */
X
Xvoid printFH(s,FH)
Xchar *s;
XBPTR FH;
X{
X/*
X   struct FileHandle *fh = BCPL(FileHandle,FH);
X
X   if (fh)
X   {
X      printX("FH Port",fh->fh_Port);
X      printX("FH Type",fh->fh_Type);
X   }
X*/
X}
X
X
X/*
X *  printFIB()
X *
X *  Prints the fib_FileName field of a FileInfoBlock.
X *  Note that we expect a BPTR to a FileInfoBlock.
X */
X
Xvoid printFIB(FIB)
XBPTR FIB;
X{
X   struct FileInfoBlock *fib = BCPL(FileInfoBlock,FIB);
X
X   if (fib)
X      printBUF("File Name",&(fib->fib_FileName[1]),fib->fib_FileName[0]);
X     else
X      printf("   File Name: \"\"");
X}
X
X
X/*
X *  printfINFO()
X *
X *  Prints the volume name from an InfoData structure.
X *  Note that we expect a BPTR to InfoData.
X */
X
Xvoid printINFO(INFO)
XBPTR INFO;
X{
X   struct InfoData *info = BCPL(InfoData,INFO);
X   char name[60];
X
X   name[0] = '\0';
X   if (info) GetInfoVolume(name,info);
X   printBUF("Volume",name,strlen(name));
X}
X
X
X/*
X *  PrintPkt()
X *
X *  Prints the type of packet and the information appropriate to
X *  that packet type (if known).  Packet types and contents are described
X *  in the AmigaDOS Technical Reference Manual, secion 3.8.1.
X */
X
Xvoid PrintPkt(pkt)
Xstruct DosPacket *pkt;
X{
X   switch(pkt->dp_Type)
X   {
X      case _ACTION_NIL:
X         printall("ACTION_NIL",pkt);
X         break;
X
X      case _ACTION_GET_BLOCK:
X         printall("ACTION_GET_BLOCK",pkt);
X         break;
X
X      case _ACTION_SET_MAP:
X         printall("ACTION_SET_MAP",pkt);
X         break;
X
X      case _ACTION_DIE:
X         printall("ACTION_DIE",pkt);
X         break;
X
X      case _ACTION_EVENT:
X         printall("ACTION_EVENT",pkt);
X         break;
X
X      case _ACTION_CURRENT_VOLUME:
X         printall("ACTION_CURRENT_VOLUME",pkt);
X         break;
X
X      case _ACTION_LOCATE_OBJECT:
X         printf("ACTION_LOCATE_OBJECT (Lock)\n");
X         printLOCK("Lock",ARG1);
X         printBSTR("Name",ARG2);
X         printINT("Mode",ARG3);
X         printf("\n");
X         printLOCK("NewLock",RES1);
X         printf("\n");
X         break;
X
X      case _ACTION_RENAME_DISK:
X         printf("ACTION_RENAME_DISK\n");
X         printBSTR("New Name",ARG1);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_FREE_LOCK:
X         printf("ACTION_FREE_LOCK\n");
X         printLOCK("Lock",ARG1);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_DELETE_OBJECT:
X         printf("ACTION_DELETE_OBJECT\n");
X         printLOCK("Dir Lock",ARG1);
X         printBSTR("Name",ARG2);
X         printf("\n");
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_RENAME_OBJECT:
X         printf("ACTION_RENAME_OBJECT\n");
X         printLOCK("From Lock",ARG1);
X         printBSTR("From Name",ARG2);
X         printf("\n");
X         printLOCK("To Lock",ARG3);
X         printBSTR("To Name",ARG4);
X         printf("\n");
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_MORE_CACHE:
X         printall("ACTION_MORE_CACHE",pkt);
X         break;
X
X      case _ACTION_COPY_DIR:
X         printf("ACTION_COPY_DIR (DupLock)\n");
X         printLOCK("Lock",ARG1);
X         printLOCK("NewLock",RES1);
X         printf("\n");
X         break;
X
X      case _ACTION_WAIT_CHAR:
X         printf("ACTION_WAIT_CHAR\n");
X         printINT("Timeout",ARG1);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_SET_PROTECT:
X         printf("ACTION_SET_PROTECT\n");
X         printLOCK("Dir Lock",ARG2);
X         printBSTR("Name",ARG3);
X         printf("\n");
X         printX("Mask",ARG4);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_CREATE_DIR:
X         printf("ACTION_CREATE_DIR\n");
X         printLOCK("Dir Lock",ARG1);
X         printBSTR("Name",ARG2);
X         printf("\n");
X         printLOCK("New Lock",RES1);
X         printf("\n");
X         break;
X
X      case _ACTION_EXAMINE_OBJECT:
X         printf("ACTION_EXAMINE_OBJECT\n");
X         printLOCK("Lock",ARG1);
X         printFIB(ARG2);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_EXAMINE_NEXT:
X         printf("ACTION_EXAMINE_NEXT\n");
X         printLOCK("Dir Lock",ARG1);
X         printFIB(ARG2);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_DISK_INFO:
X         printf("ACTION_DISK_INFO\n");
X         printINFO(ARG1);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_INFO:
X         printall("ACTION_INFO",pkt);
X         break;
X
X      case _ACTION_FLUSH:
X         printall("ACTION_FLUSH",pkt);
X         break;
X
X      case _ACTION_SET_COMMENT:
X         printf("ACTION_SET_COMMENT\n");
X         printLOCK("Lock",ARG2);
X         printBSTR("Name",ARG3);
X         printf("\n");
X         printBSTR("Comment",ARG4);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_PARENT:
X         printf("ACTION_PARENT\n");
X         printLOCK("Dir Lock",ARG1);
X         printLOCK("Parent",RES1);
X         printf("\n");
X         break;
X
X      case _ACTION_TIMER:
X         printall("ACTION_TIMER",pkt);
X         break;
X
X      case _ACTION_INHIBIT:
X         printf("ACTION_INHIBIT\n");
X         printINT("On/Off",ARG1);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_DISK_TYPE:
X         printall("ACTION_DISK_TYPE",pkt);
X         break;
X
X      case _ACTION_DISK_CHANGE:
X         printall("ACTION_DISK_CHANGE",pkt);
X         break;
X
X      case _ACTION_SET_FILE_DATE:
X         printall("ACTION_SET_FILE_DATE",pkt);
X         break;
X
X      case _ACTION_READ:
X         printf("ACTION_READ\n");
X         printX("Arg1",ARG1);
X         printX("Buffer",ARG2);
X         printINT("Length",ARG3);
X         printINT("Actual Length",RES1);
X         printf("\n");
X         printBUF("Data",ARG2,RES1);
X         printf("\n");
X         break;
X
X      case _ACTION_WRITE:
X         printf("ACTION_WRITE\n");
X         printX("Arg1",ARG1);
X         printX("Buffer",ARG2);
X         printINT("Length",ARG3);
X         printINT("Actual Length",RES1);
X         printf("\n");
X         printBUF("Data",ARG2,RES1);
X         printf("\n");
X         break;
X
X      case _ACTION_SET_SCREEN_MODE:
X         printall("ACTION_SET_SCREEN_MODE",pkt);
X         break;
X
X      case _ACTION_READ_INTERNAL:
X         printall("ACTION_READ_INTERNAL",pkt);
X         break;
X
X      case _ACTION_WRITE_INTERNAL:
X         printall("ACTION_WRITE_INTERNAL",pkt);
X         break;
X
X      case _ACTION_FIND_INPUT:
X         printf("ACTION_FIND_INPUT\n");
X         printFH("File Handle",ARG1);
X         printLOCK("Dir Lock",ARG2);
X         printBSTR("Name",ARG3);
X         printf("\n");
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_FIND_OUTPUT:
X         printf("ACTION_FIND_OUTPUT\n");
X         printFH("File Handle",ARG1);
X         printLOCK("Lock",ARG2);
X         printBSTR("Name",ARG3);
X         printf("\n");
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_CLOSE:
X         printf("ACTION_CLOSE\n");
X         printX("Arg1",ARG1);
X         printBOOL(RES1);
X         break;
X
X      case _ACTION_SEEK:
X         printf("ACTION_SEEK\n");
X         printFH("File Handle",ARG1);
X         printINT("Position",ARG2);
X         printINT("Mode",ARG3);
X         printINT("Old Pos",RES1);
X         printf("\n");
X         break;
X
X      default:
X         printf("Unknown packet: %ld",pkt->dp_Type);
X         printall("",pkt);
X         break;
X  }
X}
SHAR_EOF
#	End of shell archive
exit 0