/*
**	AssignWedge - AmigaDOS 2.04 utility
**
**	Copyright © 1992 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

#include <intuition/intuitionbase.h>
#include <workbench/startup.h>
#include <libraries/locale.h>
#include <dos/dosextens.h>
#include <exec/execbase.h>
#include <libraries/asl.h>
#include <dos/dostags.h>
#include <exec/memory.h>

#include <clib/intuition_protos.h>
#include <clib/utility_protos.h>
#include <clib/locale_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/asl_protos.h>

	/* Create the requester strings. */

#define STRINGARRAY

#include "assignwedge.h"

	/* The requester selection IDs. */

enum	{	REQ_CANCEL, REQ_RETRY, REQ_ASSIGN, REQ_MOUNT, REQ_DENY };

	/* Some handy signal macros. */

#define SIG_KILL	SIGBREAKF_CTRL_C
#define SIG_NOTIFY	(1 << MainPort -> mp_SigBit)

	/* The MC680x0 `jump to absolute address' opcode. */

#define JMP_ABS 0x4EF9

	/* A simple wedge definition which is to consist of a jmp
	 * instruction and the destination of the jump.
	 */

struct Wedge
{
	UWORD		 Command;
	APTR		 Address;
};

	/* Process and command names which are no longer allowed to
	 * access certain paths will be identified by information
	 * to be found in a list. The following structure definition
	 * holds the necessary data (name and process base address).
	 */

struct DenyNode
{
	struct MinNode	 Node;

	struct Process	*Process;
	UBYTE		 Name[40],
			 ProgramName[40];
};

	/* The library vector offset of the intuition.library routine to patch. */

extern ULONG __far	 LVOEasyRequestArgs;

	/* The version ID tag. */

STATIC UBYTE Version[]	= "\0$VER: AssignWedge 1.1 (12.4.92)";

	/* Global and shared library identifiers. */

struct IntuitionBase	*IntuitionBase;
struct ExecBase		*SysBase;
struct DosLibrary	*DOSBase;
struct Library		*UtilityBase;
struct Library		*AslBase;

	/* Locale support. */

struct LocaleBase	*LocaleBase;
struct Catalog		*Catalog;

	/* Registration of programs which are not allowed to
	 * access certain devices.
	 */

struct SignalSemaphore	 DenySemaphore;
struct MinList		 DenyList;

	/* The following counter and the associated access semaphore help
	 * to keep track of the number of programs currently using the
	 * patched EasyRequestArgs() routine.
	 */

struct SignalSemaphore	 RunSemaphore;
LONG			 RunCount;

	/* Handshake data. */

struct Process		*MainProcess;
struct MsgPort		*MainPort;
BYTE			 Removed;

	/* To compensate for possible incompatibilities introduced by internationalized
	 * requester texts, we will try to determine the text to turn up when an
	 * `please insert volume' requester is opened. The `SearchName' will receive
	 * the string to look for.
	 */

UBYTE			 SearchName[256];

	/* Function prototypes. */

LONG __saveds		 Main(VOID);
STRPTR __regargs	 GetString(LONG ID);
LONG __saveds __asm	 NewEasyRequestArgs(register __a0 struct Window *Window,register __a1 struct EasyStruct *EasyStruct,register __a2 ULONG *IDCMPPtr,register __a3 APTR *Args);
LONG			(* __asm OldEasyRequestArgs)(register __a0 struct Window *,register __a1 struct EasyStruct *,register __a2 ULONG *,register __a3 APTR *,register __a6 struct IntuitionBase *);

LONG __saveds
Main()
{
	struct WBStartup	*WBenchMsg	= NULL;
	LONG			 ReturnCode	= RETURN_FAIL;

		/* Set up ExecBase */

	SysBase = *(struct ExecBase **)4;

		/* Determine current process identifier. */

	MainProcess = (struct Process *)SysBase -> ThisTask;

		/* Are we running from CLI? If so, wait for Workbench
		 * startup message. 
		 */

	if(!MainProcess -> pr_CLI)
	{
		WaitPort(&MainProcess -> pr_MsgPort);

		WBenchMsg = (struct WBStartup *)GetMsg(&MainProcess -> pr_MsgPort);
	}

		/* Try to find the global handshake port and if present
		 * send a termination signal.
		 */

	if(MainPort = FindPort("AssignWedge Rendezvous"))
	{
		Signal(MainPort -> mp_SigTask,SIG_KILL);

		ReturnCode = RETURN_OK;
	}
	else
	{
			/* Are we running under Kickstart version 37 or higher? */

		if(SysBase -> LibNode . lib_Version > 36)
		{
				/* Create the global handshake port. */

			if(MainPort = CreateMsgPort())
			{
					/* Give it a name and add it to the public list. */

				MainPort -> mp_Node . ln_Name = "AssignWedge Rendezvous";

				AddPort(MainPort);

					/* Open the required libraries. */

				if(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library",37))
				{
					if(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37))
					{
						if(UtilityBase = OpenLibrary("utility.library",37))
						{
							if(AslBase = OpenLibrary(AslName,37))
							{
								struct Wedge *Wedge;

									/* Try to open locale.library, but don't panic
									 * if it's not available.
									 */

								if(LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library",38))
								{
									if(!(Catalog = OpenCatalog(NULL,"assignwedge.catalog",
										OC_BuiltInLanguage,	"english",
										OC_BuiltInCodeSet,	0,
									TAG_DONE)))
									{
										CloseLibrary(LocaleBase);

										LocaleBase = NULL;
									}
								}

									/* Initialize the access semaphore. */

								InitSemaphore(&RunSemaphore);

								RunCount = 0;

									/* Initialize the access semaphore and
									 * the list of programs to which access
									 * to certain devices has been denied.
									 */

								InitSemaphore(&DenySemaphore);

								NewList((struct List *)&DenyList);

									/* Allocate a system library function wedge. */

								if(Wedge = AllocMem(sizeof(struct Wedge),MEMF_PUBLIC))
								{
									struct DenyNode	*NextNode,
											*DenyNode;

										/* Initialize the wedge. */

									Wedge -> Command = JMP_ABS;
									Wedge -> Address = (APTR)NewEasyRequestArgs;

									Removed = FALSE;

										/* Install the wedge. */

									Forbid();

									OldEasyRequestArgs = (APTR)SetFunction(IntuitionBase,(LONG)&LVOEasyRequestArgs,(APTR)Wedge);

										/* Make sure the data gets written to memory. */

									CacheClearU();

										/* Put up an example requester. Note: this requester
										 * will be trapped by the wedge routine, giving us
										 * the string to look for in the future.
										 */

									ErrorReport(ERROR_DEVICE_NOT_MOUNTED,REPORT_INSERT,(ULONG)"Test:",NULL);

										/* We're up and running now. Note that the
										 * Forbid() will be broken by the Wait() for
										 * a ^C signal.
										 */

									Wait(SIG_KILL);

										/* We are no longer running,
										 * tell the wedge routine to
										 * skip the `access denied'
										 * part which requires the
										 * list to be initialized.
										 */

									Removed = TRUE;

										/* Redirect the wedge pointer to
										 * the original routine.
										 */

									Wedge -> Address = OldEasyRequestArgs;

										/* Make sure that the data
										 * gets written to memory.
										 */

									CacheClearU();

										/* Turn the multitasking back on. */

									Permit();

										/* Clear pending signals. */

									SetSignal(0,SIG_NOTIFY);

										/* Wait until our wedge routine
										 * is no longer in use.
										 */

									while(RunCount)
										Wait(SIG_NOTIFY);

										/* Clear the `access denied' list. */

									DenyNode = (struct DenyNode *)DenyList . mlh_Head;

									while(NextNode = (struct DenyNode *)DenyNode -> Node . mln_Succ)
									{
										FreeVec(DenyNode);

										DenyNode = NextNode;
									}

										/* Successful termination. */

									ReturnCode = RETURN_OK;
								}

									/* Close the resources
									 * we had allocated,
									 * but *not* the wedge
									 * memory.
									 */

								if(Catalog)
									CloseCatalog(Catalog);

								if(LocaleBase)
									CloseLibrary(LocaleBase);

								CloseLibrary(AslBase);
							}

							CloseLibrary(UtilityBase);
						}

						CloseLibrary(IntuitionBase);
					}

					CloseLibrary(DOSBase);
				}

					/* Remove the global handshake port. */

				RemPort(MainPort);

				DeleteMsgPort(MainPort);
			}
		}
	}

		/* If run from Workbench, reply the startup message. */

	if(WBenchMsg)
	{
		Forbid();

		ReplyMsg(&WBenchMsg -> sm_Message);
	}

		/* That's all folks. */

	return(ReturnCode);
}

	/* GetString(LONG ID):
	 *
	 *	Fetch a text from the database.
	 */

STRPTR __regargs
GetString(LONG ID)
{
	STRPTR	Builtin = NULL;
	WORD	i;

		/* Try to find the builtin string to match the ID we received. */

	if(AppStrings[ID] . as_ID != ID)
	{
		for(i = 0 ; i < (sizeof(AppStrings) / sizeof(struct AppString)) ; i++)
		{
			if(AppStrings[i] . as_ID == ID)
			{
				Builtin = AppStrings[i] . as_Str;

				break;
			}
		}
	}
	else
		Builtin = AppStrings[ID] . as_Str;

		/* If locale.library is installed and the database catalog was
		 * successfully opened, query the library's idea of the corresponding
		 * text string. Otherwise, return the builtin string.
		 */

	if(LocaleBase)
		return(GetCatalogStr(Catalog,ID,Builtin));
	else
		return(Builtin);
}

	/* NewEasyRequestArgs():
	 *
	 *	A custom version of the original EasyRequest() routine which
	 *	is to provide enhanced options whenever a DOS handler puts
	 *	up a `REPORT_INSERT' style requester.
	 */

LONG __saveds __asm
NewEasyRequestArgs(register __a0 struct Window *Window,register __a1 struct EasyStruct *EasyStruct,register __a2 ULONG *IDCMPPtr,register __a3 APTR *Args)
{
	struct Process	*ThisProcess = (struct Process *)SysBase -> ThisTask;
	LONG		 Result;

		/* This may be the first call to the new routine, made by the
		 * main program trying to determine the text to look for in
		 * future requesters.
		 */

	if(!SearchName[0] && ThisProcess == MainProcess)
	{
		strcpy(SearchName,Args[0]);

		return(REQ_CANCEL);
	}

		/* Increment the use count. */

	ObtainSemaphore(&RunSemaphore);

	RunCount++;

	ReleaseSemaphore(&RunSemaphore);

		/* Is the caller a process, i.e. will it be able to use all
		 * DOS routines?
		 */

	if(ThisProcess -> pr_Task . tc_Node . ln_Type == NT_PROCESS)
	{
			/* Do the arguments match the pattern we had expected and
			 * are DOS requesters enabled?
			 */

		if(!Strnicmp(EasyStruct -> es_TextFormat,"%s",2) && Args && ThisProcess -> pr_WindowPtr != (APTR)-1)
		{
				/* Did we get any calling parameters? */

			if(Args[0])
			{
					/* Does the first argument match the `please insert volume...' title? */

				if(!Stricmp(Args[0],SearchName))
				{
					UBYTE *DirBuffer;

						/* Allocate a temporary storage buffer
						 * required by a number of routines
						 * lateron.
						 */

					if(DirBuffer = (UBYTE *)AllocVec(512 + 60,MEMF_ANY))
					{
						UBYTE				*HailBuffer;
						struct EasyStruct __aligned	 Easy = *EasyStruct;
						struct DenyNode			*DenyNode;
						struct FileRequester		*AslFileRequest;
						struct Screen			*FirstScreen;
						ULONG				 IntuiLock;
						BPTR				 In,Out;

							/* Gain access to the list of
							 * programs to which access to
							 * some devices has been denied.
							 */

						ObtainSemaphore(&DenySemaphore);

						DenyNode = (struct DenyNode *)DenyList . mlh_Head;

							/* If this process has a CLI structure
							 * attached, look for a command name
							 * to match a list entry.
							 */

						if(ThisProcess -> pr_CLI)
						{
								/* Process the list... */

							while(DenyNode -> Node . mln_Succ)
							{
									/* Does this entry refer to
									 * a command name?
									 */

								if(DenyNode -> ProgramName[0])
								{
										/* Does the name of the device
										 * in question match the name
										 * in the list entry?
										 */

									if(!Stricmp(DenyNode -> Name,Args[1]))
									{
											/* Obtain the name of the program
											 * currently running.
											 */

										if(GetProgramName(DirBuffer,512))
										{
												/* Does the name match the one
												 * in the list entry?
												 */

											if(!Stricmp(FilePart(DirBuffer),DenyNode -> ProgramName))
											{
													/* Release the access semaphore. */

												ReleaseSemaphore(&DenySemaphore);

													/* Free the temporary storage buffer. */

												FreeVec(DirBuffer);

													/* Decrement use count. */

												ObtainSemaphore(&RunSemaphore);

												RunCount--;

												ReleaseSemaphore(&RunSemaphore);

													/* Tell the main process to take a
													 * look at the use count.
													 */

												Signal(MainProcess,SIG_NOTIFY);

													/* Return failure. */

												return(REQ_CANCEL);
											}
										}
									}
								}

									/* Proceed to the next list entry. */

								DenyNode = (struct DenyNode *)DenyNode -> Node . mln_Succ;
							}
						}
						else
						{
								/* Run down the list. */

							while(DenyNode -> Node . mln_Succ)
							{
									/* Does the process identifier match the
									 * one in the list entry?
									 */

								if(DenyNode -> Process == ThisProcess)
								{
										/* Does the name of the device in
										 * question match the one in the
										 * list entry?
										 */

									if(!Stricmp(DenyNode -> Name,Args[1]))
									{
											/* Release the access semaphore. */

										ReleaseSemaphore(&DenySemaphore);

											/* Free the temporary storage buffer. */

										FreeVec(DirBuffer);

											/* Decrement use count. */

										ObtainSemaphore(&RunSemaphore);

										RunCount--;

										ReleaseSemaphore(&RunSemaphore);

											/* Tell the main process to take a
											 * look at the use count.
											 */

										Signal(MainProcess,SIG_NOTIFY);

											/* Return failure. */

										return(REQ_CANCEL);
									}
								}

									/* Proceed to the next entry. */

								DenyNode = (struct DenyNode *)DenyNode -> Node . mln_Succ;
							}
						}

							/* Release the access semaphore. */

						ReleaseSemaphore(&DenySemaphore);

							/* Split the buffer to create the directory
							 * requester title string.
							 */

						HailBuffer = DirBuffer + 512;

							/* Fill in the `Retry|Assign|Mount|Deny|Cancel' buttons. */

						Easy . es_GadgetFormat = GetString(MSG_PROMPT_GAD);

							/* Put up the requester and decide what to do. */

						switch(Result = OldEasyRequestArgs(Window,&Easy,IDCMPPtr,Args,IntuitionBase))
						{
							case REQ_ASSIGN:

									/* Try to obtain the name of the current directory,
									 * most programs to look for assignments or volume
									 * names will be happy if assignments are made
									 * referring to their home directories.
									 */

								if(!GetCurrentDirName(DirBuffer,512))
									DirBuffer[0] = 0;

									/* Set up the window title. */

								sprintf(HailBuffer,GetString(MSG_HAIL_GAD),Args[1]);

									/* create the directory requester. */

								if(AslFileRequest = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
									ASL_Dir,	DirBuffer,
									ASL_Hail,	HailBuffer,
									ASL_OKText,	GetString(MSG_ASSIGN_GAD),
									ASL_Window,	Window,
									ASL_FuncFlags,	FILF_NEWIDCMP,
									ASL_ExtFlags1,	FIL1F_NOFILES,
								TAG_DONE))
								{
										/* Remember the first screen ID. */

									IntuiLock = LockIBase(NULL);

									FirstScreen = IntuitionBase -> FirstScreen;

									UnlockIBase(IntuiLock);

										/* Move the screen the requester is to appear on
										 * up if necessary.
										 */

									if(Window -> WScreen -> TopEdge > 0)
										MoveScreen(Window -> WScreen,0,-Window -> WScreen -> TopEdge);

										/* Move the screen to the front. */

									ScreenToFront(Window -> WScreen);

										/* Display the requester. */

									while(AslRequestTags(AslFileRequest,TAG_DONE))
									{
										APTR OldWindowPtr = ThisProcess -> pr_WindowPtr;
										BPTR FileLock;

											/* Disable the system requesters. */

										ThisProcess -> pr_WindowPtr = (APTR)-1;

											/* Try to access the directory the
											 * user has just selected.
											 */

										if(FileLock = Lock(AslFileRequest -> rf_Dir,ACCESS_READ))
										{
												/* Try to create the assignment. */

											if(!AssignLock(Args[1],FileLock))
											{
													/* Oops, something went wrong. */

												DisplayBeep(Window -> WScreen);

												UnLock(FileLock);
											}
											else
											{
													/* Restore the window pointer. */

												ThisProcess -> pr_WindowPtr = OldWindowPtr;

												break;
											}
										}
										else
											DisplayBeep(Window -> WScreen);

											/* Restore the window pointer. */

										ThisProcess -> pr_WindowPtr = OldWindowPtr;
									}

										/* Free the requester data. */

									FreeAslRequest(AslFileRequest);

										/* Pop the screen which was frontmost
										 * before the requester was displayed
										 * back to the front.
										 */

									IntuiLock = LockIBase(0);

									if(FirstScreen == IntuitionBase -> FirstScreen)
										UnlockIBase(IntuiLock);
									else
									{
										struct Screen	*Screen = IntuitionBase -> FirstScreen;
										BYTE		 IsValid = FALSE;

											/* Try to determine if the screen ID we
											 * remembered is still valid.
											 */

										while(Screen && !IsValid)
										{
											if(Screen == FirstScreen)
												IsValid = TRUE;
											else
												Screen = Screen -> NextScreen;
										}

										Forbid();

										UnlockIBase(IntuiLock);

											/* Push the screen to the front. */

										if(IsValid)
											ScreenToFront(FirstScreen);

										Permit();
									}
								}

									/* Free the directory buffer. */

								FreeVec(DirBuffer);

									/* Decrement use count. */

								ObtainSemaphore(&RunSemaphore);

								RunCount--;

								ReleaseSemaphore(&RunSemaphore);

									/* Tell the main process to take a
									 * look at the use count.
									 */

								Signal(MainProcess,SIG_NOTIFY);

									/* Tell AmigaDOS to take a second look at the
									 * device list.
									 */

								return(REQ_RETRY);

							case REQ_MOUNT:

									/* Open input stream. */

								if(In = Open("NIL:",MODE_OLDFILE))
								{
										/* Open output stream. */

									if(Out = Open("NIL:",MODE_OLDFILE))
									{
										APTR OldPtr = ThisProcess -> pr_WindowPtr;

											/* Enter the mount command string. */

										sprintf(DirBuffer,"Mount >NIL: <NIL: %s:",Args[1]);

											/* Disable DOS requesters. */

										ThisProcess -> pr_WindowPtr = (APTR)-1;

											/* Execute the mount command. */

										SystemTags(DirBuffer,
											SYS_Input,	In,
											SYS_Output,	Out,
										TAG_DONE);

											/* Restore window pointer. */

										ThisProcess -> pr_WindowPtr = OldPtr;

											/* Close output stream. */

										Close(Out);
									}

										/* Close input stream. */

									Close(In);
								}

									/* Free the directory buffer. */

								FreeVec(DirBuffer);

									/* Decrement use count. */

								ObtainSemaphore(&RunSemaphore);

								RunCount--;

								ReleaseSemaphore(&RunSemaphore);

									/* Tell the main process to take a
									 * look at the use count.
									 */

								Signal(MainProcess,SIG_NOTIFY);

									/* Tell AmigaDOS to take a second look at the
									 * device list.
									 */

								return(REQ_RETRY);

							case REQ_DENY:

									/* If not about to be removed, allocate an `access denied' node. */

								if(!Removed)
								{
									if(DenyNode = (struct DenyNode *)AllocVec(sizeof(struct DenyNode),MEMF_CLEAR))
									{
											/* Fill in the current process ID. */

										DenyNode -> Process = ThisProcess;

											/* Copy the name of the device in question. */

										strcpy(DenyNode -> Name,Args[1]);

											/* If this process has a CLI structure attached,
											 * try to remember the program name.
											 */

										if(ThisProcess -> pr_CLI)
										{
											if(GetProgramName(DirBuffer,512))
												strcpy(DenyNode -> ProgramName,FilePart(DirBuffer));
										}

											/* Gain access to the list. */

										ObtainSemaphore(&DenySemaphore);

											/* Add the entry to the list. */

										AddTail((struct List *)&DenyList,(struct Node *)DenyNode);

											/* Release the semaphore again. */

										ReleaseSemaphore(&DenySemaphore);
									}
								}

									/* Free the directory buffer. */

								FreeVec(DirBuffer);

									/* Decrement use count. */

								ObtainSemaphore(&RunSemaphore);

								RunCount--;

								ReleaseSemaphore(&RunSemaphore);

									/* Tell the main process to take a
									 * look at the use count.
									 */

								Signal(MainProcess,SIG_NOTIFY);

									/* Return failure. */

								return(REQ_CANCEL);

							default:

									/* Free the directory buffer. */

								FreeVec(DirBuffer);

									/* Decrement use count. */

								ObtainSemaphore(&RunSemaphore);

								RunCount--;

								ReleaseSemaphore(&RunSemaphore);

									/* Tell the main process to take a
									 * look at the use count.
									 */

								Signal(MainProcess,SIG_NOTIFY);

									/* Return the result (can be either `retry' or `cancel'). */

								return(Result);
						}
					}
				}
			}
		}
	}

		/* In any other case, use the standard call. */

	Result = OldEasyRequestArgs(Window,EasyStruct,IDCMPPtr,Args,IntuitionBase);

		/* Decrement use count. */

	ObtainSemaphore(&RunSemaphore);

	RunCount--;

	ReleaseSemaphore(&RunSemaphore);

		/* Tell the main process to take a
		 * look at the use count.
		 */

	Signal(MainProcess,SIG_NOTIFY);

		/* Return the result. */

	return(Result);
}
