#include	<sys/types.h>
#include	<ctype.h>
#include	<varargs.h>
#include	<fcntl.h>
#include	<sys/time.h>
#include	<sys/errno.h>
#include	<sys/socket.h>
#include	<netinet/in.h>


/*
Copyright(C) 1990, Marcus J. Ranum, All Rights Reserved.
This software may be freely used, modified, and redistributed,
as long as this copyright message is left intact, and this
software is not used to develop any commercial product, or used
in any product that is provided on a pay-for-use basis.
*/

/*
network layer library for Ubermud. this comprises a completely
'hidden' network interface for TCP/IP. All I/O function calls
are contained in this file.
*/


#include	"ubermud.h"
#include	"io.h"
#include	"externs.h"

/*
this arguably should be in ubermud.h, but is not, because it is
network-implementation-specific.
*/
#define	MUDPORT	6666

/*
Routines to handle multiplexing I/O. You can plug anything you like into
the I/O routines, if you're not on a BSD machine, but follow this
interface. All you need to know is the following:

1) you will get called with ioinit() once at startup.
2) you will get called with ioloop() as long as ioloop() returns 0. each
	call to ioloop() is expected to handle calling dispatch() where
	appropriate.
3) after ioloop() returns 0, iosync() is called.
4) when ioloop() returns 1, iowrap() is called.
*/


/*
#define	IODEBUG
*/


/* old iob handles are kept around, since we may go through these fast */
static	Iob	*freeiob = (Iob *)0;

/* pointer to active Iobs by file descriptor */
static	Iob	**iobindex;
static	int	iobindexsiz;

/* a real hack, but fun. - figure it out. */
static	Iob	*lastiob = (Iob *)0;
static	int	onewrt = 0;

/* TCP specific stuff. */
static	int		serfd;
struct	sockaddr_in	addr;



/*
set everything up for I/O. only call ONCE! Need I add 'or else' ?
*/
ioinit()
{
	int	x;

	/*
	if you ain't got dtablesize(), fudge this with whatever
	your systems max file descriptor value is. erring on the
	high side will waste a little memory.
	*/
	iobindexsiz = getdtablesize();

	
	/* prep index */
	iobindex = (Iob **)malloc((unsigned)(sizeof(Iob) * iobindexsiz));
	if(iobindex == (Iob **)0)
		return(1);

	for(x = 0; x < iobindexsiz; x++)
		iobindex[x] = (Iob *)0;

	if((serfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
		return(1);

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(MUDPORT);

	if(bind(serfd,(struct sockaddr *)&addr,sizeof(addr))) {
		logf("cannot bind socket: ",(char *)-1,"\n",0);
		return(1);
	}

	if(listen(serfd,5) == -1) {
		logf("cannot listen at socket: ",(char *)-1,"\n",0);
		return(1);
	}

#ifdef	IODEBUG
	printf("I/O system ready\n");
#endif
	return(0);
}



/*
drop an Iob from the active list, stack it on the free list, and
make sure everything is cleaned. if we reach here, we are DAMN
sure not to flush it - who knows what state the connection is in.
*/
void
iobdrop(iob)
Iob	*iob;
{
	int	x;

	for(x = 0; x < iobindexsiz; x++) {
		if(iobindex[x] == iob) {
#ifdef	IODEBUG
			printf("close connection on fd %d\n",x);
#endif
			logf("close connect @",itoa(x),"\n",0);
			(void)close(x);

			/*
			put the Iob on free chain
			pressing the buf-ptr as a list ptr
			*/
			iob->bufp = (char *)freeiob;
			freeiob = iob;

			/* mark it as dead */
			iob->state = IOSTATE_HUNGUP;

			/* de-index (very important) */
			iobindex[x] = (Iob *)0;
		}
	}
}



/*
iowrap is responsible for shutting down and cleaning everything up.
*/
void
iowrap()
{
	int	x;
	Iob	*bp;

	/* shut down bound socket */
	(void)close(serfd);

	/* shut down any I/O ports open */
	for(x = 0; x < iobindexsiz; x++)
		if(iobindex[x] != (Iob *)0)
			iobdrop(iobindex[x]);

	for(bp = freeiob; bp != (Iob *)0;) {
		Iob	*np = (Iob *)bp->bufp;
		(void)free((char *)bp);
		bp = np;
	}
	(void)free((char *)iobindex);
	(void)close(serfd);
}



/*
main loop. run through all the active FDs once, handle new connections,
and return.
*/
ioloop()
{
	fd_set	ready;
	fd_set	xcpt;
	Iob	*bp;
	register int	n;
	int	bcnt;
	char	buf[MUDBUFSIZ];
	struct	timeval	timo;
	int	scnt;


	timo.tv_sec = 666;
	timo.tv_usec = 5;

	/* check for new connections */
	FD_ZERO(&ready);
	FD_ZERO(&xcpt);

	/* set the fds to check */
	FD_SET(serfd,&ready);
	for(n = 0; n < iobindexsiz; n++) {
		if(iobindex[n] != (Iob *)0) {
			FD_SET(n,&ready);
			FD_SET(n,&xcpt);
		}
	}

	if((scnt = select(iobindexsiz,&ready,0,&xcpt,&timo)) < 0) {
		if(errno != EINTR) {
			logf("select in loop failed: ",(char *)-1,"\n",0);
			return(1);
		}
	}

	/* not a very interesting run, eh ? */
	if(scnt <= 0)
		return(0);

	/* check new connections */
	if(FD_ISSET(serfd,&ready)) {
		n = accept(serfd,(struct sockaddr *)0,(int *)0);
		if(n != -1) {

			/* allocate a buffer */
			if(freeiob != (Iob *)0) {
				bp = freeiob;
				freeiob = (Iob *)freeiob->bufp;
			} else {
				bp = (Iob *)malloc(sizeof(Iob));
				if(bp == (Iob *)0) {
					logf("cannot malloc a new Iob: ",
						(char *)-1,"\n",0);
					return(1);
				}
			}

			/* initialize it */
			bp->typ = INPUT_NEWCONN;
			bp->uid = bp->euid = (long)-1;
			bp->flg = INPUT_DEFAULT;
			bp->state = IOSTATE_OK;
			bp->fd = n;
			*bp->inbuf = '\0';
			bp->bufp = bp->inbuf;
			bp->bufcnt = sizeof(bp->inbuf);

			/* refuse to talk if we cannot set no blocking */
			if(fcntl(bp->fd,F_SETFL,FNDELAY) ==  0) {
#ifdef	IODEBUG
				printf("new connection fd on %d\n",n);
#endif

				iobindex[n] = bp;

				/* introduce ourselves */
				greet(bp);
			} else {
				logf("could not set non-blocking:",(char *)-1,"\n",0);
				iobdrop(bp);
			}
		}
	}

	/* check input on existing fds. */
	for(n = 0; n < iobindexsiz; n++) {

		/* first check needed to keep from checking serfd */
		if(iobindex[n] != (Iob *)0 && FD_ISSET(n,&ready)) {
			bcnt = read(n,buf,sizeof(buf) - 1);

			/* check hangups */
			if(bcnt <= 0) {
				long	oid = iobindex[n]->uid;
				int	fcf = iobindex[n]->typ;

				iobdrop(iobindex[n]);
				if(fcf != INPUT_NEWCONN)
					call_sysfunc("_quit",oid,(long)0);
			} else {
				buf[bcnt] = '\0';
				/*
				dispatch branches on the type
				of buffer we are dealing with.
				the Iob pointer is passed, too,
				since it contains uid information
				and whatnot.

				for those of you using this outside
				of Ubermud, the iob->typ flag can
				be used in dispatch() to provide a
				simple state machine for a login
				sequence. that is all completely
				irrelevant to this code.
				*/
				if(dispatch(buf,iobindex[n]))
					return(1);
			}
		} else
		if(iobindex[n] != (Iob *)0 && FD_ISSET(n,&xcpt)) {
			long	oid = iobindex[n]->uid;
			int	fcf = iobindex[n]->typ;

			logf("dropped due to exception: ",(char *)-1,"\n",0);
			iobdrop(iobindex[n]);
			if(fcf != INPUT_NEWCONN)
				call_sysfunc("_quit",oid,(long)0);
		}
	}
	return(0);
}



/*
try to flush an Iob. if the IOB would block, avoid the flush and
leave the data in it for later, if we can.
*/
static	void
iobflush(iob)
Iob	*iob;
{
	int	rv;

	if(iob->state != IOSTATE_OK)
		return;

	rv = write(iob->fd,iob->inbuf,iob->bufp - iob->inbuf);

#ifdef	IODEBUG
	printf("attempted write %d bytes, fd %d\n",iob->bufp - iob->inbuf,iob->fd);
#endif
	if(rv < 0 && errno != EWOULDBLOCK) {
		long	oid = iob->uid;
		int	fcf = iob->typ;

		logf("dropped fd due to bad write: ",(char *)-1,"\n",0);
		iobdrop(iob);

		if(fcf != INPUT_NEWCONN)
			call_sysfunc("_quit",oid,(long)0);
		return;
	}

	/* ignore partial writes and whatnot if we can. <shrug> */
	if(rv > 0 && rv != iob->bufp - iob->inbuf && iob->bufcnt > 0)
		return;

	iob->bufp = iob->inbuf;
	iob->bufcnt = sizeof(iob->inbuf);
}



/*
put text into an Iob. if it overflows, make a half-hearted attempt to
flush it, but don't get carried away, or anything.
all arguments to iobput must be strings, and the list must be terminated
with a (char *)0. warning - some machines have limited lengths of arg
lists that can be passed with varargs, so some sense is required.
*/
/* VARARGS1 */
void
iobput(iob,va_alist)
Iob	*iob;
va_dcl
{
	register char	*s;
	va_list	ap;

	if(iob == (Iob *)0 || iob->state != IOSTATE_OK)
		return;

	if(lastiob != iob) {
		lastiob = iob;
		onewrt++;
	}

	va_start(ap);
	while((s = va_arg(ap,char *)) != (char *)0) {
		while(*s && *s != MATCH_CHAR && iob != (Iob *)0) {
			if(--iob->bufcnt)
				*iob->bufp++ = *s++;
			else
				iobflush(iob);
		}
	}
	va_end(ap);
}




/*
wall a message to all the Iobs and then flush them
*/
/* VARARGS */
void
iobwall(va_alist)
va_dcl
{
	char	*s;
	int	x;
	va_list	ap;

	va_start(ap);
	while((s = va_arg(ap,char *)) != (char *)0) {
		for(x = 0; x < iobindexsiz; x++)
			if(iobindex[x] != (Iob *)0)
				iobput(iobindex[x],s,0);
	}
	va_end(ap);
}




/*
toast connects belonging to #XX
*/
void
iobdisconnect(uid)
long	uid;
{
	int	x;
	for(x = 0; x < iobindexsiz; x++)
		if(iobindex[x] != (Iob *)0 && iobindex[x]->uid == uid) {
			iobflush(iobindex[x]);
			iobdrop(iobindex[x]);
		}
}




/*
let us know if user #XX is connected.
*/
int
iobconnected(uid)
long	uid;
{
	int	x;
	if(uid == 0L)
		return(0);
	for(x = 0; x < iobindexsiz; x++)
		if(iobindex[x] != (Iob *)0 && iobindex[x]->uid == uid)
			return(1);
	return(0);
}




/*
put text into an Iob by user-id.
it the user-id is connected more than once, they get more than one
copy of the output. as with iobput, all arguments must be strings,
and the list must be terminated with (char *)0.
*/
/* VARARGS1 */
void
iobtell(uid,va_alist)
long	uid;
va_dcl
{
	register char	*s;
	int	x;
	va_list	ap;

	for(x = 0; x < iobindexsiz; x++) {
		if(iobindex[x] != (Iob *)0 && iobindex[x]->uid == uid &&
			iobindex[x]->state == IOSTATE_OK) {

			if(lastiob != iobindex[x]) {
				lastiob = iobindex[x];
				onewrt++;
			}

			va_start(ap);
			while((s = va_arg(ap,char *)) != (char *)0) {
				while(*s && *s != MATCH_CHAR && iobindex[x] != (Iob *)0) {
					if(--iobindex[x]->bufcnt)
						*iobindex[x]->bufp++ = *s++;
					else
						iobflush(iobindex[x]);
				}
			}
			va_end(ap);
		}
	}
}




/*
flush all the Iobs.
*/
void
iosync()
{
	int	n;

	if(!onewrt)
		return;

	if(onewrt == 1 && lastiob != (Iob *)0 && lastiob->bufp != lastiob->inbuf) {
#ifdef	IODEBUG
		printf("only one iob to flush\n");
#endif
		iobflush(lastiob);
		return;
	}

	for(n = 0; n < iobindexsiz; n++) {
		if(iobindex[n] != (Iob *)0 && iobindex[n]->bufp != iobindex[n]->inbuf) {
#ifdef	IODEBUG
			printf("sync connection on fd %d\n",n);
#endif
			iobflush(iobindex[n]);
		}
	}

	onewrt = 1;
	lastiob = (Iob *)0;
}