/*
 * newio.c: This is some handy stuff to deal with file descriptors in a way
 * much like stdio's FILE pointers 
 *
 * IMPORTANT NOTE:  If you use the routines here-in, you shouldn't switch to
 * using normal reads() on the descriptors cause that will cause bad things
 * to happen.  If using any of these routines, use them all 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#if 0
static	char	rcsid[] = "@(#)$Id: newio.c,v 1.24 1995/01/15 01:13:52 mrg Exp $";
#endif

#include "irc.h"
#include "ircaux.h"
#include <sys/ioctl.h>

#ifdef ISC22
# include <sys/bsdtypes.h>
#endif /* ISC22 */

#ifdef ESIX
# include <lan/net_types.h>
#endif /* ESIX */

#include "irc_std.h"

#define IO_BUFFER_SIZE 512

#ifdef HAVE_SYSCONF
# define IO_ARRAYLEN sysconf(_SC_OPEN_MAX)
#else
# ifdef FD_SETSIZE
#  define IO_ARRAYLEN FD_SETSIZE
# else
#  define IO_ARRAYLEN NFDBITS
# endif
#endif

typedef	struct	myio_struct
{
	char	buffer[IO_BUFFER_SIZE + 1];
	unsigned int	read_pos,
			write_pos;
	unsigned	misc_flags;
#ifdef ESIX
	unsigned	flags;
#endif /* ESIX */
}           MyIO;

#define IO_SOCKET 1

static	struct	timeval	right_away = { 0L, 0L };
static	MyIO	**io_rec;

static	struct	timeval	dgets_timer;
static	struct	timeval	*timer;
	int	dgets_errno = 0;

#ifdef ESIX
/* Esix must know if it is a socket or not. */
#ifdef __STDC__
void mark_socket (int des)
#else
void	mark_socket(des)
int des;
#endif
{
	if (first)
	{
		int	c;
		for (c = 0; c < FD_SETSIZE; c++)
			io_rec[c] = (MyIO *) 0;
		first = 0;
	}
	if (io_rec[des] == (MyIO *) 0)
	{
		io_rec[des] = (MyIO *) new_malloc(sizeof(MyIO));
		io_rec[des]->read_pos = 0;
		io_rec[des]->write_pos = 0;
		io_rec[des]->flags = 0;
	}
	io_rec[des]->flags |= IO_SOCKET;
}

#ifdef __STDC__
void	unmark_socket(int des)
#else
void unmark_socket (des)
int des;
#endif
{

	if (first)
	{
		int	c;
		for (c = 0; c < FD_SETSIZE; c++)
			io_rec[c] = (MyIO *) 0;
		first = 0;
	}
	if (io_rec[des] == (MyIO *) 0)
	{
		io_rec[des] = (MyIO *) new_malloc(sizeof(MyIO));
		io_rec[des]->read_pos = 0;
		io_rec[des]->write_pos = 0;
		io_rec[des]->flags = 0;
	}
	io_rec[des]->flags &= ~IO_SOCKET;
}
#endif /* ESIX */

/*
 * dgets_timeout: does what you'd expect.  Sets a timeout in seconds for
 * dgets to read a line.  if second is -1, then make it a poll.
 */
#ifdef __STDC__
extern time_t dgets_timeout (int sec)
#else
extern	time_t dgets_timeout(sec)
	int	sec;
#endif
{
	time_t	old_timeout = dgets_timer.tv_sec;

	if (sec)
	{
		dgets_timer.tv_sec = (sec == -1) ? 0 : sec;
		dgets_timer.tv_usec = 0;
		timer = &dgets_timer;
	}
	else
		timer = (struct timeval *) 0;
	return old_timeout;
}

static	void	init_io _((void))
{
	static	int	first = 1;

	if (first)
	{
		int	c, max_fd = IO_ARRAYLEN;

		io_rec = (MyIO **)new_malloc(sizeof(MyIO *) * max_fd);
		for (c = 0; c < max_fd; c++)
			io_rec[c] = (MyIO *) 0;
		(void) dgets_timeout(-1);
		first = 0;
	}
}

/*
 * dgets: works much like fgets except on descriptor rather than file
 * pointers.  Returns the number of character read in.  Returns 0 on EOF and
 * -1 on a timeout (see dgets_timeout()) 
 */
#ifdef __STDC__
int dgets (char *str, int len, int des, char *specials)
#else
int	dgets(str, len, des, specials)
char	*str;
int	len;
int	des;
char	*specials;		/* unused */
#endif
{
	int	cnt = 0,
		c;
	fd_set	rd;
	int	BufferEmpty;

	init_io();
	if (io_rec[des] == (MyIO *) 0)
	{
		io_rec[des] = (MyIO *) new_malloc(sizeof(MyIO));
		io_rec[des]->read_pos = 0;
		io_rec[des]->write_pos = 0;
		io_rec[des]->misc_flags = 0;
#ifdef ESIX
		io_rec[des]->flags = 0;
#endif /* ESIX */
	}

	while (1)
	{
		if ((BufferEmpty = (io_rec[des]->read_pos == io_rec[des]->write_pos)))
		{
			if(BufferEmpty)
			{
				io_rec[des]->read_pos = 0;
				io_rec[des]->write_pos = 0;
			}
			FD_ZERO(&rd);
			FD_SET(des, &rd);
			switch (select(des + 1, &rd, 0, 0, timer))
			{
			case 0:
				str[cnt] = (char) 0;
				dgets_errno = 0;
				return (-1);
			default:
			{
#if 0
int nbytes;
ioctl(des, FIONREAD, &nbytes);
if (x_debug) yell("FD [%d], reading [%d] bytes", des, nbytes);
#endif
#ifdef ESIX
				if (io_rec[des]->flags & IO_SOCKET)
					c = recv(des, io_rec[des]->buffer +
					  io_rec[des]->write_pos,
					  IO_BUFFER_SIZE-io_rec[des]->write_pos,
					  0);
				else
#endif /* ESIX */
					c = read(des, io_rec[des]->buffer +
					 io_rec[des]->write_pos,
					 IO_BUFFER_SIZE-io_rec[des]->write_pos);

				if (c <= 0)
				{
					if (c == 0)
						dgets_errno = -1;
					else
						dgets_errno = errno;
					return 0;
				}
				io_rec[des]->write_pos += c;
				break;
			}
			}
		}
		while (io_rec[des]->read_pos < io_rec[des]->write_pos)
		{
			if (((str[cnt++] = io_rec[des]->buffer[(io_rec[des]->read_pos)++])
				== '\n') || (cnt == len))
			{
				dgets_errno = 0;
				str[cnt] = (char) 0;
				return (cnt);
			}
		}
	}
}

/*
 * new_select: works just like select(), execpt I trimmed out the excess
 * parameters I didn't need.  
 */
#ifdef __STDC__
int new_select (fd_set *rd, fd_set *wd, struct timeval *timeout)
#else
int	new_select(rd, wd, timeout)
fd_set	*rd,
	*wd;
struct	timeval	*timeout;
#endif
{
		int	i,
			set = 0;
		fd_set 	new;
	struct	timeval	*newtimeout,
			thetimeout;
		int	max_fd = -1;
	static	int	num_fd = 0;

	if (!num_fd)
	{
		num_fd = IO_ARRAYLEN; /* why do it a zillion times? */
		if (num_fd > FD_SETSIZE)
			num_fd = FD_SETSIZE;
	}

	if (timeout)
	{
		newtimeout = &thetimeout;
		bcopy(timeout, newtimeout, sizeof(struct timeval));
	}
	else
		newtimeout = NULL;

	init_io();
	FD_ZERO(&new);
	for (i = 0; i < num_fd; i++)
	{
		if (i > max_fd && ((rd && FD_ISSET(i, rd)) || (wd && FD_ISSET(i, wd))))
			max_fd = i;
		if (io_rec[i])
		{
			if (io_rec[i]->read_pos < io_rec[i]->write_pos)
			{
				FD_SET(i, &new);
				set = 1;
			}
		}
	}
	if (set)
	{
		set = 0;
		if (select(max_fd + 1, rd, wd, NULL, &right_away) <= 0)
			FD_ZERO(rd);
		for (i = 0; i < num_fd; i++)
		{
			if ((FD_ISSET(i, rd)) || (FD_ISSET(i, &new)))
			{
				set++;
				FD_SET(i, rd);
			}
			else
				FD_CLR(i, rd);
		}
		return (set);
	}
	return (select(max_fd + 1, rd, wd, NULL, newtimeout));
}

/* new_close: works just like close */
#ifdef __STDC__
void new_close (int des)
#else
void	new_close(des)
int	des;
#endif
{
#ifdef ESIX
	if (io_rec[des]->flags & IO_SOCKET)
		t_close(des);
#endif /* ESIX */
	new_free((char **)&(io_rec[des]));
	close(des);
}

/* set's socket options */
#ifdef __STDC__
extern void set_socket_options (int s)
#else
extern	void set_socket_options(s)
	int	s;
#endif
{
#ifdef	ESIX
	mark_socket(Client->read);
#else
#ifndef NO_STRUCT_LINGER
	struct linger	lin;
#endif
	int	opt = 1;
	int	optlen = sizeof(opt);

	(void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, optlen);
	opt = 1;
	(void) setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, optlen);
#ifndef NO_STRUCT_LINGER
	lin.l_onoff = lin.l_linger = 0;
	(void) setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&lin, optlen);
#endif /* NO_STRUCT_LINGER */
#endif /* ESIX */
}
