[comp.sys.amiga] A program to monitor AmigaDOS packet activity.

phillip@cbmvax.UUCP (03/30/87)

[yeah, bugs come and go, but COBOL insists on staying...]

    In the process structure you will find a field labeled "pr_PktWait." This
is a pointer for a alternate wait routine for AmigaDOS message handling.
Everytime the AmigaDOS taskwait() routine is called a check is made to see
if the process PktWait field is non-zero...if non-zero a subroutine call is
made to that address. The requirement of the subroutine is to wait for a
message to arrive at the process port (pr_MsgPort) and to return the address
of the message (NOT PACKET!) in D0.
Anyway, what follows is a simple program that will allow you to monitor any
process for packet activity. A binary is available for the asking. (uuencoded)
    BTW, When you call a AmigaDOS routine [like Read()] that talks to a
AmigaDOS handler you can monitor a process receiving packets.
-phil [ I promise this time no two signatures ]
==============================================================================
  Phillip Lindsay - Commodore Business Machines - Amiga Technical Support
  UUCP: {ihnp4|seismo|caip}!cbmvax!phillip      - Phone: (215) 431-9180
  No warranty is implied or otherwise given in the form of suggestion or 
  example. Any opinions found here are of my making. 	/* eof */

--------------ok...SA take out your blade and slice here----------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	makefile
#	monproc.c
#	getprocs.c
#	givepkt.c
# This archive created: Mon Mar 30 10:11:06 1987
export PATH; PATH=/bin:$PATH
echo shar: extracting "'makefile'" '(220 characters)'
if test -f 'makefile'
then
	echo shar: will not over-write existing file "'makefile'"
else
sed 's/^	X//' << \SHAR_EOF > 'makefile'
	X# monproc - mointor process packet activity 
	X#
	X#
	X
	XCFLAGS = +l -dMANX
	X
	XOBJS = monproc.o getprocs.o givepkt.o
	X
	Xall: $(OBJS)
	X	ln -o monproc $(OBJS) -lc32
	X
	Xmonproc: monproc.o
	X
	Xgetprocs: getprocs.o
	X
	Xgivepkt: givepkt.o
	X
	X# eof
SHAR_EOF
if test 220 -ne "`wc -c < 'makefile'`"
then
	echo shar: error transmitting "'makefile'" '(should have been 220 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'monproc.c'" '(6161 characters)'
if test -f 'monproc.c'
then
	echo shar: will not over-write existing file "'monproc.c'"
else
sed 's/^	X//' << \SHAR_EOF > 'monproc.c'
	X/* monproc.c	- monitor amigados process packet activity.
	X *
	X * Phillip Lindsay (c) 1987 Commodore-Amiga, Inc.
	X *
	X */
	X
	X#include <exec/types.h>
	X#include <exec/nodes.h>
	X#include <exec/lists.h>
	X#include <exec/memory.h>
	X#include <exec/ports.h>
	X#include <exec/tasks.h>
	X#include <exec/semaphores.h>
	X#include <libraries/dos.h>
	X#include <libraries/dosextens.h>
	X#include <libraries/filehandler.h>
	X
	X#ifdef MANX
	X#include <functions.h>
	X#include <stdio.h>
	X#else
	X#define index stpchr	/* can you say "ANSI STANDARD"...I knew you could... */
	X#include <lattice/stdio.h>
	X#endif
	X
	X/* constants */
	X
	X#define ONE			1L
	X
	X/* AmigaDOS uses task signal bit 8 for message signaling */
	X#define DOS_SIGNAL		8
	X#define DOS_MASK		( ONE << DOS_SIGNAL )
	X
	X/* referenced globals */
	Xextern void		 getprocs(),freeprocs(),givepkt();
	Xvoid			 memfill();
	Xextern struct DosLibrary *DOSBase;
	Xstruct Message		 *packetwait();
	XLONG			 pw();	/* this the asm stub that calls packetwait() */
	X
	X/* global data structures */
	Xstruct MsgPort         *deviceid;
	Xstruct Process         *memyselfNi;
	XULONG		       mymask;
	Xstruct SignalSemaphore canreturn;
	Xstruct Message         *mymess;
	X
	X
	Xmain()
	X{
	X struct   List			procs;
	X struct   Node			*proc;
	X register struct Process	*devproc;
	X register struct DosPacket	*pkt;
	X BYTE				mypri;	
	X register ULONG			waitmask,signals,oldpktwait;
	X LONG				mysignal;
	X ULONG				count,choice;
	X UBYTE				processname[81];
	X
	X puts("MONPROC - monitor AmigaDOS process packet activity.\n");
	X
	X memyselfNi = (struct Process *) FindTask(NULL);
	X NewList(&procs); 	/* initialize list header */
	X getprocs(&procs);	/* fill list with "active" devices */
	X
	X if(procs.lh_TailPred == &procs) /* list empty (should never happen) */
	X  {
	X   puts("No processes.");
	X   exit(0);
	X  }
	X
	X/*
	X * I am assuming that process list won't change. This is for hackers only.
	X */
	X do
	X  {
	X   puts("Enter NUMBER for process to monitor:"); 
	X   puts("Pick# Process  MsgPort  Name");    
	X   for(count = 1,proc = procs.lh_Head;proc->ln_Succ;proc=proc->ln_Succ,count++)
	X     printf("%4ld. %08lx %08lx %s\n",count,
	X         ((struct Process *)proc->ln_Name),
	X         &(((struct Process *)proc->ln_Name)->pr_MsgPort),
	X         ((struct Process *)proc->ln_Name)->pr_Task.tc_Node.ln_Name);
	X   printf("\nNUMBER: ");
	X   fflush(stdout);  
	X  } while( scanf("%ld",&choice) == EOF ) ;
	X
	X
	X if(!choice || choice >= count)
	X  {
	X   puts("Operation aborted.");
	X   freeprocs(&procs);
	X   exit(0);
	X  }
	X
	X/* what did he/she pick? */
	X  for(count = 1,proc = procs.lh_Head;proc->ln_Succ;proc=proc->ln_Succ,count++)
	X   if(choice == count)
	X    {
	X     devproc = (struct Process *) proc->ln_Name;
	X     break;
	X    }
	X
	X/* we don't need our process list anymore */
	X freeprocs(&procs);
	X 
	X/* make a copy of the process name */
	X strcpy(processname,devproc->pr_Task.tc_Node.ln_Name);
	X
	X/* get process msgport of process we will be monitoring */
	X deviceid = &devproc->pr_MsgPort;
	X
	X/* grab a signal for task intercommunication */
	X mysignal = (LONG) AllocSignal(-1L);
	X if(mysignal == -1)
	X  {
	X   puts("Can't allocate a task signal.");
	X   exit(0);
	X  }
	X
	X mymask = ONE << mysignal;
	X
	X waitmask = SIGBREAKF_CTRL_C | mymask;
	X
	X/* initialize our semaphore */
	X memfill(&canreturn,(ULONG)sizeof(canreturn),0L);
	X InitSemaphore(&canreturn);
	X
	X Forbid();
	X mypri = devproc->pr_Task.tc_Node.ln_Pri;
	X Permit();
	X 
	X/* make sure our priority is greater than the device task  */
	X SetTaskPri(memyselfNi, (ULONG)(mypri+1) ); 
	X 
	X puts("Waiting for messages...[CTRL_C to quit monitoring...]"); 
	X
	X /* install our packet wait function */
	X Forbid();
	X oldpktwait          = (ULONG) devproc->pr_PktWait;
	X devproc->pr_PktWait = (APTR)  pw;
	X Permit();
	X
	X/*
	X * I am using semaphores to control the monitoring of the handler's packets.
	X * There is probably a hundred better ways of doing this. I just went with
	X * what first came to mind. Since I am a higher priority than the handler...
	X * Once I am awake I will obtain the semaphore before the lower priority
	X * task has a chance...I will give it back to him once I am done with the
	X * packet data structure. 
	X */
	Xdo 
	X  {
	X
	X   signals = (ULONG) Wait(waitmask); 
	X   ObtainSemaphore(&canreturn);	
	X
	X   if(signals & mymask)             /* did the packetwait code signal us? */
	X    {                               /* yes, let's boogie...             */
	X     pkt = (struct DosPacket *) mymess->mn_Node.ln_Name; /* get packet address */
	X     printf("%s: ",processname);     
	X     givepkt(pkt);
	X    } 
	X
	X   ReleaseSemaphore(&canreturn);
	X
	X  } while(!(signals & SIGBREAKF_CTRL_C)); 
	X
	X /* remove our packet wait function and install previous value */
	X Forbid();
	X devproc->pr_PktWait = (APTR) oldpktwait;
	X Permit();
	X SetTaskPri(memyselfNi,0L);
	X puts("\nThere is no safe way to leave since the process is waiting in");
	X puts("our installed code. We have de-installed ourselves, but you should");
	X puts("be certain that the process received a packet AFTER we have been");
	X puts("de-installed. Press CTRL-E if you are certain this has happened.");
	X Wait(SIGBREAKF_CTRL_E);
	X FreeSignal(mysignal);
	X puts("All done.");
	X
	X} /* end of main() */
	X
	X
	X/**
	X ** local subroutines 
	X **/
	X
	X/*
	X * packetwait() ... Waits for a message to arrive at your port and 
	X *   returns it to you.
	X */
	Xstruct Message *packetwait()
	X{
	X#ifdef MANX	/* if MANX make sure we can see our data */
	X geta4();
	X#endif
	X /* wait for packet */
	X SetSignal(FALSE,DOS_MASK);	/* clear sigbit 8 */
	X while( !(mymess = (struct Message *) GetMsg(deviceid)) )  Wait(DOS_MASK);
	X Signal(memyselfNi,mymask); /* Wake up monitoring task he will grab the data */
	X ObtainSemaphore(&canreturn); /* we get this guy when we CAN return */	
	X ReleaseSemaphore(&canreturn);
	X
	X return(mymess); 
	X} 
	X
	X/*
	X * memfill() - fill memory with supplied byte value for "size" bytes
	X */
	Xvoid memfill(source,size,value)
	XUBYTE *source;
	XULONG size;
	XUBYTE value;	
	X{
	X register UBYTE *msource=source,
	X		mvalue=value;
	X register ULONG msize=size;
	X
	X if(msize)
	X  {
	X   do
	X    {
	X     --msize;
	X     msource[msize] = mvalue;
	X    } while(msize);
	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 _pw
	X_pw:
	X	movem.l a2/a3/a4,-(sp)
	X	jsr _packetwait
	X	movem.l (sp)+,a2/a3/a4
	X	rts
	X#endasm
	X#endif
	X
	X/* end of procdev.c */
SHAR_EOF
if test 6161 -ne "`wc -c < 'monproc.c'`"
then
	echo shar: error transmitting "'monproc.c'" '(should have been 6161 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'getprocs.c'" '(3659 characters)'
if test -f 'getprocs.c'
then
	echo shar: will not over-write existing file "'getprocs.c'"
else
sed 's/^	X//' << \SHAR_EOF > 'getprocs.c'
	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
	X#include <exec/types.h>
	X#include <exec/nodes.h>
	X#include <exec/lists.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 <libraries/filehandler.h>
	X
	X#ifdef MANX
	X#include <functions.h>
	X#include <stdio.h>
	X#else
	X#include <lattice/stdio.h>
	X#endif
	X
	X/* getnode() will allocate a node structure for you. And initialize the node
	X * structure with the given values.
	X */   	
	Xstruct Node *getnode(name,type,pri)
	Xchar *name;
	XUBYTE type,pri;
	X{
	X register struct Node *mynode;
	X
	X mynode = (struct Node *) 
	X  AllocMem((ULONG)sizeof(*mynode),MEMF_PUBLIC | MEMF_CLEAR); 
	X
	X if(!mynode) return((struct Node *)NULL);
	X 
	X mynode->ln_Name = name;
	X mynode->ln_Type = type;
	X mynode->ln_Pri  = pri;
	X
	X return(mynode);
	X}
	X
	X/* freenode() frees a given Exec node. 
	X * Make sure you remove node from any list. 
	X */
	Xvoid freenode(mynode)
	Xstruct Node *mynode;
	X{
	X FreeMem(mynode,(ULONG)sizeof(*mynode));
	X}
	X
	X
	X/* getprocs() will grab all processes in the system task's list and
	X *  append an exec node to a given list. The node name field filled
	X *  with a pointer to the process strcuture. 
	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 */
	Xvoid getprocs(plist)
	Xstruct List *plist;	/* passed a pointer to a initialized exec list */ 
	X{
	X extern   struct ExecBase 	  *SysBase;
	X register struct ExecBase	  *execbase=SysBase;
	X register struct List		  *proclist;
	X register struct Node 		  *aproc,*bproc;
	X register USHORT		  count=0;
	X
	X/* I haven't clocked this code, but you shouldn't as a rule stay disabled for
	X * no more than 250ms. I'm have no doubt I'm illegal, but you can't be
	X * too safe when dealing with something that changes with a blink of a
	X * interrupt.
	X */
	X
	X Disable();
	X
	X/* first we deal with ourselves... */
	X if(execbase->ThisTask->tc_Node.ln_Type == NT_PROCESS)
	X  {
	X   if((bproc=getnode(execbase->ThisTask,0,0)))
	X    {
	X     AddTail(plist,bproc);
	X    }
	X  }
	X
	X/* hopefully the added braces make the flow clear */
	X for(;count <= 1;count++)
	X  {
	X   if(!count) 
	X    proclist = &execbase->TaskReady;
	X   else
	X    proclist = &execbase->TaskWait;
	X
	X   if(proclist->lh_TailPred != 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)))
	X          {
	X           AddTail(plist,bproc);
	X          }
	X        }
	X      }
	X    }    
	X  }
	X
	X Enable();
	X
	X} /* end of getprocs() */
	X
	X
	X/* freeprocs() will free all nodes in a given list. Function assumes nodes where
	X *    initialized with getnode().
	X */
	Xvoid freeprocs(plist)
	Xstruct List *plist;
	X{
	X register struct Node *proc;
	X
	X while((proc=RemTail(plist)))
	X  freenode(proc);
	X}
	X
	X/*
	Xmain()
	X {
	X  struct List    procs;
	X  struct Node    *proc;
	X 
	X  NewList(&procs);	** Initialize list header **
	X  getprocs(&procs);	** Fill list **
	X
	X ** print any processess in list.... if any **
	X   if(procs.lh_TailPred != &procs)
	X    for(proc = procs.lh_Head;proc->ln_Succ;proc=proc->ln_Succ)
	X     puts( ((struct Process *)proc->ln_Name)->pr_Task.tc_Node.ln_Name );
	X     
	X   freeprocs(&procs);
	X }
	X 
	X*/
	X
SHAR_EOF
if test 3659 -ne "`wc -c < 'getprocs.c'`"
then
	echo shar: error transmitting "'getprocs.c'" '(should have been 3659 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'givepkt.c'" '(4342 characters)'
if test -f 'givepkt.c'
then
	echo shar: will not over-write existing file "'givepkt.c'"
else
sed 's/^	X//' << \SHAR_EOF > 'givepkt.c'
	X/* givepkt.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 */
	X
	X#include <exec/types.h>
	X#include <libraries/dosextens.h>
	X
	X/* packet types */
	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/* This is normally a returning timer device request. (internal) */
	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 * 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
	Xvoid givepkt(pkt)
	Xstruct DosPacket *pkt;
	X{
	X switch(pkt->dp_Type)
	X  {
	X	case _ACTION_NIL:
	X	 puts("ACTION_NIL");
	X	 break;
	X
	X	case _ACTION_GET_BLOCK:
	X	 puts("ACTION_GET_BLOCK");
	X	 break;
	X
	X	case _ACTION_SET_MAP:
	X	 puts("ACTION_SET_MAP");
	X	 break;
	X
	X	case _ACTION_DIE:
	X	 puts("ACTION_DIE");
	X	 break;
	X
	X	case _ACTION_EVENT:
	X	 puts("ACTION_EVENT");
	X	 break;
	X
	X	case _ACTION_CURRENT_VOLUME:
	X	 puts("ACTION_CURRENT_VOLUME");
	X	 break;
	X
	X	case _ACTION_LOCATE_OBJECT:
	X	 puts("ACTION_LOCATE_OBJECT");
	X	 break;
	X
	X	case _ACTION_RENAME_DISK:
	X	 puts("ACTION_RENAME_DISK");
	X	 break;
	X
	X	case _ACTION_FREE_LOCK:
	X	 puts("ACTION_FREE_LOCK");
	X	 break;
	X
	X	case _ACTION_DELETE_OBJECT:
	X	 puts("ACTION_DELETE_OBJECT");
	X	 break;
	X
	X	case _ACTION_RENAME_OBJECT:
	X	 puts("ACTION_RENAME_OBJECT");
	X	 break;
	X
	X	case _ACTION_MORE_CACHE:
	X	 puts("ACTION_MORE_CACHE");
	X	 break;
	X
	X	case _ACTION_COPY_DIR:
	X	 puts("ACTION_COPY_DIR");
	X	 break;
	X
	X	case _ACTION_WAIT_CHAR:
	X	 puts("ACTION_WAIT_CHAR");
	X	 break;
	X
	X	case _ACTION_SET_PROTECT:
	X	 puts("ACTION_SET_PROTECT");
	X	 break;
	X
	X	case _ACTION_CREATE_DIR:
	X	 puts("ACTION_CREATE_DIR");
	X	 break;
	X
	X	case _ACTION_EXAMINE_OBJECT:
	X	 puts("ACTION_EXAMINE_OBJECT");
	X	 break;
	X
	X	case _ACTION_EXAMINE_NEXT:
	X	 puts("ACTION_EXAMINE_NEXT");
	X	 break;
	X
	X	case _ACTION_DISK_INFO:
	X	 puts("ACTION_DISK_INFO");
	X	 break;
	X
	X	case _ACTION_INFO:
	X	 puts("ACTION_INFO");
	X	 break;
	X
	X	case _ACTION_FLUSH:
	X	 puts("ACTION_FLUSH");
	X	 break;
	X
	X	case _ACTION_SET_COMMENT:
	X	 puts("ACTION_SET_COMMENT");
	X	 break;
	X
	X	case _ACTION_PARENT:
	X	 puts("ACTION_PARENT");
	X	 break;
	X
	X	case _ACTION_TIMER:
	X	 puts("ACTION_TIMER");
	X	 break;
	X
	X	case _ACTION_INHIBIT:
	X	 puts("ACTION_INHIBIT");
	X	 break;
	X
	X	case _ACTION_DISK_TYPE:
	X	 puts("ACTION_DISK_TYPE");
	X	 break;
	X
	X	case _ACTION_DISK_CHANGE:
	X	 puts("ACTION_DISK_CHANGE");
	X	 break;
	X
	X	case _ACTION_SET_FILE_DATE:
	X	 puts("ACTION_SET_FILE_DATE");
	X	 break;
	X
	X	case _ACTION_READ:
	X	 puts("ACTION_READ");
	X	 break;
	X
	X	case _ACTION_WRITE:
	X	 puts("ACTION_WRITE");
	X	 break;
	X
	X	case _ACTION_SET_SCREEN_MODE:
	X	 puts("ACTION_SET_SCREEN_MODE");
	X	 break;
	X
	X	case _ACTION_READ_INTERNAL:
	X	 puts("ACTION_READ_INTERNAL");
	X	 break;
	X
	X	case _ACTION_WRITE_INTERNAL:
	X	 puts("ACTION_WRITE_INTERNAL");
	X	 break;
	X
	X	case _ACTION_FIND_INPUT:
	X	 puts("ACTION_FIND_INPUT");
	X	 break;
	X
	X	case _ACTION_FIND_OUTPUT:
	X	 puts("ACTION_FIND_OUTPUT");
	X	 break;
	X
	X	case _ACTION_CLOSE:
	X	 puts("ACTION_CLOSE");
	X	 break;
	X
	X	case _ACTION_SEEK:
	X	 puts("ACTION_SEEK");
	X	 break;
	X
	X	default:
	X	 printf("Unknown packet: %ld\n",pkt->dp_Type);
	X	 break;
	X  }
	X}
	X
	X/* end of givepkt.c */
SHAR_EOF
if test 4342 -ne "`wc -c < 'givepkt.c'`"
then
	echo shar: error transmitting "'givepkt.c'" '(should have been 4342 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0

phillip@cbmvax.UUCP (03/31/87)

I forgot to mention some hints on "monproc" operation.
	1. Execute "monproc" in his own CLI.
	2. When selecting a process to monitor and names are the same use
	   the process address to see who is who. Higher addresses are
	   usually the most newly created processes.
		(ie. The "File System" process with the lowest address
			is in most cases [if not all] "DF0:")  
-phil
==============================================================================
  Phillip Lindsay - Commodore Business Machines - Amiga Technical Support
  UUCP: {ihnp4|seismo|caip}!cbmvax!phillip      - Phone: (215) 431-9180
  No warranty is implied or otherwise given in the form of suggestion or 
  example. Any opinions found here are of my making. 	/* eof */