/* Xfer.c */

#include "Sys.h"

#ifndef _POSIX_SOURCE
#	define _POSIX_SOURCE 1
#endif

#ifndef _POSIX_C_SOURCE
#	define _POSIX_C_SOURCE 3	/* For solaris only? */
#endif

#include <signal.h>
#include <errno.h>

#define _xfer_c_ 1

#include "Util.h"
#include "Main.h"
#include "Xfer.h"
#include "Poll.h"
#include "RCmd.h"
#include "FTP.h"
#include "Progress.h"

/* Large buffer to hold blocks of data during transferring. */
char *gXferBuf = NULL;

char *gSecondaryBuf = NULL;

/* Size of the transfer buffer.  */
size_t gXferBufSize = kXferBufSize;

/* A copy of the current transfer information block, in case we get
 * interrupted during the transfer itself.
 */
XferSpecPtr gCurXp;

/* Stores whether we had an interrupt occur during the transfer. */
static int gAbortFlag;

char *gSecondaryBufPtr;
char *gSecondaryBufLimit;

int gUsingBufferGets;

extern int gNumReadTimeouts, gNumWriteTimeouts, gDebug;
extern int gStdout;


void InitXferBuffer(void)
{
	/* Try allocating a big block of data.  If we fail, try halving
	 * the size and try again.
	 */ 
	gSecondaryBuf = NULL;
	gXferBuf = NULL;
	for ( ; (gXferBufSize > (size_t) 256); gXferBufSize = gXferBufSize / (size_t) 2) {
		gXferBuf = (char *) malloc(gXferBufSize);
		if (gXferBuf != NULL) {
			gSecondaryBuf = (char *) malloc(gXferBufSize);
			if (gSecondaryBuf == NULL) {
				free(gXferBuf);
			} else {
				/* Allocated both buffers. */
				return;
			}
		}
	}
	fprintf(stderr, "No memory for transfer buffer.\n");
	Exit(kExitOutOfMemory);
}	/* InitXferBuffer */




int BufferGets(char *buf, size_t bufsize, XferSpecPtr xp)
{
	int err;
	char *src;
	char *dst;
	char *dstlim;
	int len;
	int nr;

	gUsingBufferGets = 1;
	err = 0;
	dst = buf;
	dstlim = dst + bufsize - 1;		/* Leave room for NUL. */
	src = gSecondaryBufPtr;
	for ( ; dst < dstlim; ) {
		if (src >= gSecondaryBufLimit) {
			/* Fill the buffer. */

/* Don't need to poll it here.  The routines that use BufferGets don't
 * need any special processing during timeouts (i.e. progress reports),
 * so go ahead and just let it block until there is data to read.
 */
#if 0
			if (PollRead(xp) < 0) {
				Error(kDontPerror, "BufferGets: Timed-out while trying to read data.\n");
				goto done;
			}
#endif
			nr = (int) read(xp->inStream, gSecondaryBuf, gXferBufSize);
			if (nr == 0) {
				/* EOF. */
				goto done;
			} else if (nr < 0) {
				/* Error. */
				err = -1;
				goto done;
			}
			gSecondaryBufPtr = gSecondaryBuf;
			gSecondaryBufLimit = gSecondaryBuf + nr;
			src = gSecondaryBufPtr;
		}
		if (*src == '\r') {
			++src;
		} else {
			if (*src == '\n') {
				*dst++ = *src++;
				goto done;
			}
			*dst++ = *src++;
		}
	}

done:
	gSecondaryBufPtr = src;
	*dst = '\0';
	len = (int) (dst - buf);
	if (err < 0)
		return (err);
	return (len);
}	/* BufferGets */




/* We get here upon a signal we can handle during transfers. */
void XferSigHandler(int sigNum)
{
	gAbortFlag = sigNum;
	return;
}	/* XferSigHandler */




/* This initializes a transfer information block to zeroes, and
 * also initializes the two Response blocks.
 */
XferSpecPtr InitXferSpec(void)
{
	XferSpecPtr xp;
	
	xp = (XferSpecPtr) calloc(SZ(1), sizeof(XferSpec));
	if (xp == NULL)
		OutOfMemory();
	xp->cmdResp = InitResponse();
	xp->xferResp = InitResponse();
	return (xp);
}	/* InitXferSpec */




/* Disposes the transfer information block, and the responses within it. */
void DoneWithXferSpec(XferSpecPtr xp)
{
	DoneWithResponse(xp->cmdResp);
	DoneWithResponse(xp->xferResp);
	CLEARXFERSPEC(xp);
	free(xp);
}	/* DoneWithXferSpec */




void AbortDataTransfer(XferSpecPtr xp)
{
	DebugMsg("Start Abort\n");

	SendTelnetInterrupt();	/* Probably could get by w/o doing this. */
	
	/* If we aborted too late, and the server already sent the whole thing,
	 * it will just respond a 226 Transfer completed to our ABOR.  But
	 * if we actually aborted, we'll get a 426 reply instead, then the
	 * server will send another 226 reply.  So if we get a 426 we'll
	 * print that quick and get rid of it by NULLing it out;  RDataCmd()
	 * will then do its usual GetResponse and get the pending 226.
	 * If we get the 226 here, we don't want RDataCmd() to try and get
	 * another response.  It will check to see if there is already is
	 * one, and if so, not get a response.
	 */
	(void) RCmd(xp->xferResp, "ABOR");
	
	if (xp->xferResp->code == 426) {
		TraceMsg("(426) Aborted in time.\n");
		ReInitResponse(xp->xferResp);
	}
	CloseDataConnection();

	DebugMsg("End Abort\n");
}	/* AbortDataTransfer */




/* We take advantage of POSIX's signal routines.  One problem that I couldn't
 * resolve was if we were interrupted in the middle of a write(), the
 * program space was corrupted.  For example, while fetching a file, I
 * would hit interrupt, and many times the program would just choke, leaving
 * stdout munged so subsequent printf's would print a few characters of
 * garbage.
 *
 * As a work-around, we block the signals until the current i/o operation
 * completes, then abort.  Use POSIX signals instead of even more #ifdefs
 * for System V's sighold/sigrlse and BSD's sigblock, because recent versions
 * of System V and BSD both support POSIX.
 */

int DataTransfer(XferSpecPtr xp)
{
	GetBlockProc		get;
	PutBlockProc		put;
	int					in, out;
	int					errs;
	long				nRead, nPut;
	Sig_t				origIntr, origPipe;
#ifdef POSIX_SIGNALS
	sigset_t			blockSet, origSet;
#endif

	get = xp->getBlock;
	put = xp->putBlock;
	in = xp->inStream;
	out = xp->outStream;
	gAbortFlag = 0;

	/* In case we happen to use the secondary I/O buffer (i.e. want to use
	 * BufferGets), this line sets the buffer pointer so that the first thing
	 * BufferGets will do is reset and fill the buffer using real I/O.
	 */
	gSecondaryBufPtr = gSecondaryBuf + gXferBufSize;
	gUsingBufferGets = 0;

	/* Always call StartProgress, because that initializes the logging
	 * stuff too.
	 */
	StartProgress(xp);
	
	InitPoll(xp);

#ifdef POSIX_SIGNALS
	sigemptyset(&blockSet);
	sigaddset(&blockSet, SIGINT);
	sigprocmask(0, NULL, &origSet);
#endif

	origIntr = SIGNAL(SIGINT, XferSigHandler);
	origPipe = SIGNAL(SIGPIPE, XferSigHandler);

	for (errs = 0, errno = 0; ; ) {
		if (gAbortFlag > 0) {
			SIGNAL(SIGINT, SIG_IGN);	/* Don't interrupt while aborting. */
			SIGNAL(SIGPIPE, SIG_IGN);
			/* It's important to make sure that the local file gets it's times
			 * set correctly, so that reget works like it should.  When we
			 * call AbortDataTransfer, often the server just hangs up, and
			 * in that case we longjmp someplace else.
			 */
			if ((xp->netMode == kNetReading) && (xp->outStream != gStdout))
				SetLocalFileTimes(xp->doUTime, xp->remoteModTime, xp->localFileName);
			if (gAbortFlag == SIGPIPE) {
				if (gDebug)
					EPrintF("\r** Broken pipe **\n");
			} else
				EPrintF("\r** Aborting Transfer **\n");
			AbortDataTransfer(xp);
			break;
		}

		/* Wait for data to read. */
		if ((gUsingBufferGets == 0) && (PollRead(xp) < 0)) {
			Error(kDontPerror, "Timed-out while trying to read data.\n");
			++errs;
			goto doneXfer;
		}

		/* Read the block. */
#ifdef POSIX_SIGNALS
		sigprocmask(SIG_BLOCK, &blockSet, NULL);
#endif
		nRead = (*get)(gXferBuf, gXferBufSize, xp);
#ifdef POSIX_SIGNALS
		sigprocmask(SIG_UNBLOCK, &blockSet, NULL);
#endif

		if (nRead <= 0L) {
			if (nRead == 0L) {
				break;
			} else {
				/* nRead < 0. */
				if (errno != EINTR)
					Error(kDoPerror, "Error occurred during read!\n");
				break;
			}
		}

		xp->bytesTransferred += nRead;

		/* Wait until output device is ready for writing, if that
		 * device is a network descriptor. Otherwise, just write to it
		 * and let it block if it wants to.
		 */
		if (NETWRITING(xp) && (PollWrite(xp) < 0)) {
			Error(kDontPerror, "Timed-out while trying to write data.\n");
			++errs;
			goto doneXfer;
		}

		/* Write the block. */
#ifdef POSIX_SIGNALS
		sigprocmask(SIG_BLOCK, &blockSet, NULL);
#endif
		nPut = (*put)(gXferBuf, (size_t) nRead, xp);
#ifdef POSIX_SIGNALS
		sigprocmask(SIG_UNBLOCK, &blockSet, NULL);
#endif

		if (nPut == -1L) {
			if ((errno != EINTR) && (errno != EPIPE))
				Error(kDoPerror, "Error occurred during write!\n");
			break;
		}

		/* Check to see if we need to have a transfer progress report. */
		if (xp->doReports)
			ProgressReport(xp, kOptionalUpdate);
	}

doneXfer:
	/* Always call EndProgress, because that does logging too. */
	EndProgress(xp);

	/* Do this here in case an error occurred above. */
	if ((xp->netMode == kNetReading) && (xp->outStream != gStdout))
		SetLocalFileTimes(xp->doUTime, xp->remoteModTime, xp->localFileName);

	(void) SIGNAL(SIGINT, origIntr);
	(void) SIGNAL(SIGPIPE, origPipe);
#ifdef POSIX_SIGNALS
	(void) sigprocmask(SIG_SETMASK, &origSet, NULL);
#endif

#if defined(HAVE_SELECT) || defined(HAVE_POLL)
	if (gNumReadTimeouts + gNumWriteTimeouts > 0)
		DebugMsg("Read timeouts: %d;  Write timeouts: %d.\n",
			gNumReadTimeouts, gNumWriteTimeouts);
#endif

	return (errs);
}	/* DataTransfer */

/*****************************************************************************

How the XferSpec is used.
-------------------------

Because there are different ways to transfer a file, and different things to
do with the transfer data, it's difficult to have one central transferring
facility without having a lot of "if (this) else if (that)'s."  The other way
would be to have one function for each task, at the expense of redundancy and
inconvenience when general changes would have to be made for each one.

I chose to keep things really general, and make heavy use of function
pointers and parameter blocks.  I've declared a parameter block which I call
the XferSpec for lack of a catchy name.  It looks intimidating, considering
the number of fields it has, but I've designed it so you can fill in a
minimum number of fields and leave the others blank.

The key here are the function pointers.  The GetBlockProc and PutBlockProc
are the heart of the XferSpec, and are also declared in Xfer.h. This makes it
possible to generalize the transfer process, without compromising power.  In
fact, I can do much more with these than I could hacking the BSD ftp code to
special case everything.  You can design your transfer task by changing
writing the transfer functions to do what you want with the data.  You will
see many examples of different ways to munge data using GetBlockProcs and
PutBlockProcs, in Get.c, List.c, Glob.c, Put.c, and more.

The other pretty important part of the XferSpec are the responses.  A
transfer actually consists of two responses, a preliminary response, and a
concluding response.  RCmd.c's RDataCmd takes care of getting those filled
in, and you probably won't care about them from there.  The important part is
getting the XferSpec set up and handing it over to RDataCmd, which in turn
calls DataTransfer when the time comes.

Filling in the XferSpec should be straight forward.  If this is an actual
file transfer, where a file is being moved, you should fill in a few extra
fields so that transfer progress is reported to the user.  You should not do
that if you aren't doing a file transfer.  The only time you wouldn't want to
do that, is if you are doing a directory listing.  You don't want progress
reports for that.

The required fields are:
	int					netMode;
	GetBlockProc		getBlock;
	PutBlockProc		putBlock;
	int					inStream;
	int					outStream;

Use the netMode field to tell what you're doing -- reading from the network or
writing to it.  Accordingly, you need to fill in either inStream *OR*
outStream.  RDataCmd looks at netMode and fills in inStream if you're reading
from the network, or outStream if you're writing to it.  You then need to
specify the other one, which is the file you're transferring on the local
side.

The getBlock and putBlock fields are pointers to functions are declared by
you.  Your functions should write the data according to the parameters given
to them, and return the number of bytes handled.  The GetBlockProc should
return -1L only if a non-end-of-file error occurred.

Once you've filled in the XferSpecPtr, which you got by calling InitXferSpec,
you can give it to RDataCmd and let it do the rest.  When it finishes, you'll
get back a full XferSpec, but you'll probably not care about that and want to
dispose of it with DoneWithXferSpec.

*****************************************************************************/

/* eof */
