/* Poll.c
 *
 * This is the non-POSIX section of Xfer.c really.
 * Neither select() or poll() are part of POSIX yet, so I've split this
 * part off so we can define _POSIX_SOURCE in Xfer.c to get the POSIX
 * signal stuff declared without hiding the non-POSIX stuff.
 */

#include "Sys.h"

#include <errno.h>

#include "Util.h"
#include "Xfer.h"
#include "Poll.h"
#include "Progress.h"

#ifdef XFER_USE_SELECT
#	ifdef HAVE_SYS_BSDTYPES_H
#		include <sys/bsdtypes.h>	/* May be needed to declare fd_set. */
#	endif
#	ifdef CAN_USE_SYS_SELECT_H
#		include <sys/select.h>	/* Usually <sys/types.h> will do. */
#	endif
#else
#	ifdef XFER_USE_POLL
#		include <poll.h>
#	endif
#endif	/* XFER_USE_SELECT */

int gNumReadTimeouts;	/* Number of timeouts occurred during reads. */
int gNumWriteTimeouts;	/* Number of timeouts occurred during writes. */

extern int gNetworkTimeout;	/* Max time before aborting transfer. */

#ifdef XFER_USE_SELECT
fd_set				gReadSet, gWriteSet;
#	if POLL_LOG
fd_set				gExceptionSet;
#	endif
#endif	/* XFER_USE_SELECT */




/*ARGSUSED*/
void InitPoll(XferSpecPtr xp)
{
	gNumReadTimeouts = gNumWriteTimeouts = 0;
#ifdef XFER_USE_SELECT
	ZERO(gReadSet);
	ZERO(gWriteSet);
#	if POLL_LOG
	ZERO(gExceptionSet);
#	endif
#endif	/* XFER_USE_SELECT */
}	/* InitPoll */




/*ARGSUSED*/
int PollRead(XferSpecPtr xp)
{
#ifdef XFER_USE_SELECT
	int					nt;	/* Timeout counter. */
	int					nr;	/* Number of descriptors to check in the read set. */
	int					result;
	struct timeval		tvTimeOut;	/* Timeout length for select(). */
	int					fd;

	fd = xp->inStream;
	if (fd < 0)
		return 0;

	nr = fd + 1;

	for (nt=0; nt<kMaxConsecTimeOuts; ++nt) {
		FD_SET(fd, &gReadSet);
#if POLL_LOG
		FD_SET(fd, &gExceptionSet);
#endif	/* POLL_LOG */
		tvTimeOut.tv_sec = (long) kTimeOutLen;
		tvTimeOut.tv_usec = 0L;
		result = select(
			nr,
			&gReadSet,
			NULL,
#if POLL_LOG
			&gExceptionSet,
#else
			NULL,
#endif	/* POLL_LOG */
			&tvTimeOut
		);
		if (result > 0) {
#if POLL_LOG
			/* Check and log the exceptions. */
			if (FD_ISSET(fd, &gExceptionSet)) {
				TraceMsg("An exception occurred on the input descriptor.\n");
				/* The next loop iteration would think a timeout occurred,
				 * so subtract one here and we'll be in the same loop
				 * index next time.
				 */
				--nt;
				continue;
			}
#endif	/* POLL_LOG */
			/* Ready for reading. */
			return (0);
		} else if (result == 0) {
			++gNumReadTimeouts;
			/* While we're waiting, do a progress report. */
			if (xp->doReports)
				ProgressReport(xp, kForceUpdate);
		} else if (errno != EINTR) {
			Error(kDoPerror, "select read failed.\n");
		}
	}
	return (-1);
#else

#ifdef XFER_USE_POLL
	struct pollfd		p;
	int					nt;	/* Timeout counter. */	
	int					fd;
	int					result;

	fd = xp->inStream;
	if (fd < 0)
		return 0;
	
	for (nt=0; nt<kMaxConsecTimeOuts; ++nt) {
		p.fd = fd;
		p.events = POLLIN;
#if POLL_LOG
		p.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;
#endif	/* POLL_LOG */
		result = poll(&p, (unsigned long) 1, (1000 * kTimeOutLen));
#if POLL_LOG
		if ((p.revents & POLLIN) != 0) TraceMsg("POLLIN\n");
		if ((p.revents & POLLRDNORM) != 0) TraceMsg("POLLRDNORM\n");
		if ((p.revents & POLLRDBAND) != 0) TraceMsg("POLLRDBAND\n");
		if ((p.revents & POLLPRI) != 0) TraceMsg("POLLPRI\n");
		if ((p.revents & POLLERR) != 0) TraceMsg("POLLERR\n");
		if ((p.revents & POLLHUP) != 0) TraceMsg("POLLHUP\n");
		if ((p.revents & POLLNVAL) != 0) TraceMsg("POLLNVAL\n");
		TraceMsg("--\n");
#endif	/* POLL_LOG */
		if (result > 0) {
			if ((p.revents & POLLIN) != 0)
				return (0);
		} else if (result == 0) {
			++gNumReadTimeouts;
			/* While we're waiting, do a progress report. */
			if (xp->doReports)
				ProgressReport(xp, kForceUpdate);
		} else if (errno != EINTR) {
			Error(kDoPerror, "poll read failed.\n");
		}
	}
	return (-1);
#endif	/* XFER_USE_POLL */

#endif	/* XFER_USE_SELECT */

#ifndef XFER_USE_SELECT
#ifndef XFER_USE_POLL
	return (0);
#endif
#endif
}	/* PollRead */




/*ARGSUSED*/
int PollWrite(XferSpecPtr xp)
{
#ifdef XFER_USE_SELECT
	int					nt;	/* Timeout counter. */
	int					nw;	/* Number of descriptors to check in the write set. */
	struct timeval		tvTimeOut;	/* Timeout length for select(). */
	int					fd;
	int					result;

	fd = xp->outStream;
	if (fd < 0)
		return 0;

	nw = fd + 1;
	
	for (nt=0; nt<kMaxConsecTimeOuts; ++nt) {
		FD_SET(fd, &gWriteSet);
#if POLL_LOG
		FD_SET(fd, &gExceptionSet);
#endif	/* POLL_LOG */
		tvTimeOut.tv_sec = (long) kTimeOutLen;
		tvTimeOut.tv_usec = 0L;
		result = select(
			nw,
			NULL,
			&gWriteSet,
#if POLL_LOG
			&gExceptionSet,
#else
			NULL,
#endif	/* POLL_LOG */
			&tvTimeOut
		);
		if (result > 0) {
#if POLL_LOG
			/* Check and log the exceptions. */
			if (FD_ISSET(fd, &gExceptionSet)) {
				TraceMsg("An exception occurred on the output descriptor.\n");
				/* The next loop iteration would think a timeout occurred,
				 * so subtract one here and we'll be in the same loop
				 * index next time.
				 */
				--nt;
				continue;
			}
#endif	/* POLL_LOG */
			return (0);
		} else if (result == 0) {
			++gNumWriteTimeouts;
			/* While we're waiting, do a progress report. */
			if (xp->doReports)
				ProgressReport(xp, kForceUpdate);
		} else if (errno != EINTR) {
			Error(kDoPerror, "select write failed.\n");
		}
	}
	return (-1);
#else

#ifdef XFER_USE_POLL
	struct pollfd		p;
	int					nt;	/* Timeout counter. */	
	int					fd;
	int					result;

	fd = xp->outStream;
	if (fd < 0)
		return 0;
	
	for (nt=0; nt<kMaxConsecTimeOuts; ++nt) {
		p.fd = fd;
		p.events = POLLOUT;
#if POLL_LOG
		p.events = POLLOUT | POLLWRNORM | POLLWRBAND;
#endif	/* POLL_LOG */
		result = poll(&p, (unsigned long) 1, (1000 * kTimeOutLen));
#if POLL_LOG
		if ((p.revents & POLLOUT) != 0) TraceMsg("POLLOUT\n");
		if ((p.revents & POLLWRNORM) != 0) TraceMsg("POLLWRNORM\n");
		if ((p.revents & POLLWRBAND) != 0) TraceMsg("POLLWRBAND\n");
		if ((p.revents & POLLPRI) != 0) TraceMsg("POLLPRI\n");
		if ((p.revents & POLLERR) != 0) TraceMsg("POLLERR\n");
		if ((p.revents & POLLHUP) != 0) TraceMsg("POLLHUP\n");
		if ((p.revents & POLLNVAL) != 0) TraceMsg("POLLNVAL\n");
		TraceMsg("--\n");
#endif	/* POLL_LOG */
		if (result > 0) {
			if ((p.revents & POLLOUT) != 0)
				return (0);
		} else if (result == 0) {
			++gNumWriteTimeouts;
			/* While we're waiting, do a progress report. */
			if (xp->doReports)
				ProgressReport(xp, kForceUpdate);
		} else if (errno != EINTR) {
			Error(kDoPerror, "poll write failed.\n");
		}
	}
	return (-1);
#endif	/* XFER_USE_POLL */

#endif	/* XFER_USE_SELECT */

#ifndef XFER_USE_SELECT
#ifndef XFER_USE_POLL
	return (0);
#endif
#endif
}	/* PollWrite */

/* Poll.c */
