/*
 * $Id: server.c,v 1.1 1993/12/02 20:45:46 Rhialto Exp $
 * $Log: server.c,v $
 * Revision 1.1  1993/12/02  20:45:46  Rhialto
 * Initial revision
 *
 * The bulk of the server code: receive packets and act upon them.
 */

#include "netfs.h"
#include <stdio.h>
#include <string.h>
#include "getopt.c"

#ifdef DEBUG
#   include "syslog.h"
#else
#   define	debug(x)
#endif

long		UnitNr;
char	       *DevName = "parnet.device";
ULONG		DevFlags;
struct DosPacket *DosPkt;
ULONG		WaitMask;
ULONG		Validation;
ULONG		MyValidation;
struct MsgPort *DosPort;
struct MsgPort *AsyncDosPort;
struct Process *Proc;
void	       *pr_WindowPtr = -2;

void
chkabort(void)
{
    /* do not check for break */
}

void
dumppkt(Packet *pkt)
{
    debug(("Packet: %08lx; %08lx, %08lx %08lx %08lx %08lx %08lx\n",
	  *(long *)pkt, pkt->p_Action,
	  pkt->p_Arg[0], pkt->p_Arg[1], pkt->p_Arg[2],
	  pkt->p_Arg[3], pkt->p_Arg[4], pkt->p_Arg[5]));
}

Prototype ULONG dopacket(struct DosPacket *packet, struct MsgPort *port);

ULONG
dopacket(struct DosPacket *packet, struct MsgPort *port)
{
    packet->dp_Port = DosPort;

    PutMsg(port, packet->dp_Link);
    WaitPort(DosPort);
    GetMsg(DosPort);

    return packet->dp_Res1;
}

void
doasyncpacket(struct DosPacket *packet, struct MsgPort *port)
{
    packet->dp_Port = AsyncDosPort;

    PutMsg(port, packet->dp_Link);
}

/*
 * Create a DOS style packet: a message and a dos packet linked to
 * each other. The size of the message is variable so you can include
 * extra information behind it, or use it as an I/O request.
 */

struct DosPacket *
CreateDosPkt(int size)
{
    struct DosPacket *dp;
    struct Message *msg;

    size += sizeof(struct Message) + sizeof(struct DosPacket);

    if (msg = AllocMem(size, MEMF_PUBLIC | MEMF_CLEAR)) {
	dp = (char *)msg + size - sizeof(struct DosPacket);

	dp->dp_Link = msg;
	msg->mn_Node.ln_Name = (char *)dp;
	msg->mn_Length = size;
    }
    return dp;
}

void
DeleteDosPkt(struct DosPacket *dp)
{
    if (dp) {
	struct Message *msg = dp->dp_Link;

	FreeMem(msg, msg->mn_Length);
    }
}

struct MsgPort *
FindProc(BPTR fl, char *bstr)
{
    struct FileLock *lock;

    if (strchr(bstr + 1, ':')) {
	return DeviceProc(bstr + 1);
    }

    lock = BTOC(fl);

    return lock->fl_Task;
}

UBYTE *
SkipString(UBYTE *s)
{
    s = s + s[0] + 2;
    s = (UBYTE *)((ULONG)(s + 3) & ~3);
    return s;
}

/* ---------------------------------------------------------------------- */

void
HandleRequest(struct IOParReq *io, Packet *pkt)
{
    LONG	    tmp;
    ULONG	    replysize;

    debug(("type %d action %d\n", pkt->p_Type, pkt->p_Action));

    switch (DoProtocol(io, pkt)) {
    case pt_Request:
	break;
    default:
	return;
    }

    /* Now we know it's a request, we can do something about it. */

    replysize = STDREPLY;
    Proc->pr_Result2 = 0;

    switch (pkt->p_Action) {
/*  case ACTION_DIE:	       / *  attempt to die?		     */
/*  case ACTION_CURRENT_VOLUME:/ * -		       VolNode,UnitNr*/
    case ACTION_RENAME_DISK:	/* New name		   Bool      */
	{
	    char *newname;

	    newname = SkipString((char *)&pkt->p_Arg[0])+1;
	    debug(("Relabel '%s' -> '%s'\n", ((char *)&pkt->p_Arg[0])+1, newname));
	    pkt->p_Res1 = Relabel((char *)&pkt->p_Arg[0]+1, newname);
	    pkt->p_Res2 = Proc->pr_Result2;
	}
	break;
    case ACTION_LOCATE_OBJECT:	/* Lock,Name,Mode	Lock	     */
	tmp = CurrentDir(pkt->p_Arg[0]);
	pkt->p_Res1 = Lock((char *)&pkt->p_Arg[2]+1, pkt->p_Arg[1]);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
    case ACTION_FREE_LOCK:	/* Lock 		   Bool      */
	UnLock(pkt->p_Arg[0]);
	pkt->p_Res1 = DOSTRUE;
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_DELETE_OBJECT:	/* Lock,Name		Bool	     */
	tmp = CurrentDir(pkt->p_Arg[0]);
	pkt->p_Res1 = DeleteFile((char *)&pkt->p_Arg[1]+1);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
    case ACTION_RENAME_OBJECT:	/* SLock,SName,DLock,DName   Bool    */
	{
	    struct MsgPort *handler;
	    char *sname;
	    char *dname;

	    sname = (char *)(&pkt->p_Arg[2]);
	    dname = SkipString(sname);

	    DosPkt->dp_Action = ACTION_RENAME_OBJECT;
	    DosPkt->dp_Arg1 = pkt->p_Arg[0];
	    DosPkt->dp_Arg2 = CTOB(sname);
	    DosPkt->dp_Arg3 = pkt->p_Arg[1];
	    DosPkt->dp_Arg4 = CTOB(dname);

	    handler = FindProc(pkt->p_Arg[0], sname+1);
	    if (handler == NULL)
		handler = FindProc(pkt->p_Arg[1], dname+1);
	    if (handler != NULL) {
		dopacket(DosPkt, handler);

		pkt->p_Res1 = DosPkt->dp_Res1;
		pkt->p_Res2 = DosPkt->dp_Res2;
	    } else {
		pkt->p_Res1 = DOSFALSE;
		pkt->p_Res2 = ERROR_INVALID_LOCK;
	    }
	}
	break;
    case ACTION_COPY_DIR:	/* Lock 		   Lock      */
	pkt->p_Res1 = DupLock(pkt->p_Arg[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_SET_PROTECT:	/* -,Lock,Name,Mask	   Bool      */
	tmp = CurrentDir(pkt->p_Arg[0]);
	pkt->p_Res1 = SetProtection((char *)&pkt->p_Arg[2]+1, pkt->p_Arg[1]);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
    case ACTION_CREATE_DIR:	/* Lock,Name		Lock	     */
	tmp = CurrentDir(pkt->p_Arg[0]);
	pkt->p_Res1 = CreateDir((char *)&pkt->p_Arg[1]+1);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
/*
 * Note that for ExNext() the FileInfoBlock is copied from the client
 * to the server, and back again. This is because ExNext needs the fib
 * to be initialised by a previous call. In fact, it even may insist
 * on the *very same* fib to be used in each call in a sequence.
 * Possibly it may even require that several simultaneous scans of the
 * same directory use *different* fibs. Obviously, we can't comply with
 * those requirements. For the moment it seems to work ok, but who knows
 * it might break in the future!
 * Also, because the fib is copied in and out, we refer to it through
 * the incoming field names of the packet exclusively. Care must be
 * taken with overlapping fields in request and reply.
 */
    case ACTION_EXAMINE_OBJECT: /* Lock,Fib	       Bool	     */
    case ACTION_EXAMINE_NEXT:	/* Lock,Fib	       Bool	     */
	DosPkt->dp_Action = pkt->p_Action;
	DosPkt->dp_Arg1 = pkt->p_Arg[0];
	DosPkt->dp_Arg2 = CTOB(&pkt->p_Arg[1]);

	dopacket(DosPkt, ((struct FileLock *)BTOC(pkt->p_Arg[0]))->fl_Task);

	pkt->p_Res2 = DosPkt->dp_Res2;
	if (pkt->p_Res1 = DosPkt->dp_Res1)
	    replysize = STDREPLY + sizeof(struct FileInfoBlock);
#ifdef xDEBUG
	{
	    struct FileInfoBlock *fib;
	    fib = (struct FileInfoBlock *)&pkt->p_Data[0];
	    debug(("key %x size %d name '%.*s'\n", fib->fib_DiskKey, fib->fib_Size,
		    fib->fib_FileName[0], &fib->fib_FileName[1]));
	}
#endif
	break;
/*  case ACTION_DISK_INFO:     / * InfoData	       Bool:TRUE     */
    case ACTION_INFO:	/* Lock,InfoData	       Bool:TRUE     */
	pkt->p_Res1 = Info(pkt->p_Arg[0], (void *)&pkt->p_Data[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	if (pkt->p_Res1)
	    replysize = STDREPLY + sizeof(struct InfoData);
	break;
/*  case ACTION_FLUSH:	       / * writeout bufs, disk motor off     */
    case ACTION_SET_COMMENT:	/* -,Lock,Name,Comment	   Bool      */
	tmp = CurrentDir(pkt->p_Arg[0]);
	pkt->p_Res1 = SetComment((char *)&pkt->p_Arg[1]+1,
				 SkipString((char *)&pkt->p_Arg[1])+1);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
	break;
    case ACTION_PARENT: /* Lock 		       ParentLock    */
	pkt->p_Res1 = ParentDir(pkt->p_Arg[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
/*  case ACTION_INHIBIT:       / * Bool 		   Bool      */
    case ACTION_SET_DATE: /* -,Lock,Name,CPTRDateStamp	   Bool      */
	tmp = CurrentDir(pkt->p_Arg[0]);
/*V37*/ pkt->p_Res1 = SetFileDate((char *)&pkt->p_Arg[4]+1,
				  (struct DateStamp *)&pkt->p_Arg[1]);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
    case ACTION_SAME_LOCK:  /* Lock1,Lock2		   Result    */
	if (pkt->p_Arg[0] == 0 || pkt->p_Arg[1] == 0) {
	    /* We never send 0 locks */
	    pkt->p_Res1 = DOSFALSE;
	    pkt->p_Res2 = ERROR_INVALID_LOCK;
	} else {
	    DosPkt->dp_Action = ACTION_SAME_LOCK;
	    DosPkt->dp_Arg1 = pkt->p_Arg[0];
	    DosPkt->dp_Arg2 = pkt->p_Arg[1];

	    dopacket(DosPkt, ((struct FileLock *)BTOC(pkt->p_Arg[0]))->fl_Task);

	    pkt->p_Res1 = DosPkt->dp_Res1;
	    pkt->p_Res2 = DosPkt->dp_Res2;
	}
	break;
    case ACTION_READ:	/* FHArg1,CPTRBuffer,Length	  ActLength  */
	tmp = Read(pkt->p_Arg[0], &pkt->p_Data[0], pkt->p_Arg[1]);
	pkt->p_Res1 = tmp;
	pkt->p_Res2 = Proc->pr_Result2;
	if (tmp > 0)
	    replysize = STDREPLY + tmp;
	break;
    case ACTION_WRITE:	/* FHArg1,CPTRBuffer,Length	  ActLength  */
	pkt->p_Res1 = Write(pkt->p_Arg[0], &pkt->p_Arg[2], pkt->p_Arg[1]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_OPENRW: 	/* FileHandle,Lock,Name    Bool      */
    case ACTION_OPENOLD:	/* FileHandle,Lock,Name    Bool      */
    case ACTION_OPENNEW:	/* FileHandle,Lock,Name    Bool      */
	tmp = CurrentDir(pkt->p_Arg[0]);
	pkt->p_Res1 = Open((char *)&pkt->p_Arg[1]+1, pkt->p_Action);
	pkt->p_Res2 = Proc->pr_Result2;
	CurrentDir(tmp);
	break;
    case ACTION_CLOSE:	/* FHArg1			Bool:Success */
	pkt->p_Res1 = Close(pkt->p_Arg[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_SEEK:	/* FHArg1,Position,Mode 	 OldPosition */
	pkt->p_Res1 = Seek(pkt->p_Arg[0], pkt->p_Arg[1], pkt->p_Arg[2]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
#ifdef notdef
    case ACTION_FORMAT: /* vol,type			Bool:success */
	break;
#endif
    case ACTION_MAKE_LINK: /* parent,name,target,mode	   Bool      */
	{
	    ULONG soft;

	    tmp = CurrentDir(pkt->p_Arg[0]);
	    soft = pkt->p_Arg[1];
	    dumppkt(pkt);
	    if (soft) {
		debug(("MakeLink soft '%s' -> '%s'\n",
		       SkipString((char *)&pkt->p_Arg[2])+1,
		       (char *)&pkt->p_Arg[2]+1));
/*V37*/ 	pkt->p_Res1 = MakeLink(SkipString((char *)&pkt->p_Arg[2])+1,
				       (ULONG)(char *)&pkt->p_Arg[2]+1,
				       soft);
	    } else {
		pkt->p_Res1 = MakeLink((char *)&pkt->p_Arg[3]+1,
				       pkt->p_Arg[2],
				       soft);
	    }
	    pkt->p_Res2 = Proc->pr_Result2;
	    CurrentDir(tmp);
	}
	break;
    case ACTION_READ_LINK: /* parent,name,target,size	   length    */
	{
	    struct FileLock *fl;
	    ULONG	    size;

	    fl = BTOC(pkt->p_Arg[0]);
	    size = min(pkt->p_Arg[1], MAXDATA);

/*V37*/     pkt->p_Res1 = ReadLink(fl->fl_Task,
				   pkt->p_Arg[0],
				   (char *)&pkt->p_Arg[2]+1,
				   (char *)&pkt->p_Data[0],
				   size);
	    pkt->p_Res2 = Proc->pr_Result2;
	    if (pkt->p_Res1 >= 0)
		replysize = STDREPLY + pkt->p_Res1;
	}
	break;
    case ACTION_SET_FILE_SIZE:
/*V37*/ pkt->p_Res1 = SetFileSize(pkt->p_Arg[0], pkt->p_Arg[1], pkt->p_Arg[2]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
/*  case ACTION_WRITE_PROTECT: */
    case ACTION_FH_FROM_LOCK: /* FH,Lock		    BOOL     */
/*V37*/ pkt->p_Res1 = OpenFromLock(pkt->p_Arg[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_CHANGE_MODE:
/*V37*/ pkt->p_Res1 = ChangeMode(pkt->p_Arg[0], pkt->p_Arg[1], pkt->p_Arg[2]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_COPY_DIR_FH:	/* fh_Arg1		   Lock      */
/*V37*/ pkt->p_Res1 = DupLockFromFH(pkt->p_Arg[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_PARENT_FH:
/*V37*/ pkt->p_Res1 = ParentOfFH(pkt->p_Arg[0]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    case ACTION_EXAMINE_FH:	/* fh_Arg1,Fib		   Bool      */
	{
	    struct FileHandle *fh;

	    fh = (struct FileHandle *)BTOC(pkt->p_Arg[0]);
	    DosPkt->dp_Action = ACTION_EXAMINE_FH;
	    DosPkt->dp_Arg1 = fh->fh_Arg1;
	    DosPkt->dp_Arg2 = CTOB(&pkt->p_Data[0]);

	    dopacket(DosPkt, fh->fh_Type);

	    pkt->p_Res2 = DosPkt->dp_Res2;
	    if (pkt->p_Res1 = DosPkt->dp_Res1)
		replysize = STDREPLY + sizeof(struct FileInfoBlock);
	}
	break;
    case ACTION_LOCK_RECORD:	/* fh,pos,len,mode,time    Bool      */
	{
	    struct DosPacket *dp;
	    struct FileHandle *fh;

	    fh = (struct FileHandle *)BTOC(pkt->p_Arg[0]);
	    dp = CreateDosPkt(2*sizeof(long));
	    debug(("LOCK_RECORD %x, dp %x\n", fh, dp));
	    dp->dp_Action = ACTION_LOCK_RECORD;
	    dp->dp_Arg1 = fh->fh_Arg1;
	    dp->dp_Arg2 = pkt->p_Arg[1];
	    dp->dp_Arg3 = pkt->p_Arg[2];
	    dp->dp_Arg4 = pkt->p_Arg[3];
	    dp->dp_Arg5 = pkt->p_Arg[4];

	    ((long *)dp)[-1] = pkt->p_Origin;
	    ((long *)dp)[-2] = pkt->p_Arg[5];     /* client cookie */

	    doasyncpacket(dp, fh->fh_Type);
	}
	break;
    case ACTION_FREE_RECORD:	/* fh,pos,len		   Bool      */
/*V37*/ pkt->p_Res1 = UnLockRecord(pkt->p_Arg[0], pkt->p_Arg[1], pkt->p_Arg[2]);
	pkt->p_Res2 = Proc->pr_Result2;
	break;
    default:
	pkt->p_Res1 = DOSFALSE;
	pkt->p_Res2 = ERROR_ACTION_NOT_KNOWN;
	break;
    }

    debug(("res: %d err: %d\n", pkt->p_Res1, pkt->p_Res2));
    DoReply(io, pkt, replysize);
}

void
HandleAsyncDosPkt(struct Message *msg)
{
    char	    buf[STDREPLY + 2*sizeof(long)];
    Packet	   *p = buf;
    struct DosPacket *dp = (struct DosPacket *)msg->mn_Node.ln_Name;

    debug(("HandleAsyncDosPkt(msg %x) dp %x res: %x/%d\n",
	   msg, dp, dp->dp_Res1, dp->dp_Res2));

    p->p_Res1 = dp->dp_Res1;
    p->p_Res2 = dp->dp_Res2;
    p->p_Data[0] = dp->dp_Action;

    switch (dp->dp_Action) {
    case ACTION_LOCK_RECORD:
	p->p_Origin  = ((long *)dp)[-1];
	p->p_Data[1] = ((long *)dp)[-2];            /* client cookie */
	break;
    default:
	debug(("Funny DOS packet returning %x %d\n", msg, dp->dp_Action));
	goto end;
    }

    DoReplyAsync(ParReq, p, sizeof(buf));
    DeleteDosPkt(dp);

end:;
}

/* ---------------------------------------------------------------------- */

Prototype void DoAsyncReply(Packet *pkt);

void
DoAsyncReply(Packet *pkt)
{
    debug(("We're not supposed to get these!\n"));
}


/* ---------------------------------------------------------------------- */

ULONG
InitServer(void)
{
    debug(("InitServer\n"));

    DosPkt = CreateDosPkt(0);
    DosPort = CreatePort("NetServer DosPort", -1);
    AsyncDosPort = CreatePort("NetServer AsyncDosPort", -1);

    WaitMask = 1L << RdPort->mp_SigBit |
	       1L << AsyncDosPort->mp_SigBit |
	       SIGBREAKF_CTRL_C;

    Proc = (struct Process *)FindTask(NULL);
    pr_WindowPtr = Proc->pr_WindowPtr;
    Proc->pr_WindowPtr = (void *)-1;

    return 0;
}

void
CleanupServer(void)
{
    debug(("CleanupServer\n"));
    if (pr_WindowPtr != (void *)-2)
	Proc->pr_WindowPtr = pr_WindowPtr;
    if (DosPkt) {
	DeleteDosPkt(DosPkt);
	DosPkt = NULL;
    }
}

void
ServeLoop(void)
{
    ULONG	    sigs;

    while (1) {
	debug(("ServeLoop: Wait ...\n"));
	sigs = Wait(WaitMask);
	{
	    struct Message *msg;

	    while (msg = GetMsg(AsyncDosPort)) {
		HandleAsyncDosPkt(msg);
	    }
	}
	if (sigs & SIGBREAKF_CTRL_C)
	    break;
	{
	    struct IOParReq *io;
	    Packet	   *pkt;

	    while (io = (struct IOParReq *)GetMsg(RdPort)) {
		pkt = ((struct IOStdReq *)io)->io_Data;
		if (((struct IOStdReq *)io)->io_Error == 0)
		    HandleRequest(io, pkt);
		else
		    DoReset(ParReq);
		SendRead(io, pkt, PKTSIZE);
	    }
	}
    }
}

/* ---------------------------------------------------------------------- */

int
main(int argc, char **argv)
{
    int 	    c;

#ifdef DEBUG
    initsyslog();
#endif				/* DEBUG */

    if (DOSBase->dl_lib.lib_Version < 37) {
	Write(Output(),
	    "You should upgrade to at least 2.0 as soon as possible!\n"
	    "If any client uses a 2.0+ packet, you will crash!\n", 106);
    }

    /* Primary defaults */
    MyAddr = SRV_ADDR;
    PeerAddr = CLI_ADDR;
    NetFSPort = NETFS_PORT;

    while ((c = getopt(argc, argv, "sd:u:f:p:a:c:")) != -1) {
	switch (c) {
	case 's':
	    /* Secundary server/client */
	    MyAddr = CLI_ADDR;
	    PeerAddr = SRV_ADDR;
	    NetFSPort = NETFS_PORT + 1;
	    break;
	case 'd':
	    DevName = optarg;
	    break;
	case 'u':
	    UnitNr = strtol(optarg, NULL, 0);
	    break;
	case 'f':
	    DevFlags = strtol(optarg, NULL, 0);
	    break;
	case 'p':
	    NetFSPort = strtol(optarg, NULL, 0);
	    break;
	case 'a':
	    MyAddr = strtol(optarg, NULL, 0);
	    break;
	case 'c':
	    PeerAddr = strtol(optarg, NULL, 0);
	    break;
	case '?':
	    return 20;
	}
    }

    debug(("call OpenNetwork\n"));
    if (OpenNetwork())
	return 20;

    if (InitServer() == 0) {
	ServeLoop();
	CleanupServer();
    }

    CloseNetwork();
#ifdef DEBUG
    uninitsyslog();
#endif				/* DEBUG */
}
