/*
 *		SNOOPDOS.C
 *
 *		(C) Copyright Eddy Carroll, May 1990. Freely distributable.
 *
 *		Snoopdos patches into dos.library and outputs a message to a
 *		debugging window or file whenever a process calls certain DOS
 *		functions.
 *
 *		Type snoopdos -h for a list of available options. See the
 *		documentation for further details.
 *
 *		Compiles under Lattice C V5.10. I use flags: -cusq -j88i -ms -v
 *		Note: Source assumes tabs set every 4 columns.
 *
 *		Revisions
 *		---------
 *		30 Oct 90:  Fixed potential deadlock problem in termination code
 *					Improved handling of NULL lock (i.e. boot device)
 *
 *		27 Jan 91:	Really fixed deadlock problem in termination code!
 */

#ifndef LATTICE_50
#include "system.h"
#endif

/*
 *		Assorted strings
 */
#define TITLE \
"SnoopDos V1.2 (C) Copyright Eddy Carroll, Jan 1991. Freely distributable."

char *HEADER =
"Process name          Func  Filename                                Mode Res."
"\r\n"
"------------          ----  --------                                ---- ----"
"\r\n";

#define PORTNAME	"SnoopDos Port"

#define DEFWINDOW	"CON:0/0/640/120/"

/*
 *		The following message array contains both colour and non-colour
 *		versions of the various short message strings displayed.
 */
char *msgs[][2] = {
/* Monochrome   		Colour   */
/* ----------   		------   */
	"OLD ",				"OLD ",
	"NEW ",				"\033[33mNEW\033[0m ",
	"R/W ",				"\033[32mR/W\033[0m ",
	"??? ",				"??? ",
	"SHAR",				"SHAR",
	"EXCL",				"\033[33mEXCL\033[0m",
	"????",				"????",
	"Okay\r\n",			"Okay\r\n",
	"Fail\r\n",			"\033[33mFail\033[0m\r\n",
	">",				"\033[33m>\033[0m",
	"> (Done)",			"> (Done)",
	"Warning: Missed",	"\033[33mWarning:\033[0m Missed",
	">>>>\r\n",			">>>>\r\n",
	"Open",				"Open",
	"Lock",				"\033[33mLock\033[0m",
	"Load",				"\033[32mLoad\033[0m",
	"Exec",				"\033[32mExec\033[0m",
	"CD  ",				"CD  ",
	"Del ",				"\033[33mDel\033[0m "
};

#define TXT_OLD		msgs[ 0][colour]
#define TXT_NEW		msgs[ 1][colour]
#define TXT_R_W		msgs[ 2][colour]
#define TXT_QM3		msgs[ 3][colour]
#define TXT_SHAR	msgs[ 4][colour]
#define TXT_EXCL	msgs[ 5][colour]
#define TXT_QM4		msgs[ 6][colour]
#define TXT_OKAY	msgs[ 7][colour]
#define TXT_FAIL	msgs[ 8][colour]
#define TXT_POINT	msgs[ 9][colour]
#define TXT_DONE	msgs[10][colour]
#define TXT_WARN	msgs[11][colour]
#define TXT_NEST	msgs[12][colour]
#define TXT_OPEN	msgs[13][colour]
#define TXT_LOCK	msgs[14][colour]
#define TXT_LOAD	msgs[15][colour]
#define TXT_EXEC	msgs[16][colour]
#define TXT_CURDIR	msgs[17][colour]
#define TXT_DELETE	msgs[18][colour]

#define POINT(x)	((x) ? TXT_POINT : " ")

/*
 *		Now some standard system-type macros
 */
#define reg_d0	register __d0
#define reg_d1	register __d1
#define reg_d2	register __d2
#define reg_d3	register __d3
#define reg_a0	register __a0

#define BTOC(x)	(void *)(((ULONG)x) << 2)

#define D_S(name, type) char c_##name[sizeof(type)+3];\
						type *name = (type *)((long)(c_##name+3) & ~3)

extern __asm BPTR CallOpen(reg_d1 UBYTE *, reg_d2 int);
extern __asm BPTR CallLock(reg_d1 UBYTE *, reg_d2 int);
extern __asm BPTR CallLoadSeg(reg_d1 UBYTE *);
extern __asm LONG CallExecute(reg_d1 UBYTE *, reg_d2 BPTR, reg_d3 BPTR);
extern __asm BPTR CallCurrentDir(reg_d1 BPTR);
extern __asm LONG CallDeleteFile(reg_d1 UBYTE *);

/*
 *		Structure used to pass messages back and fro
 */
typedef struct {
	struct Message msg;			/* Standard message header	*/
	struct Process *process;	/* Sending process id		*/
	int msgtype;				/* Message type, see below	*/
	int  data1;					/* Data field 1				*/ 
	void *data2;				/* Data field 2				*/
} MYMSG;

/*
 *		Now the various settings that can be set to affect the monitoring
 */
typedef struct {
	int set_doopen;				/* If true, monitor Open()			*/
	int	set_dolock;				/* If true, monitor Lock()			*/
	int set_doloadseg;			/* If true, monitor LoadSeg()		*/
	int set_doexecute;			/* If true, monitor Execute()		*/
	int set_docurdir;			/* If true, monitor CurrentDir()	*/
	int set_dodelete;			/* If true, monitor DeleteFile()	*/
	int	set_showfullpath;		/* If true, display full paths		*/
	int set_sleepwait;			/* If true, sleep if necessary		*/
	int set_snoopactive;		/* If true, monitoring is active	*/
	int set_colour;				/* If true, use ANSI colour codes	*/
} SETTINGS;

/*
 *		Default settings
 */
SETTINGS settings = { 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 };

/*
 *		These defines allow the various settings to be accessed as
 *		normal variables rather than structure members; this is purely
 *		for convenience.
 */
#define doopen			settings.set_doopen
#define dolock			settings.set_dolock
#define doloadseg		settings.set_doloadseg
#define doexecute		settings.set_doexecute
#define docurdir		settings.set_docurdir
#define dodelete		settings.set_dodelete
#define showfullpath	settings.set_showfullpath
#define sleepwait		settings.set_sleepwait
#define snoopactive		settings.set_snoopactive
#define colour			settings.set_colour

/*
 *		Now the various message types that can be sent
 */
typedef enum {
	MSG_QUIT,			/* Quit 					*/
	MSG_GETOPTIONS,		/* Read options				*/
	MSG_SETOPTIONS,		/* Update options			*/
	MSG_OPEN,			/* Open file				*/
	MSG_OPEN_DONE,		/* Open file completed		*/
	MSG_LOCK,			/* Lock file				*/
	MSG_LOCK_DONE,		/* Lock file completed		*/
	MSG_LOADSEG,		/* LoadSeg file				*/
	MSG_LOADSEG_DONE,	/* LoadSeg file completed	*/
	MSG_EXECUTE,		/* Execute command			*/
	MSG_CURDIR,			/* CurrentDir				*/
	MSG_DELETE,			/* DeleteFile				*/
	MSG_DELETE_DONE,	/* DeleteFile completed		*/
} MSGTYPES;

struct SignalSemaphore sem[1];
struct MsgPort   *myport;		/* Pointer to background SnoopDos msg port	*/
struct MsgPort   *inport;		/* Pointer to our own message port			*/
struct Task 	 *snooptask;	/* Pointer to our own task					*/
BPTR debugwin;					/* Output file or window					*/
BPTR stderr;					/* Standard CLI console						*/
int  extfile;					/* True if output directed to external file	*/
char extfilename[100];			/* Name of window/file to open if specified	*/
char outbuf[800];				/* Output buffer used by myprintf() etc.	*/

int missed_open_sig;			/* Signal to indicate Open() missed			*/
int missed_lock_sig;			/* Signal to indicate Lock() missed			*/
int missed_loadseg_sig;			/* Signal to indicate LoadSeg() missed		*/
int missed_execute_sig;			/* Signal to indicate Execute() missed		*/
int missed_curdir_sig;			/* Signal to indicate CurrentDir() missed	*/
int missed_delete_sig;			/* Signal to indicate DeleteFile() missed	*/
int portsig;					/* Signal used by our public port			*/

/**************************** Start of Functions ****************************/

/*
 *		cleanup()
 *		---------
 *		Cleans up all active resources and exits. If err is -1, then
 *		returns instead of exiting.
 */
void cleanup(int err)
{
	static int called = 0;

	if (called++)		/* Make sure not called twice by accident */
		return;

	if (inport)
		DeletePort(inport);

	if (debugwin)
		Close(debugwin);

	if (stderr)
		Close(stderr);

	if (err != -1)
		exit(err);
}

/*
 *		myprintf(), myfprintf()
 *		-----------------------
 *		Two low cost alternatives to printf that go directly to AmigaDOS
 *		Note we deliberately use the pre-ANSI declaration, to avoid Lattice
 *		kicking up a fuss about the wrong number of parameters.
 */
void myprintf(format, p1, p2, p3, p4, p5, p6)
UBYTE *format;
ULONG p1, p2, p3, p4, p5, p6;
{
	sprintf(outbuf, format, p1, p2, p3, p4, p5, p6);
	Write(Output(), outbuf, strlen(outbuf));
}

void myfprintf(file, format, p1, p2, p3, p4, p5, p6)
BPTR file;
char *format;
ULONG p1, p2, p3, p4, p5, p6;
{
	sprintf(outbuf, format, p1, p2, p3, p4, p5, p6);
	Write(file, outbuf, strlen(outbuf));
}

/*
 *		sendmsg()
 *		---------
 *		Sends a message to myport, and waits for a reply to arrive.
 *		A message type and some message data are all that need be provided
 *		by the caller; the callee will provide the rest. Doesn't return
 *		until a reply has been received.
 */
void sendmsg(int type, int data1, void *data2)
{
	MYMSG msg;
	struct Process *me = (struct Process *)FindTask(0);
	struct MsgPort *replyport = &me->pr_MsgPort;

	msg.msg.mn_Node.ln_Type = NT_MESSAGE;
	msg.msg.mn_Length       = sizeof(MYMSG);
	msg.msg.mn_ReplyPort    = replyport;
	msg.process				= me;
	msg.msgtype             = type;
	msg.data1               = data1;
	msg.data2               = data2;

	PutMsg(myport, &msg);
	WaitPort(replyport);
	GetMsg(replyport);
}


/*
 *		getlockpath()
 *		-------------
 *		Returns a pointer to a string containing the full path for the
 *		specified lock. You must copy the string before calling this
 *		routine again.
 */
char *getlockpath(BPTR lock)
{
	struct Process *p = (struct Process *)FindTask(0L);
	APTR oldwin = p->pr_WindowPtr;
	static char path[300];
	int pos = 299;
	BPTR mylock;
	char *name;
	D_S(fib, struct FileInfoBlock);
	int err = 0;

	p->pr_WindowPtr = (APTR)-1;	/* Disable error requesters */
	mylock = DupLock(lock);

	path[pos] = '\0';

	do {
		int len;
		BPTR newlock;

		if (!Examine(mylock, fib)) {
			UnLock(mylock);
			err++;
			break;
		}
		name = fib->fib_FileName;
		if (*name == '\0')
			name = "RAM";		/* Workaround for old RAM: disk bug */
		len = strlen(name);
		pos = pos - len - 1;
		newlock = ParentDir(mylock);
		UnLock(mylock);
		mylock = newlock;
		strncpy(path + pos, name, len);
		if (mylock)
			path[pos + len] = '/';
		else
			path[pos + len] = ':';
	} while (mylock);
	p->pr_WindowPtr = oldwin;	/* Enable error requesters again */

	if (err) {
		/*
		 *		Volume not present so have to be happy with just
		 *		returning the volume node instead.
		 */
		struct FileLock *fl;
		struct DeviceList *dl;
		UBYTE *name;

		if (lock) {
			fl = BTOC(lock);
			dl = BTOC(fl->fl_Volume);
			name = BTOC(dl->dl_Name);

			strncpy(path, name + 1, *name);
			path[*name] = '\0';
		} else {
			strcpy(path, "<Boot device>");
		}
		strcat(path, ":.../");
		return (path);
	} else
		return (path + pos);
}

/*
 *		makepath()
 *		----------
 *		Builds a full path string given a process ptr and a filename.
 *		If the filename includes relative references (// or :)
 *		these are handled also. The point of this is to resolve relative
 *		directory references.
 *
 *		Returns TRUE if a new string was generated, FALSE if the existing
 *		string didn't depend on the current directory (this doesn't affect
 *		the output stored in buf though).
 */
int makepath(char *buf, BPTR curdir, char *filename)
{
	char *origfilename = filename;
	int pos;
	int doneroot = 0;

	/*
	 *		Special check for the 'current process console' file '*'
	 */
	if (strcmp(filename, "*") == 0) {
		strcpy(buf, filename);
		return (FALSE);
	}

	strcpy(buf, getlockpath(curdir));
	pos = strlen(buf);

	for (;;) {
		if (!doneroot && *filename == ':') {
			/*
			 *		Path is relative to root
			 */
			doneroot = 1;
			for (pos = 0; buf[pos] && buf[pos] != ':'; pos++)
				;
			if (buf[pos] == ':')
				pos++;
		} else if (*filename == '/') {
			/*
			 *		Path is relative to parent directory; if none, then
			 *		remains the same.
			 */
			int newpos = pos - 2;
			while (newpos >= 0 && buf[newpos] != '/' && buf[newpos] != ':')
				newpos--;
			if (newpos >= 0)
				pos = newpos + 1;
		} else {
			/*
			 *		No more special characters; just append what's left of
			 *		the filename.
			 */
			if (!doneroot) {
				/*
				 *		If the filename wasn't relative to the root
				 *		directory, then make sure there are no device/volume
				 *		references contained within it. We copy the original
				 *		filename rather than the currently modified version
				 *		since a volume name of /A: is legal and the / would
				 *		be stripped off the modified name.
				 */
				char *p;
				for (p = filename; *p; p++) {
					if (*p == ':') {
						strcpy(buf, origfilename);
						return (0);
					}
				}
			}
			strcpy(buf + pos, filename);
			return (1);
		}
		filename++;
	}
}

/*
 *		mainloop()
 *		----------
 *		This is the main event loop for SnoopDOS, where everything happens.
 *		Control is passed here when SnoopDOS first starts up. When this
 *		function returns, it terminates the background process.
 */
void mainloop(void)
{
	static char fullname[300];
	int done   = 0;					/* True if finished processing			*/
	int col0   = 1;					/* True if cursor in column 0			*/
	int nested = 0;					/* True if nested DOS calls				*/

#define MISSED_NONE		0			/* Haven't missed anything	*/
#define MISSED_OPEN		(1 << 0)	/* Missed Open() call		*/
#define MISSED_LOCK		(1 << 1)	/* Missed Lock() call		*/
#define MISSED_LOADSEG	(1 << 2)	/* Missed LoadSeg() call	*/
#define MISSED_EXECUTE	(1 << 3)	/* Missed Execute() call	*/
#define MISSED_CURDIR	(1 << 4)	/* Missed CurrentDir() call	*/
#define MISSED_DELETE	(1 << 5)	/* Missed DeleteFile() call	*/

	int missed = MISSED_NONE;		/* Was a DOS function missed? See above	*/

	inport = CreatePort(PORTNAME, 0);
	if (!inport) {
		myfprintf(stderr, "SnoopDos: Can't allocate message port.\n");
		cleanup(-1);
		return;
	}

	myport  = inport;
	portsig = 1 << myport->mp_SigBit;

	/*
	 *		Allocate signals
	 */
	missed_open_sig	   = 1 << AllocSignal(-1);
	missed_lock_sig	   = 1 << AllocSignal(-1);
	missed_loadseg_sig = 1 << AllocSignal(-1);
	missed_execute_sig = 1 << AllocSignal(-1);
	missed_curdir_sig  = 1 << AllocSignal(-1);
	missed_delete_sig  = 1 << AllocSignal(-1);

	if (	missed_open_sig    == -1  || missed_lock_sig    == -1  ||
			missed_loadseg_sig == -1  || missed_execute_sig == -1  ||
			missed_curdir_sig  == -1  || missed_delete_sig  == -1) {
		myfprintf(stderr, "SnoopDos: Can't allocate enough signals.\n");
		cleanup(-1);
		return;
	}

	if (extfile) {
		debugwin = Open(extfilename, MODE_NEWFILE);
		if (!debugwin) {
			myfprintf(stderr, "SnoopDos: Can't open file %s for output.\n",
								extfilename);
			cleanup(-1);
			return;
		}
		myfprintf(debugwin, "\r\n" TITLE "\r\n\r\n");
	} else {
		debugwin = Open(DEFWINDOW TITLE, MODE_NEWFILE);
		if (!debugwin) {
			myfprintf(stderr, "SnoopDos: Can't open display window.\n");
			cleanup(-1);
			return;
		}
	}
	Close(stderr); stderr = NULL;

	if (!extfile) {
		myfprintf(debugwin, "\n"
	"Type CTRL-E/CTRL-D to enable/disable snooping. Type CTRL-C to exit.\n\n");
	};
	myfprintf(debugwin, HEADER);

	InitSemaphore(sem);
	snooptask = FindTask(0L);
	installdospatch();

	/*
	 *		Now just sit processing messages
	 */
	while (!done) {
		int mask;

#define SIGMASK	(portsig | missed_open_sig | missed_lock_sig | \
				 missed_loadseg_sig | missed_execute_sig |     \
				 missed_curdir_sig | missed_delete_sig |       \
				 SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E)

		mask = Wait(SIGMASK);

		if (mask & SIGBREAKF_CTRL_C)
			done = 1;

		if (mask & SIGBREAKF_CTRL_D) {
			if (snoopactive) {
				myfprintf(debugwin,
"\nSnooping now disabled; type CTRL-E to enable it again.\r\n\r\n");
				snoopactive = 0;
			}
		}

		if (mask & SIGBREAKF_CTRL_E) {
			if (!snoopactive) {
				myfprintf(debugwin,
"Snooping now enabled; type CTRL-D to disable it again.\r\n\r\n%s", HEADER);
				snoopactive = 1;
			}
		}

		if (mask & missed_open_sig)
			missed |= MISSED_OPEN;

		if (mask & missed_lock_sig)
			missed |= MISSED_LOCK;

		if (mask & missed_loadseg_sig)
			missed |= MISSED_LOADSEG;

		if (mask & missed_execute_sig)
			missed |= MISSED_EXECUTE;

		if (mask & missed_curdir_sig)
			missed |= MISSED_CURDIR;

		if (mask & missed_delete_sig)
			missed |= MISSED_DELETE;

		if (mask & portsig) {
			MYMSG *msg;

			/*
			 *		Note in the following, there is some slightly tricky
			 *		stuff to handle the case where a DOS function calls
			 *		another DOS function internally. Under 1.3, this doesn't
			 *		happen, but it may under WB 2.0 and in addition, Jim
			 *		Goodnow's handy Rez utility patches LoadSeg() so that
			 *		it calls Open(). `nested' is true when a nested call
			 *		like this happens, and in this case, process names are
			 *		printed out prefixed by `> ' to indicate the nesting.
			 *
			 *		The nesting is detected because an incoming Open/Lock/
			 *		LoadSeg message will arrive when the cursor is positioned
			 *		to print a Fail/Okay result from a previous call (i.e.
			 *		col0 is false; the cursor is not in column 0). When a
			 *		result arrives in and the cursor IS in column 0, then
			 *		this must be the result from an earlier call so the
			 *		nesting is ended. Note that the semaphores used ensures
			 *		that this situation can only ever occur as a result of
			 *		nesting; other tasks will always wait until they are
			 *		properly in sync before sending messages to the main
			 *		Snoopdos process.
			 *
			 *		The more alert among you are probably saying something
			 *		like "Ah, but what if SnoopDos is configured to display
			 *		all DOS calls, even if it means forcing some tasks to
			 *		sleep until SnoopDos can process them? Then, when a
			 *		task calls LoadSeg() (say) and LoadSeg() calls Open(),
			 *		Open() will sleep forever waiting for the semaphore
			 *		Obtain()ed by LoadSeg() to become free again." Well,
			 *		in fact, it won't, since exec's ObtainSemaphore() call
			 *		will suceed even when the semaphore is in use, IF the
			 *		caller is the task that owns the semaphore -- and in such
			 *		a case, the caller will be the same task. (It took me a
			 *		while to figure out exactly what things were working --
			 *		it looked like I should get a serious lockup in certain
			 *		circumstances).
			 *		
			 */
			while ((msg = (MYMSG *)GetMsg(myport)) != NULL) {
				/*
				 *		Get the name of the process/command for
				 *		printing out if necessary.
				 */
				static char namebuf[256];
				UBYTE *procname = msg->process->pr_Task.tc_Node.ln_Name;
				struct CommandLineInterface *cli = BTOC(msg->process->pr_CLI);
				BPTR curdir = msg->process->pr_CurrentDir;
				UBYTE *s;
				UBYTE *filename;
				int newname;	/* If true, a new name was generated */

				if (!col0 && (msg->msgtype == MSG_OPEN		||
							  msg->msgtype == MSG_LOCK		||
							  msg->msgtype == MSG_LOADSEG	||
							  msg->msgtype == MSG_CURDIR	||
							  msg->msgtype == MSG_DELETE)) {
					nested = 1;
					myfprintf(debugwin, TXT_NEST);
				}
				if (cli && cli->cli_Module) {
					UBYTE *cliname = BTOC(cli->cli_CommandName);
					if (*cliname > 0) {
						/* Only use CLI name if it's not null */
						strncpy(namebuf+2, cliname + 1, *cliname);
						namebuf[*cliname+2] = '\0';
						procname = namebuf + 2;
						if (nested) {
							/*
							 *		If we're not at column 0, then we're
							 *		calling this DOS function from within
							 *		another DOS function so indent display
							 */
							procname = namebuf;
							procname[0] = '>';
							procname[1] = ' ';
						}
					}
				} else {
					if (nested) {
						sprintf(namebuf, "> %s", procname);
						procname = namebuf;
					}
				}

				/*
				 *		Now handle the message
				 */
				filename = msg->data2;	/* Standard file name			*/
				newname  = 0;			/* No problems expanding it		*/

				switch (msg->msgtype) {

					case MSG_QUIT:
						done = 1;
						break;

					case MSG_GETOPTIONS:
						memcpy(msg->data2, &settings, sizeof(SETTINGS));
						break;

					case MSG_SETOPTIONS:
						memcpy(&settings, msg->data2, sizeof(SETTINGS));
						break;

					case MSG_OPEN:
						if (msg->data1 == MODE_OLDFILE)
							s = TXT_OLD;
						else if (msg->data1 == MODE_NEWFILE)
							s = TXT_NEW;
						else if (msg->data1 == MODE_READWRITE)
							s = TXT_R_W;
						else
							s = TXT_QM3;

						if (showfullpath) {
							newname = makepath(fullname, curdir, msg->data2);
							filename = fullname;
						}
						myfprintf(debugwin, "%-21s %s %s%-39s %s ", procname,
									TXT_OPEN, POINT(newname), filename, s);
						col0 = 0;
						break;  

					case MSG_LOCK:
						if (msg->data1 == ACCESS_READ)
							s = TXT_SHAR;
						else if (msg->data1 == ACCESS_WRITE)
							s = TXT_EXCL;
						else
							s = TXT_QM4;

						if (showfullpath) {
							newname = makepath(fullname, curdir, msg->data2);
							filename = fullname;
						}
						myfprintf(debugwin, "%-21s %s %s%-39s %s ", procname,
									TXT_LOCK, POINT(newname), filename, s);
						col0 = 0;
						break;

					case MSG_LOADSEG:
						if (showfullpath) {
							newname = makepath(fullname, curdir, msg->data2);
							filename = fullname;
						}
						myfprintf(debugwin, "%-21s %s %s%-44s ", procname,
									TXT_LOAD, POINT(newname), filename);
						col0 = 0;
						break;

					case MSG_EXECUTE:
						myfprintf(debugwin, "%-21s %s  %s\r\n", procname,
													TXT_EXEC, filename);
						col0 = 1;
						break;

					case MSG_CURDIR:
						{
							char *dirname = getlockpath(msg->data1);
							int i = strlen(dirname);

							if (dirname[i-1] == '/')
								dirname[i-1] = '\0';
							myfprintf(debugwin, "%-21s %s  %s\r\n", procname,
													TXT_CURDIR, dirname);
							col0 = 1;
						}
						break;

					case MSG_DELETE:
						if (showfullpath) {
							newname = makepath(fullname, curdir, msg->data2);
							filename = fullname;
						}
						myfprintf(debugwin, "%-21s %s %s%-44s ", procname,
									TXT_DELETE, POINT(newname), filename);
						col0 = 0;
						break;

					case MSG_LOCK_DONE:
					case MSG_OPEN_DONE:
					case MSG_LOADSEG_DONE:
					case MSG_DELETE_DONE:
						if (col0 && nested) {
							myfprintf(debugwin, "%-73s", TXT_DONE);
							nested = 0;
						}
						if (msg->data1)
							myfprintf(debugwin, TXT_OKAY);
						else
							myfprintf(debugwin, TXT_FAIL);
						col0 = 1;
						break;
				}
				ReplyMsg(msg);
			}
		}

		/*
		 *		Finally, check if we missed a DOS function. If we did,
		 *		AND we are in column 0, print out an appropriate message.
		 *		Otherwise, wait until next time. This stops the display
		 *		getting messed up when waiting to print a 'Fail' or 'Okay'.
		 */
		if (missed) {
			if (col0) {
				if (missed & MISSED_OPEN)
					myfprintf(debugwin, "%s an Open()\r\n", TXT_WARN);
				if (missed & MISSED_LOCK)
					myfprintf(debugwin, "%s a Lock()\r\n", TXT_WARN);
				if (missed & MISSED_LOADSEG)
					myfprintf(debugwin, "%s a LoadSeg()\r\n", TXT_WARN);
				if (missed & MISSED_EXECUTE)
					myfprintf(debugwin, "%s an Execute()\r\n", TXT_WARN);
				if (missed & MISSED_CURDIR)
					myfprintf(debugwin, "%s a CurrentDir()\r\n", TXT_WARN);
				if (missed & MISSED_DELETE)
					myfprintf(debugwin, "%s a DeleteFile()\r\n", TXT_WARN);
				missed = MISSED_NONE;
			}
		}
	}

	/*
	 *		Remove the port from the public ports list; this stops any
	 *		SnoopDos processes started up elsewhere from trying to
	 *		communicate with us (which may happen if this process can't
	 *		exit immediately).
	 */
	RemPort(myport);
	myport->mp_Node.ln_Name = NULL;
	snoopactive = 0;	/* Make sure our patch stops sending msgs to us! */

	/*
	 *		Now try and obtain our semaphore. Until we can achieve it,
	 *		we may still be receiving messages from other tasks telling
	 *		us about DOS operations, so, we reply to these as they arrive
	 *		(else deadlock occurs).
	 */
	while (!AttemptSemaphore(sem)) {
		MYMSG *msg = (MYMSG *)GetMsg(myport);

		if (msg)
			ReplyMsg(msg);
		Delay(5);				/* Wait for 0.2 seconds */
	}

	/*
	 *		Now remove the dospatch, and cleanup
	 */
	if (!uninstalldospatch()) {
		/*
		 *		Someone else has patched DOSbase so print a message for
		 *		the user and keep trying every 5 seconds until we succeed.
		 */
		int count = 0;

		myfprintf(debugwin,
"\r\n"
"Someone else has patched dos.library so I'll have to wait until they quit."
"\r\n");
		if (!extfile) {
			myfprintf(debugwin, 
  "This window will close shortly though to stop it from annoying you.\r\n");
		}

		do {
			Delay(50);					/* Wait for one second */
			if (debugwin) {
				count++;
				if (count > 10) {
					Close(debugwin);
					debugwin = NULL;
				}
			}
		} while (!uninstalldospatch());
	}

	if (debugwin)
		myfprintf(debugwin, "\r\nSnoopDos terminated.\r\n");

	/*
	 *		We do a final wait for 0.2 seconds, just in case their are any
	 *		other tasks still running in our SetFunction'd code.
	 *		(This isn't 100% bulletproof, but it's fairly safe).
	 */
	Delay(10);
	cleanup(-1);
}

/*
 *		main()
 *		------
 *		Mainline. Parses the command line and, if necessary, kicks off the
 *		background task to do the real work.
 */
void main(int argc, char **argv)
{
	int error  = 0;			/* True if error detected in cmd line	*/
	int quit   = 0;			/* True if we want to quit				*/
	int colsel = 0;			/* True if colour option was specified	*/

	/*
	 *		See if we are already active elsewhere; if yes, then read in
	 *		the default settings used by that process.
	 */

	myport = FindPort(PORTNAME);
	if (myport)
		sendmsg(MSG_GETOPTIONS, 0, &settings);

	while (argc > 1 && argv[1][0] == '-') {
		int val;

		/* Make -X == -x0 and -x == -x1 */
		if (argv[1][1] >= 'a' && argv[1][2] != '0')
			val = 1;
		else
			val = 0;

		switch (tolower(argv[1][1])) {

			case 'a': colour 		= val;
					  colsel		= 1;   break;	/* Display ANSI colour	*/
			case 'c': docurdir		= val; break;	/* Monitor CurrentDir() */
			case 'd': dodelete		= val; break;	/* Monitor DeleteFile()	*/
			case 'f': showfullpath	= val; break;	/* Show full filepath	*/
			case 'g': doloadseg		= val; break;	/* Monitor LoadSeg()	*/
			case 'h': error			= 1;   break;	/* Just print help msg	*/
			case 'l': dolock		= val; break;	/* Monitor Lock()		*/
			case 'm': snoopactive	= val; break;	/* Actively monitoring	*/
			case 'o': doopen		= val; break;	/* Monitor Open()		*/
			case 'q': quit			= 1;   break;	/* Ask SnoopDos to quit */
			case 'r': error			= 2;   break;	/* Report settings		*/
			case 'w': sleepwait		= val; break;	/* Wait when necessary	*/
			case 'x': doexecute		= val; break;	/* Monitor Execute()	*/
			case 'z':
					if (argv[1][2]) {
						strcpy(extfilename, &argv[1][2]);
						extfile = 1;
					} else {
						argv++;
						argc--;
						if (argc > 1) {
							strcpy(extfile, argv[1]);
							extfile = 1;
						}
					}
					if (!extfile) {
						myprintf(
		"-z must be followed by a filename. Type SnoopDos -h for help.\n");
						exit(5);
					}
					/*
					 *		If outputting to a file, make colour off
					 *		by default, rather than on.
					 */
					if (colsel == 0)
						colour = 0;
					break;

			default:
				myprintf("Unrecognised option %s. "
						 "Type Snoopdos -h for help.\n", argv[1]);
				exit(5);
		}
		argv++; argc--;
	}

	if (argc > 1)
		error = 1;

	if (error) {
		if (error == 1) {
			myprintf(TITLE "\n\n"
"SnoopDos monitors calls made to various AmigaDOS functions by all processes\n"
"on the system. The following options are available. Use -x1 or just -x to\n"
"enable an option, -x0 or -X to disable that option. Current settings in (-)."
"\n\n");
		} else
			myprintf("Current SnoopDos settings:\n\n");

#define O(x,y) myprintf(x, y ? "(on)" : "(off)")
O("    -a  Use ANSI colour sequences                     %s\n", colour);
O("    -c  Monitor CurrentDir() calls                    %s\n", docurdir);
O("    -d  Monitor DeleteFile() calls                    %s\n", dodelete);
O("    -f  Display full filename paths                   %s\n", showfullpath);
O("    -g  Monitor LoadSeg() calls                       %s\n", doloadseg);
O("    -l  Monitor Lock() calls                          %s\n", dolock);
O("    -m  Globally enable/disable monitoring            %s\n", snoopactive);
O("    -o  Monitor Open() calls                          %s\n", doopen);
O("    -w  Display all activity, sleeping if necessary   %s\n", sleepwait);
O("    -x  Monitor Execute() calls                       %s\n", doexecute);

		if (error == 1) {
			myprintf("\n"
"    -h  Print out this help message\n"
"    -q  Tell SnoopDos to quit\n"
"    -r  Report current SnoopDos settings\n" 
"    -z% Output to file % (e.g. -zCON:0/0/640/100/SnoopDos or -zSER:)\n"
"\n");
		}
		cleanup(5);
	}

	/*
	 *		First see are we already installed. If so, send a copy of the
	 *		updated settings to the background process, or send a QUIT
	 *		message if the user wanted to quit.
	 */
	if (myport) {
		if (quit)
			sendmsg(MSG_QUIT, 0, 0);
		else
			sendmsg(MSG_SETOPTIONS, 0, &settings);
		cleanup(0);
	}

	/*
	 *		If user wanted to quit and we weren't already running, just
	 *		quit without doing anything.
	 */
	if (quit) {
		myprintf("There is no background SnoopDos process to tell to quit!\n");
		cleanup(0);
	}

	/*
	 *		Not installed, so install ourselves. First of all though, we
	 *		kick ourselves into the background and return control to the
	 *		calling CLI. We need to do this before we start creating ports
	 *		and opening files, to prevent Exec and AmigaDOS getting
	 *		confused about which task we are.
	 *
	 *		Also open stderr channel for the new task to output fatal
	 *		error messages to; the new task takes responsibility for closing
	 *		this channel.
	 */
	stderr = Open("*", MODE_NEWFILE);
	if (!res("SnoopDos", 5, mainloop, 4000)) {
		myprintf("Couldn't spawn background SnoopDos task.\n");
		cleanup(10);
	}
}

/*
 *		NewOpen()
 *		---------
 *		This is the new Open() code. It checks to see if the calling task
 *		is actually the SnoopDos task; if it is, then it essentially does
 *		nothing (this stops any possible deadlock occurring). Otherwise,
 *		it sends a message to the task with the pertinent info.
 *
 *		If sleeping is disabled then if the semaphore isn't immediately
 *		available, a signal is sent to the Snoopdos task telling it that
 *		it's missed trapping a function.
 */
__asm BPTR NewOpen(reg_d1 char *filename, reg_d2 int mode)
{
	BPTR filehandle;
	geta4();
	if (snoopactive && doopen && FindTask(0) != snooptask) {
		if (sleepwait)
			ObtainSemaphore(sem);
		else if (!AttemptSemaphore(sem)) {
			Signal(snooptask, missed_open_sig);
			return (CallOpen(filename, mode));
		}
		sendmsg(MSG_OPEN, mode, filename);
		filehandle = CallOpen(filename, mode);
		sendmsg(MSG_OPEN_DONE, filehandle, 0);
		ReleaseSemaphore(sem);
		return (filehandle);
	} else {
		return (CallOpen(filename, mode));
	}
}
/*
 *		NewLock()
 *		---------
 *		Replacement for Lock(), all the comments for NewOpen() apply
 *		equally here.
 */
__asm BPTR NewLock(reg_d1 char *filename, reg_d2 int mode)
{
	BPTR lock;
	geta4();
	if (snoopactive && dolock && FindTask(0) != snooptask) {
		if (sleepwait)
			ObtainSemaphore(sem);
		else if (!AttemptSemaphore(sem)) {
			Signal(snooptask, missed_lock_sig);
			return (CallLock(filename, mode));
		}
		sendmsg(MSG_LOCK, mode, filename);
		lock = CallLock(filename, mode);
		sendmsg(MSG_LOCK_DONE, lock, 0);
		ReleaseSemaphore(sem);
		return (lock);
	} else {
		return (CallLock(filename, mode));
	}
}

/*
 *		NewLoadSeg()
 *		------------
 *		Replacement for Lock(), all the comments for NewOpen() apply
 *		equally here.
 */
__asm BPTR NewLoadSeg(reg_d1 char *filename)
{
	BPTR seglist;
	geta4();
	if (snoopactive && doloadseg && FindTask(0) != snooptask) {
		if (sleepwait)
			ObtainSemaphore(sem);
		else if (!AttemptSemaphore(sem)) {
			Signal(snooptask, missed_loadseg_sig);
			return (CallLoadSeg(filename));
		}
		sendmsg(MSG_LOADSEG, 0, filename);
		seglist = CallLoadSeg(filename);
		sendmsg(MSG_LOADSEG_DONE, seglist, 0);
		ReleaseSemaphore(sem);
		return (seglist);
	} else {
		return (CallLoadSeg(filename));
	}
}

/*
 *		NewExecute()
 *		------------
 *		Replacement for Execute()
 */
__asm LONG NewExecute(reg_d1 char *command, reg_d2 BPTR in, reg_d3 BPTR out)
{
	geta4();
	if (snoopactive && doexecute && FindTask(0) != snooptask) {
		if (sleepwait)
			ObtainSemaphore(sem);
		else if (!AttemptSemaphore(sem)) {
			Signal(snooptask, missed_execute_sig);
			return(CallExecute(command, in, out));
		}
		sendmsg(MSG_EXECUTE, 0, command);
		ReleaseSemaphore(sem);
	}
	return(CallExecute(command, in, out));
}

/*
 *		NewCurrentDir()
 *		---------------
 *		Replacement for CurrentDir()
 */
__asm BPTR NewCurrentDir(reg_d1 BPTR newlock)
{
	geta4();
	if (snoopactive && docurdir && FindTask(0) != snooptask) {
		if (sleepwait)
			ObtainSemaphore(sem);
		else if (!AttemptSemaphore(sem)) {
			Signal(snooptask, missed_curdir_sig);
			return(CallCurrentDir(newlock));
		}
		sendmsg(MSG_CURDIR, newlock, 0);
		ReleaseSemaphore(sem);
	}
	return(CallCurrentDir(newlock));
}

/*
 *		NewDeleteFile()
 *		---------------
 *		Replacement for DeleteFile()
 */
__asm LONG NewDeleteFile(reg_d1 char *filename)
{
	LONG success;
	geta4();
	if (snoopactive && dodelete && FindTask(0) != snooptask) {
		if (sleepwait)
			ObtainSemaphore(sem);
		else if (!AttemptSemaphore(sem)) {
			Signal(snooptask, missed_delete_sig);
			return(CallDeleteFile(filename));
		}
		sendmsg(MSG_DELETE, 0, filename);
		success = CallDeleteFile(filename);
		sendmsg(MSG_DELETE_DONE, success, 0);
		ReleaseSemaphore(sem);
	} else
		return(CallDeleteFile(filename));
}
