/*
 *  io_stream.c - stream i/o wrapper - implementation 
 * 
 *  nc6 - an advanced netcat clone
 *  Copyright (C) 2002-2006 Chris Leishman <chris _at_ leishman.org>
 *  Copyright (C) 2001-2006 Mauro Tortonesi <mauro _at_ deepspace6.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */  
#include "system.h"
#include "io_stream.h"
#include "misc.h"
#include "parser.h"

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

RCSID("@(#) $Header: /ds6/cvs/nc6/src/io_stream.c,v 1.32 2006/01/19 22:46:23 chris Exp $");



#ifndef NDEBUG
static void ios_assert(const io_stream_t *ios)
{
	if (ios == NULL ||
	    ios->name == NULL ||
	    ios->buf_in == NULL ||
	    ios->buf_out == NULL)
	{
		fatal_internal("I/O stream assertion failed");
	}
}
#else
#define ios_assert(IOS) do {} while(0)
#endif



void ios_init_socket(io_stream_t *ios, const char *name, int fd, int socktype,
		circ_buf_t *inbuf, circ_buf_t *outbuf)
{
	/* check arguments */
	assert(ios != NULL);
	assert(name != NULL);
	assert(fd >= 0);
	assert(inbuf  != NULL);
	assert(outbuf != NULL);

	ios_init(ios, name, fd, fd, socktype, inbuf, outbuf);
}



void ios_init_stdio(io_stream_t *ios, const char *name,
		circ_buf_t *inbuf, circ_buf_t *outbuf)
{
	int fd_in, fd_out;

	/* check arguments */
	assert(ios != NULL);
	assert(name != NULL);
	assert(inbuf  != NULL);
	assert(outbuf != NULL);
	
	if ((fd_in  = dup(STDIN_FILENO)) < 0) 
		fatal("error duplicating stdin file descriptor: %s",
		      strerror(errno));
	
	if ((fd_out = dup(STDOUT_FILENO)) < 0) 
		fatal("error duplicating stdout file descriptor: %s",
		      strerror(errno));

	/* pretend stdio is a stream socket */
	ios_init(ios, name, fd_in, fd_out, SOCK_STREAM, inbuf, outbuf);
}



void ios_init(io_stream_t *ios, const char *name,
              int fd_in, int fd_out, int socktype,
              circ_buf_t *inbuf, circ_buf_t *outbuf)
{
	/* check arguments */
	assert(ios    != NULL);
	assert(name   != NULL);
	assert(fd_in  >= 0);
	assert(fd_out >= 0);
	assert(inbuf  != NULL);
	assert(outbuf != NULL);

	ios->fd_in  = fd_in;
	ios->fd_out = fd_out;
	ios->socktype = socktype;

	ios->flags = IOS_OK;

	ios->buf_in  = inbuf;
	ios->buf_out = outbuf;

	ios->mtu = 0; /* unlimited */
	ios->nru = 0; /* unlimited */

	ios->half_close_suppress = false;

	ios->idle_timeout = -1;  /* infinite  */
	gettimeofday(&(ios->last_active), NULL);

	ios->hold_time = -1;     /* infinite */
	timerclear(&(ios->read_eof));

	ios->name = xstrdup(name);
	ios->rcvd = 0;
	ios->sent = 0;
}



void io_stream_destroy(io_stream_t *ios)
{
	/* check argument */
	ios_assert(ios);
	
	ios_shutdown(ios, SHUT_RDWR);
	free(ios->name);
}



int ios_schedule_read(io_stream_t *ios)
{
	size_t space;

	/* check argument */
	ios_assert(ios);
	
	space = cb_space(ios->buf_in);
	
	/* if closed, the buffer is full or there isn't enough free space in
	 * the buffer to satisfy the nru, then we can't read */
	if ((ios->fd_in < 0) || space == 0 || space < ios->nru)
		return -1;
	
	/* schedule a read from fdin */
	return ios->fd_in;
}



int ios_schedule_write(io_stream_t *ios)
{
	/* check argument */
	ios_assert(ios);
	
	/* if closed or there is no data in the buffer, then we can't write */
	if ((ios->fd_out < 0) || cb_is_empty(ios->buf_out))
		return -1;
	
	/* schedule a write to fdout */
	return ios->fd_out;
}



struct timeval *ios_next_timeout(io_stream_t *ios, struct timeval *tv)
{
	struct timeval now;
	struct timeval *tvp = NULL;

	/* check arguments */
	ios_assert(ios);
	assert(tv != NULL);

	/* no idle timeout if idle_timeout is infinite */
	if (ios->idle_timeout > 0) {
		/* check if the idle timeout has been triggered */
		gettimeofday(&now, NULL);

		/* calculate the offset from now until the expiry */
		timersub(&(ios->last_active), &now, tv);
		tv->tv_sec += ios->idle_timeout;
		tvp = tv;

		/* check if the timeout has expired */
		if (istimerexpired(tv)) {
			if (very_verbose_mode())
				warning(_("%s idle timed out"), ios->name);
			ios->flags |= IOS_IDLE_TIMEDOUT;
			timerclear(tv);
		}
	}
	
	/* no hold timeout if read hasn't seen EOF or hold time is infinite */
	if ((ios->flags & IOS_INPUT_EOF) && (ios->hold_time >= 0)) {
		struct timeval hold_tv;
		/* check if the hold timeout has been triggered */

		if (ios->hold_time == 0) {
			/* instant timeout */
			/* set flag */
			ios->flags |= IOS_HOLD_TIMEDOUT;
			timerclear(&hold_tv);
		} else {
			/* calculate the offset from now until expiry */
			/* now may already be set from calculating idle above */
			if (tvp == NULL)
				gettimeofday(&now, NULL);
			timersub(&(ios->read_eof), &now, &hold_tv);
			hold_tv.tv_sec += ios->hold_time;
		}

		/* check if the timeout has expired */
		if (istimerexpired(&hold_tv)) {
			if (very_verbose_mode())
				warning(_("%s hold timed out"), ios->name);
			/* set flag */
			ios->flags |= IOS_HOLD_TIMEDOUT;
			timerclear(&hold_tv);
		}

		/* update tv if required */
		if ((tvp == NULL) || (timercmp(&hold_tv, tv, <))) {
			*tv = hold_tv;
			tvp = tv;
		}
	}

#ifndef NDEBUG
	if (tvp && !istimerexpired(tvp) && very_verbose_mode())
		warning("%s timer expires in %d.%06d",
		     ios->name, tvp->tv_sec, tvp->tv_usec);
#endif

	return tvp;
}



ssize_t ios_read(io_stream_t *ios)
{
	ssize_t rr;

	/* check argument */
	ios_assert(ios);
	
	/* should only be called if ios_schedule_read returned a true result */
	assert(ios->fd_in >= 0);
	assert(cb_space(ios->buf_in) >= ios->nru);

	/* read as much as possible */
	if (ios->socktype == SOCK_DGRAM)
		rr = cb_recv(ios->buf_in, ios->fd_in, 0, NULL, 0);
	else
		rr = cb_read(ios->buf_in, ios->fd_in, 0);

	if (rr > 0) {
		ios->rcvd += rr;
#ifndef NDEBUG
		if (very_verbose_mode())
			warning("read %d bytes from %s", rr, ios->name);
#endif
		/* record that the ios was active */
		gettimeofday(&(ios->last_active), NULL);

		return rr;
	} else if (rr == 0) {
		/* read eof - close read stream */
		if (very_verbose_mode())
			warning(_("read eof from %s"), ios->name);

		/* record the time eof was received */
		gettimeofday(&(ios->read_eof), NULL);

		/* set the eof flag */
		ios->flags |= IOS_INPUT_EOF;

		/* shutdown the read endpoint */
		ios_shutdown(ios, SHUT_RD);

		return IOS_EOF;
	} else if (errno == EAGAIN) {
		/* not ready? */
		return 0;
	} else {
		/* weird error */
		if (very_verbose_mode())
			warning(_("error reading from %s: %s"),
			     ios->name, strerror(errno));
		return IOS_FAILED;
	}
}



ssize_t ios_write(io_stream_t *ios)
{
	ssize_t rr;

	/* check argument */
	ios_assert(ios);
	
	/* should only be called if ios_schedule_write returned a true result */
	assert(ios->fd_out >= 0);
	assert(!cb_is_empty(ios->buf_out));

	/* write as much as the mtu allows */
	if (ios->socktype == SOCK_DGRAM)
		rr = cb_send(ios->buf_out, ios->fd_out, ios->mtu, NULL, 0);
	else
		rr = cb_write(ios->buf_out, ios->fd_out, ios->mtu);

	if (rr > 0) {
		ios->sent += rr;
#ifndef NDEBUG
		if (very_verbose_mode())
			warning("wrote %d bytes to %s", rr, ios->name);
#endif
		/* record that the ios was active */
		gettimeofday(&(ios->last_active), NULL);

		/* shutdown the write if buf_out is empty and out eof is set */
		if ((ios->flags & IOS_OUTPUT_EOF) && cb_is_empty(ios->buf_out))
			ios_shutdown(ios, SHUT_WR);

		return rr;
	} else if (rr == 0) {
		/* shouldn't happen? */
		return 0;
	} else if (errno == EAGAIN) {
		/* not ready? */
		return 0;
	} else {
		if (very_verbose_mode()) {
			if (errno == EPIPE)
				warning(_("received SIGPIPE on %s"), ios->name);
			else
				warning(_("error writing to %s: %s"),
				     ios->name, strerror(errno));
		}
		return IOS_FAILED;
	}
}



void ios_write_eof(io_stream_t *ios)
{
	/* check argument */
	ios_assert(ios);
	
	ios->flags |= IOS_OUTPUT_EOF;
	/* check if the buffer is already empty */
	if (cb_is_empty(ios->buf_out))
		ios_shutdown(ios, SHUT_WR);
}



void ios_shutdown(io_stream_t *ios, int how)
{
	/* check argument */
	ios_assert(ios);

	if (how == SHUT_RDWR) {
		/* close both the input and the output */
		if (ios->fd_in < 0 && ios->fd_out < 0)
			return;

		if (ios->fd_in >= 0)
			close(ios->fd_in);
		/* if the same fd is input and output, don't close twice */
		if (ios->fd_out >= 0 && ios->fd_out != ios->fd_in)
			close(ios->fd_out);
		if (very_verbose_mode())
			warning(_("closed %s"), ios->name);
		ios->fd_in = ios->fd_out = -1;
	} else if (how == SHUT_RD) {
		/* close the input */
		if (ios->fd_in < 0)
			return;

		/* if the fd is duplex, use shutdown */
		if (ios->fd_in == ios->fd_out) {
			if (!ios->half_close_suppress) {
				shutdown(ios->fd_in, SHUT_RD);
				if (very_verbose_mode())
					warning(_("shutdown %s for read"),
					     ios->name);
			}
		} else {
			close(ios->fd_in);
			if (very_verbose_mode())
				warning(_("closed %s for read"), ios->name);
		}
		ios->fd_in = -1;
	} else {
		assert(how == SHUT_WR);	
		
		/* close the output */
		if (ios->fd_out < 0)
			return;

		/* if the fd is duplex, use shutdown */
		if (ios->fd_in == ios->fd_out) {
			if (!ios->half_close_suppress) {
				shutdown(ios->fd_out, SHUT_WR);
				if (very_verbose_mode())
					warning(_("shutdown %s for write"),
					     ios->name);
			}
		} else {
			close(ios->fd_out);
			if (very_verbose_mode())
				warning(_("closed %s for write"), ios->name);
		}
		ios->fd_out = -1;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1