/************************************************************
* MultiUser - MultiUser Task/File Support System				*
* ---------------------------------------------------------	*
* Assign Clone for Non-Binding Assigns								*
* ---------------------------------------------------------	*
* © Copyright 1993-1994 Geert Uytterhoeven						*
* All Rights Reserved.													*
************************************************************/


#include <exec/memory.h>
#include <exec/execbase.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <libraries/multiuser.h>
#include <proto/multiuser.h>
#include <string.h>

#include "MAssign_rev.h"

#include "Locale.h"

char __VersTag__[] = VERSTAG;


#define MAXPATHLEN	512


static BOOL AddAssign(char *name, char *target, BOOL volume, BOOL create, struct ExecBase *SysBase,
							 struct DosLibrary *DOSBase, struct muBase *muBase);
static struct DosPacket *WaitPktPort(struct MsgPort *port, struct ExecBase *SysBase);
static ULONG GetPktOwner(struct DosPacket *pkt, struct muBase *muBase);
static BPTR LockTarget(char *target, struct muUserInfo *uinfo, struct muGroupInfo *ginfo, BOOL create,
							  LONG *error, struct DosLibrary *DOSBase);
static void StripDevName(unsigned char *old, unsigned char *new);
static void Handler(struct DosList *dlist, struct DosList *vlist, char *target, struct MsgPort *pubport,
						  BOOL create, struct ExecBase *SysBase, struct DosLibrary *DOSBase,
						  struct muBase *muBase);


int __saveds Start(char *arg)
{
	struct ExecBase *SysBase;
	struct DosLibrary *DOSBase;
	struct muBase *muBase = NULL;
	struct RDArgs *args;
	LONG argarray[] = {
#define argNAME	0
#define argTARGET	1
#define argVOLUME	2
#define argCREATE	3
		NULL, NULL, NULL, NULL
	};
	LONG error = NULL;
	int rc = RETURN_OK;
	struct muExtOwner *owner;
	char *name;
	int i;

	SysBase = *(struct ExecBase **)4;
	
	if ((!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 37))) ||
		 (!(muBase = (struct muBase *)OpenLibrary("multiuser.library", 39)))) {
		rc = ERROR_INVALID_RESIDENT_LIBRARY;
		goto Exit;
	}
	owner = muGetTaskExtOwner(NULL);
	if (muGetRelationshipA(owner, NULL, NULL) & muRelF_ROOT_UID) {
		args = ReadArgs("NAME/A,TARGET/A,VOLUME/S,CREATE/S", argarray, NULL);
		if (!args)
			error = IoErr();
		else {
			for (i = 0; ((char *)argarray[argNAME])[i] && (((char *)argarray[argNAME])[i] != ':'); i++);
			if (((char *)argarray[argNAME])[i] != ':') {
				VPrintf(GetLocStr(MSG_INVALID_DEVICE), (char **)&argarray[argNAME]);
				rc = RETURN_ERROR;
			} else if (name = AllocVec(i+1, MEMF_CLEAR|MEMF_PUBLIC)) {
				memcpy(name, (char *)argarray[argNAME], i);
				if (!AddAssign(name, (char *)argarray[argTARGET], (BOOL)argarray[argVOLUME],
									(BOOL)argarray[argCREATE], SysBase, DOSBase, muBase))
					rc = RETURN_ERROR;
				FreeVec(name);
			} else
				error = IoErr();
		}
		FreeArgs(args);
	} else {
		PutStr(GetLocStr(MSG_MUSTBEROOT));
		rc = RETURN_ERROR;
	}
	muFreeExtOwner(owner);
	if (error) {
		PrintFault(error, NULL);
		rc = RETURN_ERROR;
	}

Exit:
	CloseLibrary((struct Library *)muBase);
	CloseLibrary((struct Library *)DOSBase);

	return(rc);
}


	/*
	 *		Add the Non-Binding Assign
	 */

static BOOL AddAssign(char *name, char *target, BOOL volume, BOOL create, struct ExecBase *SysBase,
							 struct DosLibrary *DOSBase, struct muBase *muBase)
{
	struct DosList *dlist, *vlist = NULL;
	struct MsgPort *port;
	BOOL res = FALSE;

	if ((port = CreateMsgPort()) && (dlist = MakeDosEntry(name, DLT_DEVICE))) {
		if (!volume || (vlist = MakeDosEntry(name, DLT_VOLUME))) {
			dlist->dol_Task = port;
			dlist->dol_misc.dol_handler.dol_SegList = -1;
			dlist->dol_misc.dol_handler.dol_GlobVec = -1;
			if (vlist)
				vlist->dol_Task = port;
			Forbid();
			if (AddDosEntry(dlist))
				if (!vlist || AddDosEntry(vlist)) {
					Permit();
					Handler(dlist, vlist, target, port, create, SysBase, DOSBase, muBase);
					res = TRUE;
				} else {
					RemDosEntry(dlist);
					Permit();
				}
			else
				Permit();
			if (!res)
				VPrintf(GetLocStr(MSG_ASSIGNCONFLICT), (LONG *)&name);
			if (vlist)
				FreeDosEntry(vlist);
		}
		FreeDosEntry(dlist);
	} else
		PrintFault(IoErr(), NULL);

	DeleteMsgPort(port);
	return(res);
}


	/*
	 *		Wait for a DosPacket at a specified MsgPort
	 */

static struct DosPacket *WaitPktPort(struct MsgPort *port, struct ExecBase *SysBase)
{
	struct Message *msg;
	struct DosPacket *pkt = NULL;

	do
		if (msg = GetMsg(port))
			pkt = (struct DosPacket *)msg->mn_Node.ln_Name;
		else
			WaitPort(port);
	while (!pkt);
	return(pkt);
}


	/*
	 *		Get the Owner of a DosPacket
	 */

static ULONG GetPktOwner(struct DosPacket *pkt, struct muBase *muBase)
{
	struct MsgPort *port;
	struct Task *task;
	ULONG owner = NULL;

	if ((port = pkt->dp_Port) && (port->mp_Flags == PA_SIGNAL) && (task = port->mp_SigTask))
		owner = muGetTaskOwner(task);
	return(owner);
}


	/*
	 *		Lock the Target (and create it if necessary), resolving all Format Specifiers
	 */

static BPTR LockTarget(char *target, struct muUserInfo *uinfo, struct muGroupInfo *ginfo, BOOL create,
							  LONG *error, struct DosLibrary *DOSBase)
{
	BPTR lock = NULL;
	char path[MAXPATHLEN];
	int len = 0, i = 0, slen;
	char *str;

	*error = NULL;
	while (len < MAXPATHLEN)
		if (target[i])
			if (target[i] == '%') {
				switch (target[i+1]) {
					case 'u':
						if (uinfo) {
							str = uinfo->UserID;
Copy:						if ((slen = strlen(str)) <= MAXPATHLEN-len) {
								memcpy(&path[len], str, slen);
								len += slen;
							} else {
								*error = ERROR_LINE_TOO_LONG;
								return(NULL);
							}
						} else {
NotFound:				*error = ERROR_OBJECT_NOT_FOUND;
							return(NULL);
						}
						break;

					case 'h':
						if (uinfo) {
							str = uinfo->HomeDir;
							goto Copy;
						} else
							goto NotFound;

					case 'g':
						if (ginfo) {
							str = ginfo->GroupID;
							goto Copy;
						} else
							goto NotFound;

					case '%':
						path[len++] = '%';
						break;

					default:
						*error = ERROR_BAD_TEMPLATE;
						return(NULL);
				}
				i += 2;
			} else
				path[len++] = target[i++];
		else
			break;
	if (len < MAXPATHLEN) {
		path[len] = '\0';
		if (!(lock = Lock(path, ACCESS_READ)))
			if (!create || !(lock = CreateDir(path)))
				*error = IoErr();
			else {
				UnLock(lock);
				if (!(lock = Lock(path, ACCESS_READ)))
					*error = IoErr();
			}
	} else
		*error = ERROR_LINE_TOO_LONG;
	return(lock);
}


	/*
	 *		Strip the Device Name off of a File Name
	 *
	 *		Note: both old and new are CPTRs to a BSTR!
	 */

static void StripDevName(unsigned char *old, unsigned char *new)
{
	int len, i;

	len = old[0];
	for (i = 0; (i < len) && (old[i+1] != ':'); i++);
	if (i == len)
		memcpy(new, old, len+1);
	else {
		memcpy(new+1, old+i+2, len-i-1);
		new[0] = len-i-1;
	}
}


	/*
	 *		Our pseudo File System
	 */

static void Handler(struct DosList *dlist, struct DosList *vlist, char *target, struct MsgPort *pubport,
						  BOOL create, struct ExecBase *SysBase, struct DosLibrary *DOSBase,
						  struct muBase *muBase)
{
	BOOL die = FALSE;
	char *newpath = NULL;
	struct Task *task;
	BYTE oldpri;
	struct muUserInfo *uinfo;
	struct muGroupInfo *ginfo = NULL, *ginfo2;
	struct MsgPort *privport, *port;
	struct DosPacket *pubpkt, *privpkt;
	BPTR lock;
	ULONG owner;
	struct TagItem tags[3];
	LONG type, arg1, arg2, arg3, arg4, res1, res2;

	if ((uinfo = muAllocUserInfo()) && (ginfo = muAllocGroupInfo()) &&
		 (newpath = AllocVec(256, MEMF_CLEAR|MEMF_PUBLIC)) &&
		 (privpkt = AllocDosObject(DOS_STDPKT, NULL))) {
		task = SysBase->ThisTask;
		oldpri = SetTaskPri(task, 5);
		privport = &((struct Process *)task)->pr_MsgPort;

		do {
			pubpkt = WaitPktPort(pubport, SysBase);
			type = pubpkt->dp_Type;
			arg1 = pubpkt->dp_Arg1;
			arg2 = pubpkt->dp_Arg2;
			arg3 = pubpkt->dp_Arg3;
			arg4 = pubpkt->dp_Arg4;
			switch (type) {
				case ACTION_DIE:
					die = TRUE;
					ReplyPkt(pubpkt, DOSTRUE, NULL);
					break;

				case ACTION_FINDINPUT:
				case ACTION_FINDOUTPUT:
				case ACTION_FINDUPDATE:
				case ACTION_SET_PROTECT:
				case ACTION_SET_COMMENT:
				case ACTION_SET_DATE:
				case ACTION_SET_OWNER:
				case ACTION_LOCATE_OBJECT:
				case ACTION_CREATE_DIR:
				case ACTION_DELETE_OBJECT:
				case ACTION_RENAME_OBJECT:
				case ACTION_MAKE_LINK:
				case ACTION_READ_LINK:
				case ACTION_DISK_CHANGE:
				case ACTION_IS_FILESYSTEM:
				case ACTION_CURRENT_VOLUME:
				case ACTION_FLUSH:
				case ACTION_MORE_CACHE:
					owner = GetPktOwner(pubpkt, muBase);
					uinfo->uid = (owner & muMASK_UID)>>16;
					if (muGetUserInfo(uinfo, muKeyType_uid)) {
						ginfo->gid = owner & muMASK_GID;
						ginfo2 = muGetGroupInfo(ginfo, muKeyType_gid);
						if (lock = LockTarget(target, uinfo, ginfo2, create, &res2, DOSBase)) {
							port = ((struct FileLock *)BADDR(lock))->fl_Task;
							tags[0].ti_Tag = muT_UserID;
							tags[0].ti_Data = (LONG)uinfo->UserID;
							tags[1].ti_Tag = muT_NoLog;
							tags[1].ti_Data = (LONG)TRUE;
							tags[2].ti_Tag = TAG_DONE;
							if (muLoginA(tags)) {
								switch (type) {
									case ACTION_FINDINPUT:
									case ACTION_FINDOUTPUT:
									case ACTION_FINDUPDATE:
										((struct FileHandle *)BADDR(arg1))->fh_Type = port;
									case ACTION_SET_PROTECT:
									case ACTION_SET_COMMENT:
									case ACTION_SET_DATE:
									case ACTION_SET_OWNER:
										StripDevName(BADDR(arg3), newpath);
										arg2 = lock;
										arg3 = MKBADDR(newpath);
PassPkt:								privpkt->dp_Type = type;
										privpkt->dp_Arg1 = arg1;
										privpkt->dp_Arg2 = arg2;
										privpkt->dp_Arg3 = arg3;
										privpkt->dp_Arg4 = arg4;
										SendPkt(privpkt, port, privport);
										WaitPkt();
										res1 = privpkt->dp_Res1;
										res2 = privpkt->dp_Res2;
										break;

									case ACTION_LOCATE_OBJECT:
									case ACTION_CREATE_DIR:
									case ACTION_DELETE_OBJECT:
									case ACTION_RENAME_OBJECT:
									case ACTION_MAKE_LINK:
									case ACTION_READ_LINK:
										StripDevName(BADDR(arg2), newpath);
										arg1 = lock;
										arg2 = MKBADDR(newpath);
										goto PassPkt;

									case ACTION_IS_FILESYSTEM:
									case ACTION_CURRENT_VOLUME:
									case ACTION_FLUSH:
									case ACTION_MORE_CACHE:
										goto PassPkt;
								}
								tags[0].ti_Tag = muT_Quiet;
								tags[0].ti_Data = TRUE;
								tags[1].ti_Tag = TAG_DONE;
								muLogoutA(tags);
							} else {
								res1 = DOSFALSE;
								res2 = ERROR_OBJECT_NOT_FOUND;
							}
							UnLock(lock);
							ReplyPkt(pubpkt, res1, res2);
						} else
							ReplyPkt(pubpkt, DOSFALSE, res2);
					} else
						ReplyPkt(pubpkt, DOSFALSE, ERROR_OBJECT_NOT_FOUND);
					break;

				default:
					ReplyPkt(pubpkt, DOSFALSE, ERROR_ACTION_NOT_KNOWN);
					break;
			}
			if (die) {
				if (dlist && AttemptLockDosList(LDF_DEVICES|LDF_WRITE)) {
					if (RemDosEntry(dlist))
						dlist = NULL;
					UnLockDosList(LDF_DEVICES|LDF_WRITE);
				}
				if (vlist && AttemptLockDosList(LDF_VOLUMES|LDF_WRITE)) {
					if (RemDosEntry(vlist))
						vlist = NULL;
					UnLockDosList(LDF_VOLUMES|LDF_WRITE);
				}
			}
		} while (!die || dlist || vlist);
		SetTaskPri(task, oldpri);
		FreeDosObject(DOS_STDPKT, privpkt);
	} else
		PrintFault(IoErr(), NULL);

	muFreeUserInfo(uinfo);
	muFreeGroupInfo(ginfo);
	FreeVec(newpath);
}
