home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Geek Gadgets 1
/
ADE-1.bin
/
ade-dist
/
ncftp-2.3.0-src.tgz
/
tar.out
/
contrib
/
ncftp
/
Xfer.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-09-28
|
17KB
|
605 lines
/* Xfer.c */
#include "Sys.h"
#ifndef NeXT
#ifndef _POSIX_SOURCE
# define _POSIX_SOURCE 1
#endif
#endif
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 3 /* For solaris only? */
#endif
#include <signal.h>
#include <setjmp.h>
#include <errno.h>
#define _xfer_c_ 1
#include "Util.h"
#include "Main.h"
#include "Xfer.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;
/* Stores whether we had an interrupt occur during the transfer. */
int gXferAbortFlag = 0;
char *gSecondaryBufPtr;
char *gSecondaryBufLimit;
int gUsingBufferGets;
jmp_buf gXferTimeoutJmp;
int gNumReadTimeouts; /* Number of timeouts occurred during reads. */
int gNumWriteTimeouts; /* Number of timeouts occurred during writes. */
int gConsecutiveTimeouts; /* Number of timeouts in a row. */
int gTransferTimedOut; /* Flag to tell if transfer aborted with TOs. */
int gBlockTimeoutLen; /* How long we give I/O to complete. */
extern int gDebug;
extern int gStdout;
extern int gNetworkTimeout;
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 */
#ifndef ReadOrTimeout
static void IOAlarm(int sigNum)
{
alarm(0);
longjmp(gXferTimeoutJmp, 1);
} /* IOAlarm */
#endif
void ResetBlockTimeout(void)
{
gBlockTimeoutLen = gNetworkTimeout / kMaxConsecTimeOuts;
if (gBlockTimeoutLen < 2)
gBlockTimeoutLen = 2;
} /* ResetBlockTimeout */
#ifndef ReadOrTimeout
int ReadOrTimeout(const int f, char *buf, const size_t bufSize)
{
int nr;
VSig_t oa;
oa = (VSig_t) SIGNAL(SIGALRM, IOAlarm);
if (setjmp(gXferTimeoutJmp) != 0) {
(void) SIGNAL(SIGALRM, oa);
/* If the read didn't finish in x seconds, we'll return
* a timeout error. We also double the timeout length, so the
* read has more time to complete, since if it didn't finish
* in x seconds, there's no reason to assume the next attempt
* will finish in x seconds either.
*/
gBlockTimeoutLen = gBlockTimeoutLen * 2;
return (kTimeoutErr);
}
alarm((unsigned int) gBlockTimeoutLen);
nr = (int) read(f, buf, bufSize);
alarm(0);
(void) SIGNAL(SIGALRM, oa);
return (nr);
} /* ReadOrTimeout */
#endif /* ReadOrTimeout */
#ifndef WriteOrTimeout
int WriteOrTimeout(const int f, char *buf, const size_t bufSize)
{
int nr;
VSig_t oa;
oa = (VSig_t) SIGNAL(SIGALRM, IOAlarm);
if (setjmp(gXferTimeoutJmp) != 0) {
(void) SIGNAL(SIGALRM, oa);
/* If the write didn't finish in x seconds, we'll return
* a timeout error. We also double the timeout length, so the
* write has more time to complete, since if it didn't finish
* in x seconds, there's no reason to assume the next attempt
* will finish in x seconds either.
*/
gBlockTimeoutLen = gBlockTimeoutLen * 2;
return (kTimeoutErr);
}
alarm((unsigned int) gBlockTimeoutLen);
nr = (int) write(f, buf, bufSize);
alarm(0);
(void) SIGNAL(SIGALRM, oa);
return (nr);
} /* WriteOrTimeout */
#endif /* WriteOrTimeout */
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.
*/
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)
{
gXferAbortFlag = sigNum;
#if 1
/* Not a good thing to do in general from a signal handler... */
TraceMsg("XferSigHandler: SIG %d.\n", sigNum);
#endif
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)
{
#ifdef SUCK_ABORT
longstring buf;
#endif
DebugMsg("Start Abort\n");
#ifndef SUCK_ABORT
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);
}
#else
/* Fake it by just reading until EOF. */
while (read(xp->inStream, buf, sizeof(buf) > 0)
;
#endif
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;
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;
gXferAbortFlag = 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);
gNumReadTimeouts = gNumWriteTimeouts = 0;
gConsecutiveTimeouts = 0;
gTransferTimedOut = 0;
#ifdef POSIX_SIGNALS
sigemptyset(&blockSet);
sigaddset(&blockSet, SIGINT);
sigprocmask(0, NULL, &origSet);
#endif
origIntr = SIGNAL(SIGINT, XferSigHandler);
origPipe = SIGNAL(SIGPIPE, XferSigHandler);
for (errno = 0; ; ) {
/*** Read-block loop *********************************************/
for (gConsecutiveTimeouts = 0;
gConsecutiveTimeouts < kMaxConsecTimeOuts;
gConsecutiveTimeouts++)
{
if (gXferAbortFlag > 0) {
abortRead:
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 (gXferAbortFlag == SIGPIPE) {
if (gDebug)
EPrintF("\r** Broken pipe **\n");
} else if (gXferAbortFlag == SIGINT) {
EPrintF("\r** Aborting Transfer **\n");
} else {
EPrintF("\r** Aborting Transfer (%d) **\n", gXferAbortFlag);
}
DebugMsg("Read timeouts: %d; Write timeouts: %d.\n",
gNumReadTimeouts, gNumWriteTimeouts);
AbortDataTransfer(xp);
goto doneXfer; /* Not reached. */
}
/* Read the block. */
#ifdef POSIX_SIGNALS
sigprocmask(SIG_BLOCK, &blockSet, NULL);
#endif
/* The GetBlockProc should time-out as needed, and return
* kTimeoutErr when that happens.
*/
nRead = (*get)(gXferBuf, gXferBufSize, xp);
#ifdef POSIX_SIGNALS
sigprocmask(SIG_UNBLOCK, &blockSet, NULL);
#endif
if (nRead > 0L)
goto readOkay; /* Got some data, so done for now. */
if (nRead == 0L)
goto doneXfer; /* End of transmission. */
if (nRead == -1L) {
/* A generic error. */
if (errno != EINTR) {
Error(kDoPerror, "Error occurred during read!\n");
goto abortRead;
/* Note: You often get EINTR when you use ^Z. */
}
}
/* else nRead == kTimeoutErr */
/* Add one to the global counter for this data transfer. */
++gNumReadTimeouts;
/* While we're waiting, do a progress report. */
if (xp->doReports)
ProgressReport(xp, kForceUpdate);
} /* end read-block loop */
/* We only get here if we finished the loop above, which
* means we hit our limit of consecutive timeouts.
*
* Our timeout length is really a small "sub-timeout" so
* that we can squeeze in progress reports. If we
* hit X sub-timeouts in a row, we really have reached
* our network timeout, so we should abort.
*/
gTransferTimedOut = 1;
Error(kDontPerror, "Timed-out while trying to read data.\n");
goto abortRead;
readOkay:
xp->bytesTransferred += nRead;
/*** Write-block loop **********************************************/
for (gConsecutiveTimeouts = 0;
gConsecutiveTimeouts < kMaxConsecTimeOuts;
gConsecutiveTimeouts++)
{
/* This is the same glob of code as for the abortRead part. */
if (gXferAbortFlag > 0) {
abortWrite:
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 (gXferAbortFlag == SIGPIPE) {
if (gDebug)
EPrintF("\r** Broken pipe **\n");
} else if (gXferAbortFlag == SIGINT) {
EPrintF("\r** Aborting Transfer **\n");
} else {
EPrintF("\r** Aborting Transfer (%d) **\n", gXferAbortFlag);
}
DebugMsg("Read timeouts: %d; Write timeouts: %d.\n",
gNumReadTimeouts, gNumWriteTimeouts);
AbortDataTransfer(xp);
goto doneXfer; /* Not reached. */
}
/* 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 > 0L)
goto writeOkay; /* Write succeeded. */
if (nPut < -1L) {
/* Add one to the global counter for this data transfer. */
++gNumWriteTimeouts;
/* While we're waiting, do a progress report. */
if (xp->doReports)
ProgressReport(xp, kForceUpdate);
} else /* if (nPut == -1L or 0L) */ {
if (errno == EPIPE) {
goto abortWrite;
} else if (errno != EINTR) {
Error(kDoPerror, "Error occurred during write!\n");
goto abortWrite;
} /* else EINTR, fall-through and retry write. */
}
/* else nPut == kTimeoutErr, which we just continue. */
} /* End write-block loop */
/* We only get here if we finished the loop above, which
* means we hit our limit of consecutive timeouts.
*/
gTransferTimedOut = 1;
Error(kDontPerror, "Timed-out while trying to write data.\n");
goto abortWrite;
writeOkay:
/* 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);
(void) SIGNAL(SIGINT, origIntr);
(void) SIGNAL(SIGPIPE, origPipe);
#ifdef POSIX_SIGNALS
(void) sigprocmask(SIG_SETMASK, &origSet, NULL);
#endif
if (gNumReadTimeouts + gNumWriteTimeouts > 0)
DebugMsg(
"Read timeouts: %d; Write timeouts: %d; Block timeout len: %d.\n",
gNumReadTimeouts, gNumWriteTimeouts, gBlockTimeoutLen);
return (0);
} /* 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 */