/*
	tee-handler.c

	This is a `T' pipe for AmigaDOS. Now, for example, you can display
	the shell output of a program in a console window and, at the
	same time, capture it into a file.


	This stuff is explicitly declared as Public Domain.
	07-Sep-96 by Michael Krause <rawstyle@blackbox.dame.de>
*/

#include <exec/exec.h>
#include <dos/dos.h>
#include <dos/filehandler.h>

#include <proto/exec.h>
#include <proto/dos.h>

char verstring[] = "$VER: tee-handler v1.0 (7.9.96)";

static struct DosLibrary *DOSBase;
static struct ExecBase *SysBase;

static char colonchar='%', divchar='@';

struct fharg1
{
	ULONG count;
	BPTR fh[1];
};

static void ReturnPacket(struct DosPacket *, struct MsgPort *, LONG, LONG);
static struct DosPacket *GetPacket(struct MsgPort *);
static STRPTR where(STRPTR, char);
static void strncpy(STRPTR, STRPTR, UWORD);
static void replace(STRPTR);

/*----------------- THE ENTRY POINT ------------------------------------*/

void entry(void)
{
	struct MsgPort *procport, *packetport;
	struct DosPacket *packet;
	struct DeviceNode *devnode;
	LONG error=0, err2;
	ULONG i;
	STRPTR names, name1, name2;
	struct fharg1 *thisfharg1;
	BPTR fh;

	SysBase = *(APTR*)4;

	procport = &((struct Process *)FindTask(NULL))->pr_MsgPort;
	WaitPort(procport);
	packet = GetPacket(procport);

	if(DOSBase = (struct DosLibrary *)OpenLibrary(DOSNAME, 36))
	{
		/* We need a new MsgPort because the "built-in" one would interfer
			with our dos.library calls. */
		if(packetport = CreateMsgPort())
		{
			devnode = (struct DeviceNode *)BADDR(packet->dp_Arg3);
			packet->dp_Arg4 = (LONG) packetport;
			devnode->dn_Task = packetport;
			ReturnPacket(packet, packetport, DOSTRUE, 0);

			/* Handler initialized, now process messages */
			for(;;)
			{
				WaitPort(packetport);
				packet=GetPacket(packetport);
				switch(packet->dp_Type)
				{
					case ACTION_FINDOUTPUT:
						err2 = ERROR_NO_FREE_STORE;

						/* First copy the BSTR into a private buffer */
						name1 = (STRPTR)BADDR(packet->dp_Arg3);
						if(names = (STRPTR)AllocVec((*name1)+1, MEMF_ANY))
						{
							strncpy(names, name1+1, *name1);

							/* Count destination files */
							name1 = where(names, ':');
							i = 1;
							while((name2=where(name1,divchar))!=name1)
							{
								name1 = name2;
								i++;
							}

							/* Get private data space */
							if(thisfharg1 = (struct fharg1 *)AllocMem(4*i+1, MEMF_CLEAR))
							{
								thisfharg1->count = i;

								err2 = ERROR_INVALID_COMPONENT_NAME;

								/* Open destination files */
								name1 = where(names, ':');
								for(i=0;i<thisfharg1->count;i++)
								{
									name2 = where(name1, divchar);
									if(name2 != name1)
										name2[-1] = '\0'; /* Terminate string (replace divchar by \0) */
									replace(name1);

									if(where(name1, ':') == name1) /* Require an absolute path */
										break;
									if(!(thisfharg1->fh[i] = Open(name1, MODE_NEWFILE)))
										break;
									name1 = name2;
								}

								if(i==thisfharg1->count)
								{
									/* Now all files successfully opened. */
									fh = (BPTR)BADDR(packet->dp_Arg1);
									((struct FileHandle *)fh)->fh_Arg1 = (LONG)thisfharg1;
									((struct FileHandle *)fh)->fh_Port = DOSFALSE;
									FreeVec(names);
									ReturnPacket(packet, packetport, DOSTRUE, 0);
									break;
								}

								for(i=0;i<thisfharg1->count;i++)
									if(fh=thisfharg1->fh[i])
										Close(fh);
								FreeMem(thisfharg1, 4*i+1);
							}
							FreeVec(names);
						}

						ReturnPacket(packet, packetport, DOSFALSE, err2);
						break;

					case ACTION_WRITE:
						thisfharg1 = (struct fharg1 *)packet->dp_Arg1;
						for(i=0;i<thisfharg1->count;i++)
							Write(thisfharg1->fh[i], (APTR)packet->dp_Arg2, packet->dp_Arg3);
						ReturnPacket(packet, packetport, packet->dp_Arg3, 0);
						break;

					case ACTION_END:
						thisfharg1 = (struct fharg1 *)packet->dp_Arg1;
						for(i=0;i<thisfharg1->count;i++)
							Close(thisfharg1->fh[i]);
						FreeMem(thisfharg1, 4*i+1);
						ReturnPacket(packet, packetport, DOSTRUE, 0);
						/* This was easy, hmm? */
						break;

					default:
						ReturnPacket(packet, packetport, DOSFALSE, ERROR_ACTION_NOT_KNOWN);
						break;
				}
			}

			devnode->dn_Task=NULL;
		}
		else error = ERROR_NO_FREE_STORE;
		CloseLibrary((struct Library*)DOSBase);
	}
	else error = ERROR_OBJECT_NOT_FOUND;

	if(error)
		ReturnPacket(packet, procport, DOSFALSE, error);
}

/*----------------- SUBROUTINES ----------------------------------------*/

void ReturnPacket(struct DosPacket *packet, struct MsgPort *port, LONG res1, LONG res2)
{
	struct MsgPort *mp = packet->dp_Port;

	packet->dp_Port = port;
	packet->dp_Res1 = res1;
	packet->dp_Res2 = res2;

	PutMsg(mp, packet->dp_Link);
}

struct DosPacket *GetPacket(struct MsgPort *port)
{
	struct Message *msg;

	if(msg = GetMsg(port))
		return((struct DosPacket *)msg->mn_Node.ln_Name);
	else
		return(NULL);
}

/* Find character in string and return address of first occurence + 1,
   if not found, return full string */
STRPTR where(STRPTR s, char x)
{
	char a,i=0;

	while(a=s[i++])
		if(a==x) return(&s[i]);

	return(s);
}

void strncpy(STRPTR a, STRPTR b, UWORD c)
{
	for(;;)
	{
		if((WORD)--c<0) break;
		*a++ = *b++;
	}
	*a = '\0';
}

/* Replace first colonchar with colon */
void replace(STRPTR s)
{
	char c;

	while(c=*s++)
		if(c==colonchar)
		{
			s[-1]=':';
			return;
		}
}
