
/*
 *  IPC.C   25 September 1988
 *
 *  NOTE!   the BSS segment is not initialized to 0.
 */

#include <local/typedefs.h>
#include <local/ipc.h>

extern EXECBASE *SysBase;

static long  IPCLock[2] = { 0, 0 };
static MLIST IPCList = { (MNODE *)&IPCList.mlh_Tail, NULL, (MNODE *)&IPCList.mlh_Head };

extern void *FindName();
extern IPCMSG *lSendIPC2();
extern char *CCall();
extern APTR Duplicate();

/*
 *  Three-pass command parser.
 *  (1) Determine # of arguments and allocate argv array, resolve variables
 *  (2) Determine length of each argument and allocate entries in the array
 *  (3) Fill entries in the array
 *
 *  On return, an filled ARGV array will be returned.  To free the argv
 *  array and elements, call FreeParseCmd(argv).  Individual argv[]
 *  entries may be modified, including stuffing different pointers in,
 *  without effecting FreeParseCmd().  Note that offset -1 in each entry
 *  contains a status byte, currently only bit 0 (was quoted) is used.
 */

lParseCmd(buf, pav, varget, varfree, error, reserved)
char *buf;
char ***pav;
char *((*varget)());
char *((*varfree)());
long *error;
{
    short numac;	    /*	# of argv entries	*/
    char **av = NULL;	    /*	argv array		*/
    long *arglen = NULL;    /*	array of entry lengths	*/
    MLIST VList;	    /*	Hold $variable contents */
    char *endstrvar();      /*  extracts variable name  */
    short ttllen = 0;	    /*	sum of lengths of entries   */

    NewList(&VList);
    *error = 0;

    /*
     *	PASS1, determine # of arguments and retrieve variables
     */

    {
	register char *ptr;
	register short ac = 0;
	register short quo = 0;

	for (ptr = buf; *ptr == ' ' || *ptr == 9; ++ptr);
	for (; *ptr; ++ptr) {
	    switch(*ptr) {
	    case '(':
		++quo;
		break;
	    case ')':
		--quo;
		break;
	    case '\\':
	    case '^':
		if (ptr[1])
		    ++ptr;
		break;
	    case ' ':
	    case 9:
		if (!quo) {
		    while (*ptr == ' ' || *ptr == 9)
			++ptr;
		    if (*ptr)
			++ac;
		    --ptr;
		}
		break;
	    case '$':
		{
		    short len;
		    char tmp[64];
		    char *str;
		    MNODE *vl;

		    ++ptr;
		    str = endstrvar(ptr, &len);
		    if (*ptr == '(')
			++ptr;
		    BMov(ptr, tmp, len);
		    tmp[len] = 0;
		    if (varget && (ptr = (char *)CCall(varget, tmp))) {
			vl = AllocMem(8 + strlen(ptr) + 1, MEMF_PUBLIC);
			if (!vl) {
			    *error = (PERR_NOMEM << 16) | (ptr - buf);
			    goto fail;
			}
			strcpy(vl+1, ptr);
			if (varfree)
			    CCall(varfree, ptr);
		    } else {
			*error = (PERR_NOVAR << 16) | (ptr - buf);
			goto fail;
		    }
		    AddTail(&VList, vl);
		    ptr = str - 1;
		}
		break;
	    }
	}
	++ac;
	numac = ac;
    }

    /*
     *	av[-1]	= master string pointer (see below)
     *	av[-2]	= sizeof av array
     *	av[ac]	= NULL
     *
     *	Pass 2, allocate argv array and storage for the lengths of each
     *		entry.	Determine the length of each entry.
     */

    av = AllocMem(sizeof(char *) * (numac + 3), MEMF_PUBLIC);
    if (!av) {
	*error = (PERR_NOMEM << 16);
	goto fail;
    }
    av += 2;
    av[-2] = (char *)(sizeof(char *) * (numac + 3));
    arglen = AllocMem(sizeof(long) * numac, MEMF_PUBLIC);
    if (!arglen) {
	*error = (PERR_NOMEM << 16);
	goto fail;
    }

    {
	register char *ptr;
	register short ac = 0;
	register short quo = 0;
	register short len = 0;

	for (ptr = buf; *ptr == ' ' || *ptr == 9; ++ptr);
	for (; *ptr; ++ptr) {
	    switch(*ptr) {
	    case '(':
		++quo;
		if (quo != 1)
		    ++len;
		break;
	    case ')':
		--quo;
		if (quo)
		    ++len;
		break;
	    case '\\':
	    case '^':
		if (ptr[1]) {
		    ++ptr;
		    ++len;
		}
		break;
	    case ' ':
	    case 9:
		if (!quo) {
		    while (*ptr == ' ' || *ptr == 9)
			++ptr;
		    if (*ptr) {
			arglen[ac] = len;
			ttllen += len + 2;
			++ac;
			len = 0;
		    }
		    --ptr;
		} else {
		    ++len;
		}
		break;
	    case '$':
		{
		    register MNODE *node = RemHead(&VList);
		    if (node) {
			len += strlen(node+1);
			AddTail(&VList, node);
		    }
		    ptr = endstrvar(ptr + 1, NULL) - 1;
		}
		break;
	    case '\n':
		break;
	    default:
		++len;
		break;
	    }
	}
	arglen[ac] = len;
	ttllen += len + 2;		/* 1 byte status & 1 byte separator */
	++ac;
    }
    {					/* allocate space for entries */
	register short i;
	register char *mem = AllocMem(4+ttllen, MEMF_PUBLIC);
	register char *ptr = mem + 4;

	if (!mem) {
	    *error = (PERR_NOMEM << 16);
	    goto fail;
	}

	*(long *)mem = 4 + ttllen;
	for (i = 0; i < numac; ++i) {
	    *ptr++ = 0; 		/*  status byte */
	    av[i] = ptr;
	    ptr[arglen[i]] = 0;
	    ptr += arglen[i] + 1;
	}
	av[-1] = mem;
    }
    {
	register char *ptr;
	register char *avs = av[0];
	register short ac = 0;
	short quo = 0;

	for (ptr = buf; *ptr == ' ' || *ptr == 9; ++ptr);
	for (; *ptr; ++ptr) {
	    switch(*ptr) {
	    case '\\':
		if (ptr[1])
		    *avs++ = *++ptr;
		break;
	    case '^':
		if (ptr[1])
		    *avs++ = *++ptr & 0x1F;
		break;
	    case '(':
		++quo;
		if (quo != 1)
		    *avs++ = '(';
		else if (avs == av[ac])     /*  quoted argument */
		    avs[-1] = 1;
		break;
	    case ')':
		--quo;
		if (quo) {
		    *avs++ = ')';
		    break;
		}   /* fall through */
	    case ' ':
	    case 9:
		if (!quo) {
		    ++ptr;
		    while (*ptr == ' ' || *ptr == 9)
			++ptr;
		    if (*ptr) {
			*avs = 0;
			avs = av[++ac];
		    }
		    --ptr;
		} else {
		    *avs++ = *ptr;
		}
		break;
	    case '$':
		{
		    register MNODE *node = RemHead(&VList);
		    if (node) {
			strcpy(avs, node + 1);
			avs += strlen(avs);
			FreeMem(node, 8 + strlen(node+1) + 1);
		    }
		    ptr = endstrvar(ptr + 1, NULL) - 1;
		}
		break;
	    case '\n':
		break;
	    default:
		*avs++ = *ptr;
		break;
	    }
	}
	*avs = 0;
	++ac;
    }
    FreeMem(arglen, sizeof(long) * numac);
    *pav = av;
    av[numac] = NULL;
    return(numac);

fail:
    {
	register MNODE *node;

	if (arglen)
	    FreeMem(arglen, sizeof(long) * numac);
	if (av)
	    FreeMem(av - 2, (long)av[-2]);
	while (node = RemHead(&VList))
	    FreeMem(node, 8 + strlen(node+1) + 1);
    }
    *pav = NULL;
    return(0);
}

lFreeParseCmd(av)
char **av;
{
    FreeMem(av[-1], *(long *)av[-1]);
    FreeMem(av - 2, (long)av[-2]);
}

char *
endstrvar(ptr, plen)
short *plen;
register char *ptr;
{
    register short len = 0;
    if (*ptr == '(') {
	++ptr;
	while (*ptr && *ptr != ')') {
	    ++ptr;
	    ++len;
	}
	if (*ptr == ')')
	    ++ptr;
    } else {
	while ((*ptr >= 'a' && *ptr <= 'z') ||
	       (*ptr >= 'A' && *ptr <= 'Z') ||
	       (*ptr >= '0' && *ptr <= '9') ||
	       (*ptr == '_')) {
	    ++len;
	    ++ptr;
	}
    }
    if (len > 62)
	len = 62;
    if (plen)
	*plen = len;
    return(ptr);
}

IPCPORT *
lOpenIPC(name, flags)
char *name;
{
    register IPCPORT *port;
    register char *ptr;

    port = AllocMem(sizeof(IPCPORT), MEMF_PUBLIC|MEMF_CLEAR);
    if (!port)
	goto fail;
    ptr = AllocMem(strlen(name)+1, MEMF_PUBLIC);
    if (!ptr)
	goto fail;
    strcpy(ptr, name);
    port->Flags = flags;
    port->Port.mp_Node.ln_Type = NT_MSGPORT;
    port->Port.mp_Node.ln_Name = ptr;
    port->Port.mp_Flags = PA_SIGNAL;
    port->Port.mp_SigBit = AllocSignal(-1);
    port->Port.mp_SigTask = FindTask(NULL);
    NewList(&port->Port.mp_MsgList);

    LockAddr(IPCLock);
    Insert(&IPCList, port, FindName(&IPCList, name));
    UnLockAddr(IPCLock);
    return(port);
fail:
    if (port) {
	FreeMem(port, sizeof(IPCPORT));
	if (ptr)
	    FreeMem(ptr, strlen(ptr)+1);
    }
    return(NULL);
}

void
lCloseIPC(port)
register IPCPORT *port;
{
    register IPCMSG *msg;

    LockAddr(IPCLock);              /*  remove port (no new messages)   */
    Remove(port);
    UnLockAddr(IPCLock);
    while (msg = GetMsg(port))      /*  reply to pending msgs w/error   */
	ReplyIPC(msg, NULL, 0, IF_NOTFND);
    FreeMem(port->Port.mp_Node.ln_Name, strlen(port->Port.mp_Node.ln_Name)+1);
    if (port->Port.mp_Flags == PA_SIGNAL)
	FreeSignal(port->Port.mp_SigBit);
    FreeMem(port, sizeof(IPCPORT));
}

/*
 *  msg = SendIPC(appname, buf, len, flags)
 */

IPCMSG *
lSendIPC(name, buf, len, flags)
char *name;
APTR buf;
register long len, flags;
{
    register IPCMSG *msg;
    register PORT *rport;

    msg = AllocMem(sizeof(IPCMSG)+sizeof(PORT), MEMF_PUBLIC|MEMF_CLEAR);
    if (msg == NULL)
	return(NULL);
    flags |= IF_ALLOCMSG;	    /*	auto-deallocate later on    */
    rport = (PORT *)(msg + 1);
    if (buf && !(flags & IF_NOCOPY) && !(flags & IF_ALLOC)) {
	if (!(buf = Duplicate(buf, len))) {
	    FreeMem(msg, sizeof(IPCMSG)+sizeof(PORT));
	    return(NULL);
	}
	flags |= IF_ALLOC;
    }
    rport->mp_Node.ln_Type = NT_MSGPORT;
    rport->mp_Flags = PA_SIGNAL;
    rport->mp_SigBit = 4;
    rport->mp_SigTask = SysBase->ThisTask;
    NewList(&rport->mp_MsgList);

    msg->Msg.mn_ReplyPort = rport;
    msg->Msg.mn_Length = sizeof(IPCMSG) + sizeof(PORT);
    msg->TBuf = buf;
    msg->TLen = len;
    msg->TFlags = flags;
    return(lSendIPC2(name, msg));
}

IPCMSG *
lSendIPC2(name, msg)
char *name;
register IPCMSG *msg;
{
    register IPCPORT *port;

    msg->RBuf = NULL;
    msg->RLen = 0;
    msg->RFlags = 0;
    msg->Error	= 0;
    msg->Confirm = NULL;

    LockAddr(IPCLock);
    if (name) {
	port = (IPCPORT *)FindName(&IPCList, name);
    } else {
	port = GetHead(&IPCList);
    }
    msg->ToPort = port;
    if (port) {
	if ((port->Flags & IF_ALLOC) && !(msg->TFlags & IF_ALLOC)) {
	    if ((msg->TBuf = Duplicate(msg->TBuf, msg->TLen)) == NULL) {
		msg->Error = PERR_NOMEM;
		lReplyIPC(msg, NULL, 0, IF_ERROR);
		return(msg);
	    }
	    msg->TFlags |= IF_ALLOC;
	}
	PutMsg(port, msg);
	UnLockAddr(IPCLock);
    } else {
	UnLockAddr(IPCLock);
	lReplyIPC(msg, NULL, 0, IF_ERROR|IF_NOTFND|IF_NOAPP);
    }
    return(msg);
}

/*
 *  DoIPC2(name, msg, collfunc, collport)
 *
 *  Synchronous IPC routine.  To prevent lockouts in case the calling task
 *  also owns an IPC port, if this port is provided 'collfunc' will
 *  automatically be called if new requests arrive while we are waiting
 *  for our request to complete.  This call is reentrant.
 */

void
lDoIPC2(name, msg, func, port)
char *name;
IPCMSG *msg;
void (*func)();
PORT *port;
{
    SendIPC2(name, msg);        /*  send the message            */
    if (port) {
	register long mask1 = 1 << port->mp_SigBit;
	register long mask2 = 1 << msg->Msg.mn_ReplyPort->mp_SigBit;

	while (!CheckMsg(msg)) {
	    if (CheckPort(port)) {
		CCall(func, port);
		continue;
	    }
	    Wait(mask1 | mask2);
	}
    }
    WaitMsg(msg);
}

/*
 *  (void )ReplyIPC(msg, buf, len, flags)
 */

lReplyIPC(msg, buf, len, flags)
register IPCMSG *msg;
APTR buf;
long len, flags;
{
    if (buf && !(flags & (IF_NOCOPY|IF_ALLOC|IF_NOTFND))) {
	buf = Duplicate(buf, len);
	flags |= IF_ALLOC;
    }
    if (buf || !(flags & IF_NOTFND)) {
	if ((msg->RFlags & IF_ALLOC) && msg->RBuf && buf != msg->RBuf)
	    FreeMem(msg->RBuf, msg->RLen);
	msg->RBuf = buf;
	msg->RLen = len;
    }
    msg->RFlags = flags;
    LockAddr(IPCLock);
    if (msg->RFlags & IF_GLOBAL) {
	register IPCPORT *next;

	do {
	    next = GetSucc(next);
	} while (next && !(next->Flags & IF_GLOBAL));
	if (next) {
	    msg->ToPort = next;
	    PutMsg(next, msg);
	} else {
	    if ((msg->TFlags & IF_ALLOC) && msg->TBuf) {
		FreeMem(msg->TBuf, msg->TLen);
		msg->TBuf = NULL;
	    }
	    ReplyMsg(msg);
	}
	UnLockAddr(IPCLock);
	return;
    }
    if (flags & IF_NOTFND) {
	register IPCPORT *next = GetSucc(msg->ToPort);
	if (next && strcmp(msg->ToPort->Port.mp_Node.ln_Name, next->Port.mp_Node.ln_Name) == 0) {
	    msg->ToPort = next;
	    PutMsg(next, msg);
	} else {
	    if ((msg->TFlags & IF_ALLOC) && msg->TBuf) {
		FreeMem(msg->TBuf, msg->TLen);
		msg->TBuf = NULL;
	    }
	    ReplyMsg(msg);
	    msg->RFlags |= IF_ERROR;
	}
	UnLockAddr(IPCLock);
	return;
    }
    UnLockAddr(IPCLock);
    if ((msg->TFlags & IF_ALLOC) && msg->TBuf) {
	FreeMem(msg->TBuf, msg->TLen);
	msg->TBuf = NULL;
    }
    ReplyMsg(msg);
    return;
}

/*
 * (void) lFreeIPC(msg)
 *
 *  Free space associated with the reply buffer and possibly the message
 *  itself.  This call MUST be made after you receive a reply to your
 *  message.
 */

void
lFreeIPC(msg)
register IPCMSG *msg;
{
    if ((msg->RFlags & IF_ALLOC) && msg->RBuf) {
	FreeMem(msg->RBuf, msg->RLen);
	msg->RBuf = NULL;
    }
    if (msg->Confirm)                   /*  Confirmation requested  */
	CCall(msg->Confirm, msg);
    if (msg->TFlags & IF_ALLOCMSG)      /*  Message was allocated   */
	FreeMem(msg, msg->Msg.mn_Length);
}

APTR
Duplicate(buf, len)
APTR buf;
long len;
{
    APTR newbuf;
    if (newbuf = AllocMem(len, TypeOfMem(buf))) {
	BMov(buf, newbuf, len);
	return(newbuf);
    }
    return(NULL);
}

#asm

	    ;	CCall(funcptr, argument)

_CCall:     movem.l 4(sp),A0/A1
	    movem.l D2/D3/A4/A5/A6,-(sp)
	    move.l  A1,-(sp)
	    jsr     (A0)
	    addq.l  #4,sp
	    movem.l (sp)+,D2/D3/A4/A5/A6
	    rts
#endasm

