/
teeny/db/
teeny/dbm/
teeny/doc/
teeny/includes/
#include	<sys/types.h>
#include	<ctype.h>
#include	<fcntl.h>
#include	<sys/time.h>
#include	<sys/errno.h>
#include	<sys/socket.h>
#include	<netinet/in.h>

/*
	Hacked up by Andrew Molitor, somewhat severely, for use with TeenyMUD.
*/

/*
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	"teeny.h"
#include	"io.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.
*/

/*
	This will no longer work with UberMud, and rather badly not. It
provides a couple more functions, and removes some. The additional function
are dispatch(), which (here) handles player creation and connection, and also
the QUIT and WHO commands. -- Andrew

*/
/*
#define	IODEBUG
*/

extern int errno;
char *malloc();
time_t time();



/* 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))) {
		warning("ioinit","cannot bind socket");
		return(1);
	}

	if(listen(serfd,5) == -1) {
		warning("ioinit","cannot listen at socket");
		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
			(void)shutdown(x,2);
			(void)close(x);
#ifdef IODEBUG
			printf("Closed connection\n");
#endif

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

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

			/* Do this now, 'cause isalive() needs  */
			/* this index entry set to zero to work */

			if(iob->typ == INPUT_PLAY){
				dropwho(iob);
				if(!isalive(iob->player)){
					deanimate(iob->player);
				}
			}
			/*
			put the Iob on free chain
			pressing the buf-ptr as a list ptr
			Do this AFTER the dropwho, else the dropwho()
			will spam the freelist (freelist uses who list ptrs)
			*/
			iob->whofwd = freeiob;
			freeiob = iob;

			break;
		}
	}
}

/*
	returns true if the specified player is still connected somewhere or
other.

*/

isalive(player)
int player;
{
	int	x;

	if(player < 0)
		return(0);

	for(x = 0; x < iobindexsiz; x++) {
		if(iobindex[x] != (Iob *)0 && iobindex[x]->player == player)
			return(1);
	}
	return(0);
}

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

	/* shut down bound socket */
	(void)shutdown(serfd,2);
	(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->whofwd;
		(void)free((char *)bp);
		bp = np;
	}
	(void)free((char *)iobindex);
#ifdef IODEBUG
	printf("Closing serfd.\n");
#endif
	(void)shutdown(serfd,2);
	(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;
	struct	timeval	timo;
	int	scnt;
	time_t	currenttime;


	timo.tv_sec = 60;
	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,(fd_set *)0,&xcpt,&timo)) < 0) {
		if(errno != EINTR) {
			warning("ioloop","select in loop failed");
			return(1);
		}
	}

	/* Cope with resetting quotas, and running timed events and stuff */

	timers();
	currenttime = time((time_t *)0);

	/* 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->whofwd;
			} else {
				bp = (Iob *)malloc(sizeof(Iob));
				if(bp == (Iob *)0) {
					warning("ioloop"
						,"cannot malloc a new Iob");
					return(1);
				}
			}

			/* initialize it */
			bp->typ = INPUT_NEWCONN;
			bp->player = -1;
			bp->state = IOSTATE_OK;
			bp->fd = n;
			bp->quota = SLICE_QUOTA;
			bp->connect = (long) currenttime;
			bp->lastcmd = (long) currenttime;
			bp->blown = 0;
			*bp->outputbuf = '\0';
			bp->outputcnt = 0;
			bp->outputtries = 0;
			*bp->inputbuf = '\0';
			bp->inputcnt = 0;

			/* 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 {
				warning("ioloop",
					"could not set socket non-blocking");
				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)) {
			bp = iobindex[n];

			bcnt = read(n,bp->inputbuf + bp->inputcnt
				,(MUDBUFSIZ-1) - bp->inputcnt);

			/* check hangups */
			if(bcnt <= 0) {
				int	fcf = bp->typ;

				iobdrop(bp);
				if(fcf != INPUT_NEWCONN){
					disconnect_player(bp->player);
				}
			} else {
				bp->inputcnt += bcnt;
				bp->inputbuf[bp->inputcnt] = '\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(bp))
					return(1);
			}
		} else
		if(iobindex[n] != (Iob *)0 && FD_ISSET(n,&xcpt)) {
			int	fcf = iobindex[n]->typ;

			warning("ioloop","connection dropped due to exception");
			iobdrop(iobindex[n]);
			if(fcf != INPUT_NEWCONN){
				disconnect_player(iobindex[n]->player);
			}
		}
	}
	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.
*/
void
iobflush(iob)
Iob	*iob;
{
	int	rv;

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

	if(iob->outputtries > 16){  /* This bloody socket is STALLED. */
		iob->outputtries = 0;
		iob->outputcnt = 0;
		return;
	}

	rv = write(iob->fd,iob->outputbuf,iob->outputcnt);

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

		warning("iobflush","dropped fd due to bad write");
		iobdrop(iob);

		if(fcf != INPUT_NEWCONN){
			disconnect_player(iob->player);
		}
		return;
	}

	/* This is a little ookey. I should mess with pointers. Cope. */
	/*		-- Andrew */

	if(rv < iob->outputcnt){
#ifdef IODEBUG
		printf("Partial write on fd %d, coping.\n",iob->fd);
#endif
		if(rv == 0){ /* No bytes written at all! */
			iob->outputtries++;
		} else {
			iob->outputtries = 0;
			iob->outputcnt -= rv;
			bcopy(iob->outputbuf+rv,iob->outputbuf,iob->outputcnt);
		}
	} else {
		iob->outputcnt = 0;
	}

}



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

/*
wall a message to all the Iobs and then flush them (Apparently not -- Andrew)
*/
/* VARARGS */ /* Not any more. */
void
notify_wall(str)  /* Was iobwall */
char *str;
{
	int	x;

	for(x = 0; x < iobindexsiz; x++)
		if(iobindex[x] != (Iob *)0)
			iobput(iobindex[x],str);
}


/*
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 */ /* Not any more. One \0 terminated string, please. */
void
iobput(iob,str)
Iob	*iob;
char	*str;
{
	register char	*s;

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

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

	s = str;
	while(*s && iob != (Iob *)0) {
		if(iob->outputcnt < MUDBUFSIZ){
			iob->outputbuf[iob->outputcnt++] = *s++;
		} else {
			iobflush(iob);
		}
	}
}




/*
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 */  /* Not any more. Single \0 terminated string. -- Andrew */
void
notify_player(player,str)  /* Formerly iobtell() */
int 	player;
char	*str;
{
	int		x;
	register char	*s;

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

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

			s = str;
			while(*s && iobindex[x] != (Iob *)0) {
				if((iobindex[x]->outputcnt) < MUDBUFSIZ)
					iobindex[x]->outputbuf
						[iobindex[x]->outputcnt++]
						= *s++;
				else
					iobflush(iobindex[x]);
			}
		}
	}
}


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

	if(!onewrt)
		return;

	if(onewrt == 1 && lastiob != (Iob *)0 && lastiob->outputcnt != 0) {
#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]->outputcnt != 0) {
#ifdef	IODEBUG
			printf("sync connection on fd %d\n",n);
#endif
			iobflush(iobindex[n]);
		}
	}

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